Testcase ObjectThe book has now been published and the content of this chapter has likely changed substanstially.
Please see page 382 of xUnit Test Patterns for the latest information.
How do we run the tests?
We create a Command object for each test and call the run method when we wish to execute it.
Sketch Testcase Object embedded from Testcase Object.gif
The Test Runner (page X) needs a way to find and invoke the appropriate Test Methods (page X) and to present the results to the user. Many Graphical Test Runners (see Test Runner) let the user drill down into the tree of tests and pick individual tests to run. This requires that the Test Runner has to be able to inspect and manipulate the tests at run time.
How It Works
We instantiate a Command[GOF] object to represent each Test Method to execute. We use the Testcase Class (page X) as a Test Suite Factory (see Test Enumeration on page X) to create a Test Suite Object (page X) to hold all the Testcase Objects for a particular Testcase Class. We can use either Test Discovery (page X) or Test Enumeration to create the Testcase Objects.
Why We Do This
Treating tests as first-class objects opens up a lot of possibilities that are not available to us if we treat them as simple procedures. It is a lot easier for the Test Runner of the Test Automation Framework (page X) to manipulate tests when they are objects. We can hold them in collections (Test Suite Objects), iterate over them, invoke them, etc..
Most members of the xUnit family create a separate Testcase Object for each test to isolate the tests from each other as prescribed by Independent Test (see Principles of Test Automation on page X). Unfortunately, there is always an exception (see There's Always an Exception (page X)) and users of the affected Test Automation Frameworks need to be a bit more cautious.
Each Testcase Object implements a standard test interface so that the Test Runner does not need to know the specific interface for each test. This allows each Testcase Object to act as a Command object [GOF]. This allows us to build collections of these Testcase Objects which we can iterate across to do counting, running, displaying etc..
In most programming languages, we need to create a class to define the behavior of the Testcase Objects. We could create a separate Testcase Class for each Testcase Object but it is more convenient to host many Test Methods on a single Testcase Class as that results in fewer classes to manage and facilitates reuse of Test Utility Methods (page X). This requires a way for each Testcase Object of the Testcase Class to know which Test Method it should invoke. Pluggable Behavior[SBPP] is the most common way to do this. The constructor of the Testcase Class takes the name of the method to be invoked as a parameter and stores this name in an instance variable. When the run method is invoked by the Test Runner on the Testcase Object, it uses reflection to find and invoke the method whose name is in the variable.
Example: Testcase Object
The main evidence of the existence of Testcase Objects appears in the Test Tree Explorer (see Test Runner) when we "drill down" into the Test Suite Object to expose the Testcase Objects it contains. Here's an example from the JUnit Graphical Test Runner built into Eclipse. Here's the list of objects created from the sample code from the write-up of Testcase Class.
TestSuite("...flightstate.featuretests.AllTests") TestSuite("...flightstate.featuretests.TestApproveFlight") TestApproveFlight("testScheduledState_shouldThrowIn..ReEx") TestApproveFlight("testUnsheduled_shouldEndUpInAwai..oval") TestApproveFlight("testAwaitingApproval_shouldThrow..stEx") TestApproveFlight("testWithNullArgument_shouldThrow..ntEx") TestApproveFlight("testWithInvalidApprover_shouldTh..ntEx") TestSuite("...flightstate.featuretests.TestDescheduleFlight") TestDescheduleFlight("testScheduled_shouldEndUpInSc..tate") TestDescheduleFlight("testUnscheduled_shouldThrowIn..stEx") TestDescheduleFlight("testAwaitingApproval_shouldTh..stEx") TestSuite("...flightstate.featuretests.TestRequestApproval") TestRequestApproval("testScheduledState_shouldThrow..stEx") TestRequestApproval("testUnsheduledState_shouldEndU..oval") TestRequestApproval("testAwaitingApprovalState_shou..stEx") TestSuite("...flightstate.featuretests.TestScheduleFlight") TestScheduleFlight("testUnscheduled_shouldEndUpInSc..uled") TestScheduleFlight("testScheduledState_shouldThrowI..stEx") TestScheduleFlight("testAwaitingApproval_shouldThro..stEx") Example TestcaseClassPerFeatureObjectTree embedded from java/com/clrstream/ex3/solution/flightbooking/domain/flightstate/featuretests/FeatureTestsObjectTree.txt
The name outside the parenthesis is the name of the class while the string inside the parenthesis is the name of the object created from that class. By convention, the name of the Test Method(I've had to replace part of the name with ".." to keep each line within the page width limit.) to be run is used as the name of the Testcase Object and the name of a Test Suite Object is whatever string was passed to the constructor.
This is what it might look like when viewed in a Test Tree Explorer:
Sketch Test Tree Explorer embedded from Test Tree Explorer.gif
Copyright © 2003-2008 Gerard Meszaros all rights reserved