Four-Phase Test
The book has now been published and the content of this chapter has likely changed substanstially.Please see page 358 of xUnit Test Patterns for the latest information.
How do we structure our test logic to make what we are testing obvious?
Structure each test with four distinct parts executed in sequence.
Sketch Four Phase Test embedded from Four Phase Test.gif
How It Works
We design each test to have four distinct phases that are executed in sequence. The four parts are fixture setup, exercise SUT, result verification and fixture teardown.
- In the first phase, we set up the test fixture (the "before" picture) that is required for the SUT to exhibit the expected behavior as well as anything you need to put in place to be able to observe the actual outcome (such as using a Test Double (page X).)
- In the second phase, we interact with the SUT.
- In the third phase, we do whatever is necessary to determine whether the expected outcome has been obtained.
- In the fourth phase, we tear down the test fixture to put the world back into the state in which you found it.
Why We Do This
It is important for the test reader to be able to quickly determine what behavior the test is verifying. It can be very confusing when various behaviors of the system under test (SUT) are being invoked, some to set up the pre-test state (fixture) of the SUT, others to exercise the SUT and yet others to verify the post-test state of the SUT. Clearly identifying the four phases makes the intent of the test much easier to see.
The fixture setup phase of the test establishes the prior state of the test which is an important input to the test. The exercise SUT phase is where we actually cause the software we are testing to run. hen reading the test, it is important to be able to see what software is being run. The result verification phase of the test is where we specify the expected outcome. The final phase, fixture teardown, is all about housekeeping. We wouldn't want to obscure the important test logic with it because it is completely irrelevant from a Tests as Documentation (see Goals of Test Automation on page X) perspective.
We should avoid the temptation to test as much functionality as possible in a single Test Method (page X) because that can result in Obscure Tests (page X). In fact, it is preferable to have many small Single Condition Tests (see Principles of Test Automation on page X). Using comments to mark the phases of a Four-Phase Test is a good source of self-discipline in that it makes it very obvious when our tests are not Single Condition Tests. It will be self-evident if we have multiple exercise SUT phases separated by result verification phases or we have interspersed fixture setup and exercise SUT phases. Sure, the tests may work but they will provide less Defect Localization (see Goals of Test Automation) than if we have a bunch of independent Single Condition Tests.
Implementation Notes
We have several options for implementing the Four-Phase Test. In the simplest case, each test is completely free-standing. It does all four phases of the test within the body of the Test Method. This implies we are using Inline Setup (page X) and either Garbage-Collected Teardown (page X) or Inline Teardown (page X). This is the most appropriate choice when we are using Testcase Class per Class (page X) or Testcase Class per Feature (page X) to organize our Test Methods. In some cases it may be advantageous to set up common parts of the fixture using Implicit Setup (page X) and do the remaining setup within the Test Method.
The other choice is to take advantage of the Test Automation Framework's (page X) support for Implicit Setup and Implicit Teardown (page X). We factor out the common fixture setup and fixture teardown logic into setUp and tearDown methods on the Testcase Class (page X). This leaves only the exercise SUT and result verification phases in the Test Method. This approach is appropriate choice when we are using Testcase Class per Fixture (page X).
Example: Four Phase Test (Inline)
Here is an example of a test that is clearly a Four-Phase Test:
public void testGetFlightsByOriginAirport_NoFlights_inline() throws Exception { // Fixture setup NonTxFlightMngtFacade facade =new NonTxFlightMngtFacade(); BigDecimal airportId = facade.createTestAirport("1OF"); try { // Exercise System List flightsAtDestination1 = facade.getFlightsByOriginAirport(airportId); // Verify Outcome assertEquals( 0, flightsAtDestination1.size() ); } finally { // Fixture teardown facade.removeAirport( airportId ); } } Example FourPhaseTestInline embedded from java/com/clrstream/ex6/services/test/FourPhaseTest.java
All four phases of the Four-Phase Test are included inline. Because the calls to Assertion Methods (page X) raise exceptions, we need to surround the fixture teardown part of the Test Method with a try/finally construct to ensure that is is run in all cases.
Example: Four Phase Test (Implicit SetUp/TearDown)
Here is the same Four-Phase Test with the fixture setup and fixture teardown logic moved out of the Test Method:
NonTxFlightMngtFacade facade = new NonTxFlightMngtFacade(); private BigDecimal airportId; protected void setUp() throws Exception { // Fixture setup super.setUp(); airportId = facade.createTestAirport("1OF"); } public void testGetFlightsByOriginAirport_NoFlights_implicit() throws Exception { // Exercise SUT List flightsAtDestination1 = facade.getFlightsByOriginAirport(airportId); // Verify Outcome assertEquals( 0, flightsAtDestination1.size() ); } protected void tearDown() throws Exception { // Fixture teardown facade.removeAirport(airportId); super.tearDown(); } Example FourPhaseTestImplicit embedded from java/com/clrstream/ex6/services/test/FourPhaseTest.java
Because the tearDown method is called automatically even after test failures, we don't need the try/finally construct inside the Test Method.
Copyright © 2003-2008 Gerard Meszaros all rights reserved