Test Helper
The book has now been published and the content of this chapter has likely changed substanstially.Please see page 643 of xUnit Test Patterns for the latest information.
Where do we put our test code when it is in reusable Test Utility Methods?
We define a helper class to hold any Test Utility Methods we want to reuse in several tests.
Sketch Test Helper Class embedded from Test Helper Class.gif
As we write tests, we will invariably find ourselves needing to repeat the same logic in many many tests. Initially, we will just "clone & twiddle" as we write additional tests that need the same logic. We introduce Test Utility Methods (page X) to hold this logic but where do we put such reusable logic?
A Test Helper is one option for where we can put reusable test logic.
How It Works
We define a separate class to hold the reusable Test Utility Methods that we wish to make accessible to several Testcase Classes (page X). In each test that wishes to use this logic, we access the logic using either class method calls or via an instance created specifically for the purpose.
When To Use It
We can use a Test Helper if we wish to share logic or variables between several Testcase Classes and cannot (or choose not to) find or define a Testcase Superclass (page X) that all the tests that require the logic could subclass from. This may be because our programming language doesn't support inheritance (e.g. Visual Basic 5 or 6), because we are already using inheritance for some other conflicting purpose or if the Test Utility Method needs access to specific types that are not visible from the Testcase Superclass.
The decision between Test Helper and Testcase Superclass all comes down to type visibility. The client classes need to be able to see the Test Utility Method and the Test Utility Method needs to be able to see all the types and classes it depends on. When it doesn't depend on many or when everything it depends on is visible from a single place, the Test Utility Method can be put into a common Testcase Superclass we define for our project or company. If it depends on types/classes that cannot be seen from a single place that all the clients can see, it may be necessary to put it on a Test Helper in the appropriate test package or subsystem. In larger systems with many groups of domain objects, it is common to have one Test Helper for each group (package) of related domain objects.
Variation: Test Fixture Registry
A Registry[PEAA] is a well-known object that can be accessed from anywhere in a program. There may be one or more instances and we don't really care. What is important is that we can use them to store and retrieve objects from different parts of our program or tests. (They are often confused with Singletons[GOF] which are also well known but have only a single instance.) A Test Fixture Registry gives the tests the ability to access the same fixture as other tests in the same test run. Depending on how we implement our Test Helper, we may choose to provide a different instance of the Test Fixture Registry for each Test Runner (page X) to prevent Test Run Wars (see Erratic Test on page X). A common example of a Test Fixture Registry is the Database Sandbox (page X).
A Test Fixture Registry is most commonly used with a Setup Decorator (page X). or with Lazy Setup (page X); it isn't needed with SuiteFixture Setup (page X) because only tests on the same Testcase Class need to share the fixture and using a fixture holding class variable works well for this.
Variation: Object Mother
The Object Mother pattern is simply an aggregate of a number of other patterns that each make a small but significant contribution to making the test fixture easier to manage. The Object Mother is one or more Test Helpers that provide Creation Methods (page X) and Attachment Methods (see Creation Method) that tests use to create ready-to-use test fixture objects. Object Mothers often provide several Creation Methods that create instances of the same class where each one results in a test object in a different starting state (a Named State Reaching Method (see Creation Method).) The Object Mother may also include the ability to delete the objects it creates. This is an example of Automated Teardown (page X).
Because there is no single, crisp definition of what someone means when they say "Object Mother", it is advisable to refer to the individual patterns (such as Automated Teardown) when referring to specific capabilities of the Object Mother.
Implementation Notes
The methods on the Test Helper can be implemented as either class methods or as instance methods depending on the degree to which we want to keep the tests from interacting.
Variation: Test Helper
If all the Test Utility Methods are stateless, the simplest approach is to implement all the functionality of the Test Helper as class methods and have the tests access them using the ClassName.methodName (or equivalent) notation. If we need to hold references to fixture objects, we could hold them in class variables but we need to be careful to avoid inadvertently creating a Shared Fixture (page X) (unless, of course, that is exactly what we are trying to do. In that case, we really building a Test Fixture Registry.)
Variation: Test Helper Object
If it isn't possible to use class methods for some reason, we can use instance methods. In this case, the client test will need to instantiate the Test Helper and store it in an instance variable; the methods can then be accessed via this variable. This is a good approach when the Test Helper is holding references to fixture or system under test (SUT) objects and we want to make sure that we don't creep into a Shared Fixture situation. It is also useful when the helper is being used to store expectations for a set of Mock Objects (page X) so that we can verify that the calls are interleaved between the Mock Objects correctly.
Motivating Example
The following is a Test Utility Method that is on the Testcase Class.
public void testAddOneLineItem_quantity1() { Invoice inv = createAnonInvoice(); LineItem expItem = new LineItem(inv, product, QUANTITY); // Exercise inv.addItemQuantity(product, QUANTITY); // Verify assertInvoiceContainsOnlyThisLineItem(inv, expItem); } Example TestUtilityMethodUsage embedded from java/com/clrstream/camug/example/test/InvoiceTest.java
void assertInvoiceContainsOnlyThisLineItem( Invoice inv, LineItem expItem) { List lineItems = inv.getLineItems(); assertEquals("number of items", lineItems.size(), 1); LineItem actual = (LineItem)lineItems.get(0); assertLineItemsEqual("",expItem, actual); } Example TestUtilityMethod embedded from java/com/clrstream/camug/example/test/InvoiceTest.java
This Test Utility Method is not reusable outside this particular class.
Refactoring Notes
We can make a Test Utility Method more reusable by moving it to a Test Helper class. This is often as simple as doing a Move Method[Fowler] refactoring to our Test Helper class. What can get in the way is if we have used instance variables to pass arguments to or return data from the Test Utility Method. This "global data" need to be converted to explicit arguments and return values before we can do the Move Method refactoring.
Example: Test Helper with Class Methods
In this modifed version of the test, we have made the Test Utility Method a class method on a Test Helper so we can access it via the classname without creating an instance:
public void testAddOneLineItem_quantity1_staticHelper() { Invoice inv = createAnonInvoice(); LineItem expItem = new LineItem(inv, product, QUANTITY); // Exercise inv.addItemQuantity(product, QUANTITY); // Verify TestHelper.assertContainsExactlyOneLineItem(inv, expItem); } Example TestHelperStaticUsage embedded from java/com/clrstream/camug/example/test/InvoiceTest.java
Example: Test Helper with Instance Methods
Here, we have moved the Test Utility Method to a Test Helper as an instance method. Note that we must now access the method via an object reference (a variable that holds an instance of the Test Helper.)
public void testAddOneLineItem_quantity1_instanceHelper() { Invoice inv = createAnonInvoice(); LineItem expItem = new LineItem(inv, product, QUANTITY); // Exercise inv.addItemQuantity(product, QUANTITY); // Verify TestHelper helper = new TestHelper(); helper.assertInvContainsExactlyOneLineItem(inv, expItem); } Example TestHelperInstanceUsage embedded from java/com/clrstream/camug/example/test/InvoiceTest.java
Copyright © 2003-2008 Gerard Meszaros all rights reserved