Adding Fixtures
Now, let's consider the case in which each of the tests that you design needs a common set of objects. One approach can be to create those objects in each of the methods. Alternatively, the JUnit framework provides two special methods, setUp() and tearDown(), to initialize and clean up any common objects. This avoids duplicating the test code necessary to do the common setup and cleanup tasks. These are together referred to as fixtures. The framework calls the setup() before and tearDown() after each test methodthereby ensuring that there are no side effects from one test run to the next. This also implies that there is no fixed order in which the framework may execute the test methods during a run of the test runner. So in all cases, avoid temporal coupling of the tests. If however, it is necessary to perform the tests in some specific order, we can use the static suite() method to ensure the ordering. For example, you can expect that the tests below would be executed in the order indicated.
//define a test suite ensure the order of the tests public static Test suite() { TestSuite testsToRun = new TestSuite(); testsToRun.addTest(new MyTest("testFirstMethod")); testsToRun.addTest(new MyTest("testSecondMethod")); return testsToRun }
One important thing to remember is that one should not use the TestCase constructor for any of the setup code, as shown in the code below. Any exception to the setup code will simply be reported as an AssertonFailedError by the framework, and the resulting stack trace will not be very helpful for the developer.
// extend the TestCase class public class MyTest extends TestCase { //pass the name of the test to the super class public MyTest(String name) { super(name); // place the setup code here Bad Idea! }
Let's consider an application for a catalog of products. Each product has a name, a quantity, and a sales price. A catalog may have 0..n number of products (see Figure 3). The code for the two objects is included in the Resources section at the end of the article.
Figure 3 Object relationships for the catalog.
Very briefly, the classes have the following member variables, and access to them is provided using different get and set methods:
// Class Catalog public class Catalog { private String title; private Vector products = new Vector(); } //Class Product public class Product { private String name; private int quantity; private float price; }
Now let's consider a TestCase written to test the details of these classes:
package junittest; //import the junit.framework package import junit.framework.*; // extend the TestCase class public class CatalogTest extends TestCase { private Catalog myCatalog; private Product aProduct; /** * Constructs a CatalogTest with the specified name. * @param name Test case name. */ public CatalogTest(String name) { super(name); } /** * Sets up the test fixture initializes common objects.for tests * This is called before every test case method. */ protected void setUp() { myCatalog = new Catalog(); aProduct = new Product("Product 1", 4, 20.05); myCatalog.addProduct(aProduct); } /** * Tears down the test fixture. * Called after every test case method. */ protected void tearDown() { myCatalog = null; } /** * Tests adding a product to the catalog. */ public void testProductAdd() { int beforeAdd = myCatalog.getItemCount(); Product newProduct = new Product("Product 2", 2, 10.56); myCatalog.addProduct(newProduct); assertEquals(beforeAdd+1, myCatalog.getItemCount()); } /** * Tests removing a product by name in the catalog. * This test is successful if the * ProductNotFoundException is not raised. */ public void testProductRemoveByName() { try { int beforeRemove = myCatalog.getItemCount(); String productNameToRemove = "Product 1"; myCatalog.removeProductByName(productNameToRemove); assertEquals(beforeRemove-1, myCatalog.getItemCount()); } catch(ProductNotFoundException failure) { fail("Should never raise a ProductNotFoundException"); } } /** * Tests removing a product in the catalog. * This test is successful if the * ProductNotFoundException is not raised. */ public void testProductRemove() { try { int beforeRemove = myCatalog.getItemCount(); Product producttoRemove = new Product(aProduct); myCatalog.removeProduct(producttoRemove); assertEquals(beforeRemove-1, myCatalog.getItemCount()); } catch(ProductNotFoundException failure) { fail("Should never raise a ProductNotFoundException"); } } /** * Tests the emptying of the catalog. */ public void testClear() { myCatalog.clear(); assertTrue(myCatalog.isEmpty()); } /** * Tests removing a product that does not exist in the catalog * This test is successful if the * ProductNotFoundException is raised. */ public void testProductNotFound() { try { Product naProduct = new Product("Product NA", 2, 5.65); myCatalog.removeProduct(naProduct); fail("Should raise a ProductNotFoundException"); } catch(ProductNotFoundException success) { // successful test } } /** * Hook to define test cases to junit * * @param args Optional list of tests to execute. * @return List of tests to execute. */ public static Test suite(String[] args) { if (args.length == 0){ // run all tests defined in class. return new TestSuite(CatalogTest.class); } else{ TestSuite testsToRun = new TestSuite(); for (int i = 0; i < args.length; i++){ testsToRun.addTest(new CatalogTest(args[i])); } return testsToRun; } } /** * Main program for external invocation in command line mode * @param List of tests to execute via suite() */ public static void main(String args[]) throws ClassNotFoundException{ junit.textui.TestRunner.run(suite(args)); return; } }
The test methods should be self-explanatory. We will discuss three of the methods: the two private fixture methods setUp() and tearDown() and the public method suite(). The setUp() method is used to initialize the Catalog object and assign it one Product. The tearDown() method is used to release the Catalog object. The static suite() factory method creates a TestSuite containing the tests that the runner needs to execute. By default, it picks up all the testMethodName() calls of the TestCase. Alternatively, one can specify the individual method to be called on the command line and it builds a collection that it passes to the runner. After you have compiled the classes, go ahead and type the following on the command line:
java junit.swingui.TestRunner junittest.CatalogTest
This code runs the test in the JUnit swing test runner, as shown in Figure 4. As shown in the figure, all the tests that we defined the CatalogTest class were run successfully.
Figure 4 JUnit swing test runner.
Now type the following command:
java junittest.CatalogTest testProductAdd testProductRemove
The output looks like Figure 5. Only the two tests defined on the command line were run. This was achieved using the suite() method, as described earlier.
Figure 5 Console output for the two tests.