.com
Hosted by:
Unit testing expertise at your fingertips!
Home | Discuss | Lists

Creation Method

The book has now been published and the content of this chapter has likely changed substanstially.
Please see page 415 of xUnit Test Patterns for the latest information.
How do we construct the Fresh Fixture?

Set up the test fixture by calling methods that hide the mechanics of building ready-to-use objects behind Intent Revealing Names.

Sketch Creation Method embedded from Creation Method.gif

Fixture setup usually involves the creation of a number of objects. In many cases, the details of those objects (i.e., the attribute values) are unimportant but must be specified to satisfy each object's constructor method. Including all this unnecessary complexity within the fixture setup part of the test can lead to Obscure Test (page X) and certainly doesn't help us achieve Tests as Documentation (see Goals of Test Automation on page X)!

How can a properly initialized object be created without having to clutter the test with Inline Setup (page X)? The answer, of course, is to encapsulate this complexity. Delegated Setup (page X) moves the mechanics of the fixture setup into other methods but leaves overall control and coordination in the test itself. But what to delegate to? A Creation Method is one way we can encapsulate the mechanics of object creation so that irrelevant details do not distract the reader.

How It Works

As we write tests, we don't bother asking whether a desired utility function exists; we just use it! (It helps to pretend that we have a loyal helper sitting next to us who will quickly fill in the bodies of any functions we call that don't yet exist.) We write our tests in terms of these magic functions with Intent Revealing Names[SBPP] passing as parameters only those things that will be verified in the assertions or which should affect the outcome of the test.

Once we've written the test in this very intent-revealing style, we implement all the magic functions that we've been calling. The functions that create objects are our Creation Methods and they encapsulate all the complexity of object creation. The simple ones call the appropriate constructor passing it suitable default values for anything needed but not supplied as a parameter. If any of the constructor arguments are other objects, the Creation Method will first create those depended-on objects before calling the constructor.

The Creation Method may be placed in all the usual places that we put Test Utility Methods (page X). As usual, the decision is based on the expected scope of reuse and the Creation Method's dependencies on the API of the system under test (SUT). A related pattern is Object Mother (see Test Helper on page X). It is a combination of Creation Method, Test Helper and optionally Automated Teardown (page X).

When To Use It

We should use a Creation Method whenever we have significant complexity involved in constructing a Fresh Fixture (page X) and we value Tests as Documentation. Another key indicator for using Creation Method is that we are building the system in a highly incremental way and we expect the API of the system (and especially the object constructors) to evolve frequently. Encapsulating knowledge of how to create a fixture object is a special case of SUT API Encapsulation (see Test Utility Method) and it helps avoid Fragile Test (page X) and Obscure Test.

The main drawback of Creation Method is that it creates another API for test automaters to learn. This isn't much of a problem for the initial test developers because they are typically involved in building this API but it can create "one more thing" for new additions to the team to learn. But this API should be pretty easy to understand since it is just a set of Factory Methods[GOF] organized in some way.

If we are using a Prebuilt Fixture (page X), we should be using Finder Methods (see Test Utility Method) to locate the prebuilt objects, but we may still use Creation Methods to lay mutable objects that we plan to modify on top of an Immutable Shared Fixture (see Shared Fixture on page X).

There are several variations of Creation Method worth mentioning.

Variation: Parameterized Creation Method

While it is possible (and often very desirable) to have Creation Methods that take no parameters whatsoever, many tests will require some customization of the created object. A Parameterized Creation Method allows the test to pass in some attributes to be used in the creation of the object but we should pass only those attributes that are expected to affect (or those we want to demonstrate do not affect) the test's outcome as otherwise we could be headed down the slippery slope to Obscure Tests.

Variation: Anonymous Creation Method

