Standard Fixture
The book has now been published and the content of this chapter has likely changed substanstially.Please see page 305 of xUnit Test Patterns for the latest information.
Also known as: Standard Context
What fixture strategy should we use?
The same design of test fixture is used in several tests.
Sketch Standard Fixture embedded from Standard Fixture.gif
To execute an automated test, we require a text fixture that is well understood and completely deterministic. There is an effort involved in designing a custom test fixture for each test.
A Standard Fixture is a way to reuse the same fixture design in several tests without necessarily sharing the same fixture instance.
How It Works
Standard Fixture is more about attitude then technology. It is about deciding ahead of time that we will design a Standard Fixture that can be used by several or many tests rather than mining a common fixture out of tests that were designed independently. In a sense, Standard Fixture is the result of "Big Design Up Front" of the test fixture for a whole suite of tests. We then define our tests using this common test fixture design.
The choice of Standard Fixture is independent of the choice between Fresh Fixture (page X) and Shared Fixture (page X). A Shared Fixture is by definition a Standard Fixture but not the other way round because Standard Fixture focuses on reusing the design of the fixture, not when it is built or it's visibility. Having decided to use a Standard Fixture, we still need to decide whether each test will build it's own instance of the Standard Fixture (a Fresh Fixture) or whether we will build it once as a Shared Fixture and reuse it across many tests.
When To Use It
When reviewing an early draft of this book with Series Editor Martin Fowler, he asked me "Do people actually do this?" This question exemplifies the philosophical divide of fixture design. Coming from an agile background, Martin lets each test pull a fixture into existence; if several tests happen to need the same fixture then it makes sense to factor it out into the setUp method and split the class into one Testcase Class per Fixture (page X). It doesn't even occur to him to design a Standard Fixture that all tests can use. So who does?
Standard Fixtures are something of a tradition in the testing (quality assessment) community. It is very commonplace to define a large Standard Fixture that is then used as a test bed for testing activities. It makes a lot of sense in the context of manual execution of many customer tests as it removes the need for each tester to spend a lot of time setting up the test environment for each customer test. Some test automaters also use Standard Fixture when defining their automated customer tests. This is especially true when using a Shared Fixture for obvious reasons.
In the xUnit community, use of a Standard Fixture simply for the sake of avoiding designing a Minimal Fixture (page X) for each test is considered undesirable and has been given the name General Fixture (see Obscure Test on page X). Implicit Setup (page X) used in conjunction with Testcase Class per Fixture is the most common example of Standard Fixture and also the least interesting because only a few Test Methods (page X) share the design of the fixture and they share it because they need the same design. As we make a Standard Fixture more reusable across many tests with disparate needs, it tends to get larger and more complex. This can lead to Fragile Fixture (see Fragile Test on page X) as the needs of new tests introduce changes that break existing clients of the Standard Fixture. Depending on how we go about building the Standard Fixture, we may also find ourselves entertaining a Mystery Guest (see Obscure Test) if the cause/effect of the system under test's (SUT) behavior is not easy to discern either because the fixture setup is hidden from the test or because it is not clear what characteristics of the referenced part of the Standard Fixture the test is using as preconditions.
A Standard Fixture will also take longer to build than a Minimal Fixture because there is more fixture to construct. When we are
building a Fresh Fixture for each Testcase Object (page X),
this can lead to Slow Tests (page X), especially if the fixture set up
involves a database. (See the sidebar Unit Test Rulz (page X)
Include the sidebar 'Unit Test Rulz' on opposite page.
for an opinion on what is acceptable for a unit test.)
Therefore, we may be better off using a Minimal Fixture to avoid
the extra fixture setup overhead caused by creating objects that are only needed
in other tests.
Implementation Notes
As mentioned earlier, we can use a Standard Fixture as either a Fresh Fixture or a Shared Fixture and we can set it up using either Implicit Setup or Delegated Setup (page X). (Doing it with Inline Setup (page X) would be silly as we would have to copy the code to construct the Standard Fixture to every Test Method.) . When using it as a Fresh Fixture, we can define a Test Utility Method (page X) (function or procedure) that builds the Standard Fixture that we can call from each test that needs this particular design of fixture. Alternatively, we can take advantage of xUnit support for Implicit Setup by putting all the fixture construction logic in the setUp method.
When building a Standard Fixture for use as a Shared Fixture, we can employ all the fixture setup options described in Shared Fixture including SuiteFixture Setup (page X), Lazy Setup (page X) and Setup Decorator (page X).
Motivating Example
As I mentioned earlier, we are most likely to end up using a Standard Fixture because we started that way probably because of the background of one of the project participants. So it is unlikely that we would refactor to Standard Fixture when we have tests already written to use a Minimal Fixture unless it was because we were refactoring to Testcase Class per Fixture. For the sake of illustration, let's assume that we did want to get to "here" from "there". Here's an example of a building a custom Fresh Fixture for each test.
public void testGetFlightsByFromAirport_OneOutboundFlight_c() throws Exception { FlightDto outboundFlight = createOneOutboundFlightDto(); // Exercise System List flightsAtOrigin = facade.getFlightsByOriginAirport( outboundFlight.getOriginAirportId()); // Verify Outcome assertOnly1FlightInDtoList( "Flights at origin", outboundFlight, flightsAtOrigin); } public void testGetFlightsByFromAirport_TwoOutboundFlights_c() throws Exception { FlightDto[] outboundFlights = createTwoOutboundFlightsFromOneAirport(); // Exercise System List flightsAtOrigin = facade.getFlightsByOriginAirport( outboundFlights[0].getOriginAirportId()); // Verify Outcome assertExactly2FlightsInDtoList( "Flights at origin", outboundFlights, flightsAtOrigin); } Example CustomTestFixture embedded from java/com/clrstream/ex6/services/test/FlightManagementFacadeTest.java
To keep this test short, we have used Delegated Setup to populate the SUT with the Minimal Fixture needed for each test. We could have included the fixture setup code inline in each method but that would be taking us down the road of Obscure Test.
Refactoring Notes
Technically speaking, converting a pile of tests to Standard Fixture isn't really a "refactoring" because we are changing the behavior of these tests. The biggest challenge is designing the reusable Standard Fixture in such a way that each Test Method that uses it can find some part of the fixture that serves its needs. This means synthesizing all the individual purpose-built Minimal Fixtures into a single "jack of all trades" fixture. This can be a non-trivial exercise if there are a lot of tests involved.
The easy and mechanical part of the refactoring is to covert the logic in each test that constructs the fixture into calls to Finder Methods (see Test Utility Method) that retrieve the appropriate part of the Standard Fixture. This is most easily done as a two part exercise. First, we extract the inline fixture construction logic in each Test Method into one or more Creation Methods (page X) with Intent Revealing Names[SBPP]. Then we do a global replace on the "create" part of each call to "find". Finally, we generate (either manually or using our IDE's "quick fix" capability) the Finder Methods needed to get the calls to compile. Inside each Finder Methods we add in code to return the relevant part of the Standard Fixture.
Example: Standard Fixture
Here's the same example converted to use a Standard Fixture:
public void testGetFlightsByFromAirport_OneOutboundFlight() throws Exception { setupStandardAirportsAndFlights(); FlightDto outboundFlight = findOneOutboundFlight(); // Exercise System List flightsAtOrigin = facade.getFlightsByOriginAirport( outboundFlight.getOriginAirportId()); // Verify Outcome assertOnly1FlightInDtoList( "Flights at origin", outboundFlight, flightsAtOrigin); } public void testGetFlightsByFromAirport_TwoOutboundFlights() throws Exception { setupStandardAirportsAndFlights(); FlightDto[] outboundFlights = findTwoOutboundFlightsFromOneAirport(); // Exercise System List flightsAtOrigin = facade.getFlightsByOriginAirport( outboundFlights[0].getOriginAirportId()); // Verify Outcome assertExactly2FlightsInDtoList( "Flights at origin", outboundFlights, flightsAtOrigin); } Example StandardTestFixture embedded from java/com/clrstream/ex6/services/test/FlightManagementFacadeTest.java
To make the use of a Standard Fixture really obvious, I have used a Fresh Fixture that is set up explicitly in each test by calling the same Creation Method to set up the Standard Fixture (Delegated Setup). We could have achieved the same effect by putting the fixture construction logic into the setUp method thus using Implicit Setup. The resulting test would look identical to one that uses a Shared Fixture.
Copyright © 2003-2008 Gerard Meszaros all rights reserved