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

Testcase Superclass

The book has now been published and the content of this chapter has likely changed substanstially.
Please see page 638 of xUnit Test Patterns for the latest information.
Also known as: Abstract Testcase, Abstract Test Fixture (in .Net)

Where do we put our test code when it is in reusable Test Utility Methods?

We inherit reusable test-specific logic from an abstract Testcase Superclass.

Sketch Testcase Superclass embedded from Testcase Superclass.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 Testcase Superclass is one option for where we can put reusable test logic.

How It Works

We define an abstract superclass to hold the reusable Test Utility Method that we wish to make accessible to several Testcase Classes (page X). We make the methods to be reused visible to subclasses (e.g. "protected" in Java). Use this abstract class as the superclass (base class) for any tests that wish to reuse the logic. The logic can be accessed by merely calling the method as though it were defined on the Testcase Class itself.

When To Use It

We can use a Testcase Superclass if we wish to reuse Test Utility Methods between several Testcase Classes and can find or define a Testcase Superclass that all the tests that require the logic could subclass from. This assumes that our programming language supports inheritance, we are not already using inheritance for some other conflicting purpose and the Test Utility Method doesn't need access to specific types that are not visible from the Testcase Superclass.

The decision between Testcase Superclass and Test Helper (page X) 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 types 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.

Variation: Test Helper Mixin

In languages that support mixins, they give us the best of both worlds. Like a Test Helper, we can choose which Test Helper Mixins to include without being constrained by a single-inheritance hierarchy. Like a Test Helper Object (see Test Helper), we can hold test-specific state in the mixin but we don't have to instantiate and delegate to a separate object. Like a Testcase Superclass, we can access everything as methods and attributes on self.

Implementation Notes

In variants of xUnit that require all Testcase Classes to be subclasses of a Testcase Superclass provided by the Test Automation Framework (page X), we define that class as the superclass of our Testcase Superclass. In variants that use annotations or method attributes to identify the Test Method (page X), we can choose to subclass any class that we find useful.

The methods on the Testcase Superclass can be implemented as either class methods or as instance methods. For any Test Utility Methods that are stateless, it is perfectly reasonable to use class methods. If it isn't possible to use class methods for some reason, we can use instance methods. Either way, because the methods are inherited, they can be accessed as though they were defined on the Testcase Class itself. If our language supports managing the visibility of methods, we'll need to make sure to make the methods visible enough (e.g. "protected" in Java.)

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

It is not reusable outside this particular class or it's subclasses.

Refactoring Notes

We can make the Test Utility Method more reusable by moving it to a Testcase Superclass by using a Pull Up Method[Fowler] refactoring. Since the method is inherited by our Testcase Class, we can access it as if it were defined locally. If the Test Utility Method accesses any instance variables we will have to use a Pull Up Field[Fowler] refactoring to move them to where the Test Utility Method can see them. In languages that have visibility restrictions, we may also need to make the fields visible to subclasses (e.g. default or protected in Java) if Test Methods on the Testcase Class also need to access the fields.

Example: Testcase Superclass

Since the method is inherited by our Testcase Class, we can access it as if it were defined locally. Thus, the usage looks identical.

public class TestRefactoringExample extends OurTestCase {
  
   public void testAddItemQuantity_severalQuantity_v12(){
      //  Setup Fixture
      Customer cust = createACustomer(new BigDecimal("30"));
      Product prod = createAProduct(new BigDecimal("19.99"));
      Invoice invoice = createInvoice(cust);
      // Exercise SUT
      invoice.addItemQuantity(prod, 5);
      // Verify Outcome
      LineItem expected = new LineItem(invoice, prod, 5,
            new BigDecimal("30"), new BigDecimal("69.96"));
      assertContainsExactlyOneLineItem(invoice, expected);
   }
Example TestcaseSuperclassUsage embedded from java/com/clrstream/camug/example/test/TestRefactoringExample.java

The only difference is in which class the method is defined and it's visibily:

public class OurTestCase extends TestCase {
  
   void assertContainsExactlyOneLineItem(Invoice invoice, LineItem expected) {
      List lineItems = invoice.getLineItems();
      assertEquals("number of items", lineItems.size(), 1);
      LineItem actItem = (LineItem)lineItems.get(0);
      assertLineItemsEqual("",expected, actItem);
   }
Example TestcaseSuperclassDefn embedded from java/com/clrstream/camug/example/test/OurTestCase.java

Example: Test Helper Mixin

Here are some tests written in Ruby using Test::Unit.

   def test_extref
      # setup
      sourceXml = "<extref id='abc'/>"
      expectedHtml = "<a href='abc.html'>abc</a>"
      mockFile = MockFile.new
      @handler = setupHandler(sourceXml, mockFile)
      # execute
      @handler.printBodyContents
      # verify
      assert_equals_html( expectedHtml, mockFile.output, "extref: html output")
   end
  
   def testTestterm_normal
      sourceXml = "<testterm id='abc'/>"
      expectedHtml = "<a href='abc.html'>abc</a>"
      mockFile = MockFile.new
      @handler = setupHandler(sourceXml, mockFile)
      @handler.printBodyContents
      assert_equals_html( expectedHtml, mockFile.output, "testterm: html output")
   end
  
   def testTestterm_plural
      sourceXml ="<testterms id='abc'/>"
      expectedHtml = "<a href='abc.html'>abcs</a>"
      mockFile = MockFile.new
      @handler = setupHandler(sourceXml, mockFile)
      @handler.printBodyContents
      assert_equals_html( expectedHtml, mockFile.output, "testterms: html output")
   end
Example TestCodeDuplicationRuby embedded from Ruby/CrossrefHandlerTest.rb

There is a fair bit of Test Code Duplication (page X) in these tests. We can can address this by using Extract Method[Fowler] refactoring to create a Test Utility Method and we can make it more reusable by moving it to a Test Helper Mixin by using a Pull Up Method refactoring. Since the mixed in functionality is considered part of our Testcase Class, we can access it as if it were defined locally. Thus, the usage looks identical.

class CrossrefHandlerTest  <  Test::Unit::TestCase include HandlerTest
   
   def test_extref
      sourceXml = "<extref id='abc' />"
      expectedHtml = "<a href='abc.html'>abc</a>"
      generateAndVerifyHtml(sourceXml,expectedHtml,"<extref>")
   end
Example TestHelperMixinUsage embedded from Ruby/CrossrefHandlerTest.rb

The only difference is in where the method is defined and it's visibily; Ruby requires mixins to be defined in a module rather than a class:

module HandlerTest
   def generateAndVerifyHtml( sourceXml, expectedHtml,  message, &block)
      mockFile = MockFile.new
      sourceXml.delete!("\t")
      @handler = setupHandler(sourceXml, mockFile )
      block.call unless block == nil
      @handler.printBodyContents
      actual_html = mockFile.output
      assert_equal_html( expectedHtml, actual_html, message + "html output")
       actual_html
   end
Example TestHelperMixinDefn embedded from Ruby/HandlerTest.rb


Page generated at Wed Feb 09 16:39:45 +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 "Test Organization"
Named Test Suite
Test Code Reuse:
--Test Utility Method
--Parameterized Test
Testcase Class Structure:
--Testcase Class per Feature
--Testcase Class per Fixture
--Testcase Class per Class
Utility Method Location:
--Test Helper
--Testcase Superclass
----Abstract Testcase
----Abstract Test Fixture (in .Net)
----Test Helper Mixin