An Anonymous Creation Method automatically creates a Distinct Generated Value (see Generated Value on page X) as the unique identifier for the object it is creating even though the arguments it receives may not be unique. This is invaluable for avoiding Unrepeatable Test (see Erratic Test on page X) by ensuring that every object we create is unique even across multiple test runs. If the test cares about some attributes of the object to be created, it can pass them as parameters of the Creation Method and this makes the Creation Method a Parameterized Anonymous Creation Method.

Variation: Parameterized Anonymous Creation Method

Parameterized Anonymous Creation Method is just a combination of several other variations of Creation Method in that we pass in some attributes to be used in the creation of the object but let the Creation Method create the unique identifier for it. But a Creation Method could take zero parameters if the test doesn't care about any of the attributes.

Variation: Named State Reaching Method

Some SUTs are essentially stateless meaning we can call any method at any time. But when the SUT is state-rich and the validity or behavior of methods is affected by the state in which the SUT happens to be, it is important to test each method from each possible starting state. We could chain a bunch of such tests together in a single Test Method (page X) but that would be an Eager Test (see Assertion Roulette on page X). It is better to use a series of Single Condition Tests (see Principles of Test Automation on page X) for this but that leaves us with the problem of how to set up the starting state in each test without a lot of Test Code Duplication (page X).

One obvious solution is to put all the tests that depend on the same starting state into the same Testcase Class (page X) and create the SUT in the appropriate state in the setUp method using Implicit Setup (page X) (called Testcase Class per Fixture (page X).) The alternative is to use Delegated Setup by calling a Named State Reaching Method; this allows us to choose some other way to organize our Testcase Classes.

Either way, the code that sets up the SUT will be easier to understand if it is short and sweet. That's where Named State Reaching Method comes in handy. By encapsulating the logic required to create the test objects in the correct state in a single place (whether it be on the Testcase Class or a Test Helper) we reduce the amount of code we must update if we need to change how we put the test object into that state.

Variation: Attachment Method

Say we already have a test object and we want to modify it in some way. We find yourself doing this in enough tests to want to code this once and only once. The solution is a variation on the Creation Method called Attachment Method. The main difference is that we pass in the object to be modified (one we probably had returned to us by another Creation Method) and the object we want to set one of it's attributes to. The Attachment Method does the rest of the work for us.

Implementation Notes

Most Creation Methods are created by doing an Extract Method[Fowler] refactoring on parts of an existing test. When writing tests "outside in", we write our tests assuming that the Creation Methods already exist and fill in the method bodies later. We are, in effect, defining a Higher Level Language (see Principles of Test Automation) for defining our fixtures. But there is another, completely different way to defined Creation Methods:

Variation: Reuse Test for Fixture Setup

We can set up the fixture by calling another Test Method to do the fixture setup for us. This assumes that we have some way of accessing the fixture that the other test created, either through a Registry[PEAA] object or via instance variables of the Testcase Object (page X).

It may be appropriate to implement a Creation Method this way when we already have tests that are depending on other tests to set up their test fixture but we want to reduce the likelihood of a change in test execution order of Chained Tests (page X) causing tests to fail. Mind you, the tests will run slower because each test will call all the preceding tests it depends on each time each test is run rather than each test being run only once per test run. Of course, each test only needs to call the specific tests it actually depends on, not all the tests in the test suite. This slowdown won't be very noticeable if we have replaced any slow components such as a database with a Fake Object (page X).

Wrapping the Test Method in a Creation Method is a better option than calling the Test Method directly from the client Test Method because most Test Methods are named based on which test condition(s) they verify, not what (fixture) they leave behind. The Creation Method lets us put a nice Intent Revealing Name between the client Test Method and the implementing Test Method. It also solves the Lonely Test (see Erratic Test) problem because the other test is run explicitly from within the calling test rather than just assuming that it was already run. This makes the test less fragile and easier to understand but it won't solve the Interacting Tests (see Erratic Test) problem; if the test we call fails and that leaves the test fixture in a different state than we expected, our test will likely fail also even if the functionality we are testing is still working.

Motivating Example

In the following example, the "testPuchase" test requires a Customer to fill the role of the buyer. The first and last name of the buyer have no bearing on the act of purchasing, but are required parameters of the Customer constructor; we do care that the customer's credit rating is good ("G") and that they are currently active.

   public void testPurchase_firstPurchase_ICC() {
      Customer buyer = new Customer(17, "FirstName", "LastName", "G","ACTIVE");
      // ...}
   public void testPurchase_subsequentPurchase_ICC() {
      Customer buyer = new Customer(18, "FirstName", "LastName", "G","ACTIVE");
      // ...}
Example InlineConstructorCall embedded from java/com/xunitpatterns/setup/FreshFixtureTest.java

The use of constructors in tests can be problematic, especially when we are building an application very incrementally. Every change to the parameters of the constructor will force us to visit a lot of tests or jumping through hoops trying to keep the constructor signatures backward compatible for the sake of the tests.

Refactoring Notes

We can use a Extract Method refactoring to remove the direct call to the constructor. We can give the new Creation Method an appropriate Intent Revealing Name such as createCustomer based on the style of Creation Method we have created.

Example: Annonymous Creation Method

Instead of making that direct call to the Customer constructor we now use the Customer Creation Method. Notice that the coupling between the fixture setup code and the constructor has been removed. If another parameter such as phone number is added to the Customer constructor, only the Customer Creation Method will need to be updated to provide a default value; fixture setup code is insulated from the change thanks to encapsulation.

   public void testPurchase_firstPurchase_ACM() {
      Customer buyer = createAnonymousCustomer();
      // ...}
   public void testPurchase_subsequentPurchase_ACM() {
      Customer buyer = createAnonymousCustomer();
      // ...}
Example AnonymousCreationMethodCall embedded from java/com/xunitpatterns/setup/FreshFixtureTest.java

We call this an Annonymous Creation Method because the identity of the customer does not matter. The Annonymous Creation Method might look something like this:

   public Customer createAnonymousCustomer() {
      int uniqueid = getUniqueCustomerId();
      return new Customer(uniqueid, "FirstName" + uniqueid,
                          "LastName" + uniqueid,
                          "G", "ACTIVE");
   }
Example AnonymousCreationMethodSample embedded from java/com/xunitpatterns/setup/FreshFixtureTest.java

Note the use of a Distinct Generated Value to ensure that each anonymous Customer is slightly different to avoid accidently creating an identical Customer.

Example: Parameterized Creation Method

If we had wanted to supply some of the Customer's attributes as parameters, we could have defined a Parameterized Creation Method.

   public void testPurchase_firstPurchase_PCM() {
      Customer buyer = createCreditworthyCustomer("FirstName", "LastName");
      // ...}
   public void testPurchase_subsequentPurchase_PCM() {
      Customer buyer = createCreditworthyCustomer("FirstName", "LastName");
      // ...}
Example ParameterizedCreationMethodCall embedded from java/com/xunitpatterns/setup/FreshFixtureTest.java

Here's the corresponding Parameterized Creation Method definition:

   public Customer createCreditworthyCustomer( String firstName, String lastName) {
      int uniqueid = getUniqueCustomerId();
      Customer customer = new Customer(uniqueid,firstName,lastName,"G","ACTIVE");
      customer.setCredit(CreditRating.EXCELLENT);
      customer.approveCredit();
      return customer;
   }
Example ParameterizedCreationMethodSample embedded from java/com/xunitpatterns/setup/FreshFixtureTest.java

Example: Attachment Method

Here's an example of a test that uses an Attachment Method to associate two customers to verify that both get the best discount either of them has earned or negotiated:

   public void testPurchase_relatedCustomerDiscount_AM() {
      Customer buyer = createCreditworthyCustomer("Related", "Buyer");
      Customer discountHolder = createCreditworthyCustomer("Discount", "Holder");
      createRelationshipBetweenCustomers( buyer, discountHolder);
      // ...}
Example AttachmentMethodCall embedded from java/com/xunitpatterns/setup/FreshFixtureTest.java

Behind the scenes, the Attachment Method does whatever it takes to establish the relationship:

   private void createRelationshipBetweenCustomers( Customer buyer,
                                     Customer discountHolder) {
      buyer.addToRelatedCustomersList( discountHolder );
      discountHolder.addToRelatedCustomersList( buyer );
   }
Example AttachmentMethodImpl embedded from java/com/xunitpatterns/setup/FreshFixtureTest.java

This one is pretty simple but the call to this method is still simpler to understand than reading both the method calls of which it consists.

Example: Test Reused for Fixture Setup

We can reuse other tests to set up the fixture for our test. This is an examlple of how not to do it:

   private Customer buyer;
   private AccountManager sut =  new AccountManager();
   private Account account;
  
   public void testCustomerConstructor_SRT() {
      // Exercise:
      buyer = new Customer(17, "First", "Last", "G", "ACTIVE");
      // Verify:
      assertEquals( "First", buyer.firstName(), "first");
      // ...}
   public void testPurchase_SRT() {
      testCustomerConstructor_SRT();  // leaves in field "buyer"
      account = sut.createAccountForCustomer( buyer );
      assertEquals( buyer.name, account.customerName, "cust");
      // ...}
Example ReuseTestSimpleExample embedded from java/com/xunitpatterns/setup/FreshFixtureTest.java

The problem is twofold: First, the name of the Test Method we are calling describes what it verifies (name, etc.) and not what it leaves behind (a Customer in the buyer field. Second, the test does not return a Customer; it leaves it in an instance variable. This only works because the Test Method we want to reuse is on the same Testcase Classs; if it were on an unrelated class, we would have to do a few backflips to access the buyer. A better way to do this is to encapsulate this call behind a Creation Method:

   private Customer buyer;
   private AccountManager sut =  new AccountManager();
   private Account account;
  
   public void testCustomerConstructor_RTCM() {
      // Exercise:
      buyer = new Customer(17, "First", "Last", "G", "ACTIVE");
      // Verify:
      assertEquals( "First", buyer.firstName(), "first");
      // ...}
   public void testPurchase_RTCM() {
      buyer = createCreditworthyCustomer();
      account = sut.createAccountForCustomer( buyer );
      assertEquals( buyer.name, account.customerName, "cust");
      // ...}
   public Customer createCreditworthyCustomer() {
      testCustomerConstructor_RTCM();
      return buyer;
      // ...}
Example ReuseTestInCreationMethod embedded from java/com/xunitpatterns/setup/FreshFixtureTest.java

Notice how much more readable this test has become? We can see where the buyer came from!



Page generated at Wed Feb 09 16:39:35 +1100 2011

Copyright © 2003-2008 Gerard Meszaros all rights reserved

All Categories
Introductory Narratives
Web Site Instructions
Code Refactorings
Database Patterns
DfT Patterns
External Patterns
Fixture Setup Patterns
Fixture Teardown Patterns
Front Matter
Glossary
Misc
References
Result Verification Patterns
Sidebars
Terminology
Test Double Patterns
Test Organization
Test Refactorings
Test Smells
Test Strategy
Tools
Value Patterns
XUnit Basics
xUnit Members
All "Fixture Setup Patterns"
Fresh Fixture Setup:
--Inline Setup
--Delegated Setup
----Creation Method
------Parameterized Creation Method
------Anonymous Creation Method
------Parameterized Anonymous Creation Method
------Named State Reaching Method
------Attachment Method
------Reuse Test for Fixture Setup
--Implicit Setup
Shared Fixture Construction:
--Prebuilt Fixture
--Lazy Setup
--SuiteFixture Setup
--Setup Decorator
--Chained Tests