Section 13.5. Writing Test Cases


13.5. Writing Test Cases

Writing a test case for your own Java code consists, at its simplest, of writing a new class for each class that you want to test. But this class that you create is built in a special way so that the test harness of JUnit can execute it. That is, the test case class that you create should meet certain naming conventions, so that the JUnit test runners can find what they need in order to run your tests.

More specifically, your test cases will extend the JUnit class TestCase. Now, TestCase is an abstract class, meaning there are parts that you have to fill in (i.e., methods that you must write) to make it a working class. Moreover, TestCase implements (in the Java sense of the word) the Test interface. Can you begin to see how the TestCase class is a framework? It defines the rough outline of how the test cases will look so that a common test runner can run any test case, no matter who wrote it.

Let's look at a simple example, to see what such a test case looks like. Example 13.2 shows one for testing our Account class.

Example 13.2. Simple test case
 package net.multitool.core; import java.util.*;            // needed by our class import net.multitool.util.*;   // needed by our class import junit.framework.*;      // needed by JUnit /**  * for JUnit testing of Account.java  */ public class AccountTest   extends TestCase {   // our test instrumentation:   Account base; // run before each test case: protected void setUp() {   base = new Account("Base", new User("testuser"), "150"); } // our one test case public void testCreateSub() {   // Create a subaccount, assigning $50 of our pool of $150.   Account sub1 = base.createSub("sub1", "50");   // Make sure that it created something.   assertNotNull("Couldn't create sub1", sub1);   // Now a 2nd subaccount.   Account sub2 = base.createSub("sub2", "75");   assertNotNull("Couldn't create sub2", sub2);   // Now a 3rd subaccount, to use up all the $.   Account sub3 = base.createSub("sub3", "25");   assertNotNull("Couldn't create sub3", sub3);   // We should have the same total that we started with.   assertEquals(150, base.getTotal().getDollars());   // We should have used up all our $.   assertEquals(0, base.getBalance().getDollars());   // Be sure the (sub)account lookup works:   Account ex2 = base.getSub("sub2");   assertNotNull("Couldn't find sub2", ex2);   assertSame(sub2, ex2);  } // testCreateSub } // class AccountTest 

Notice how we've named our test case class. We take the name of the class and append Test to the end. This is convenient for uswe can easily see which classes have test cases; but more importantly, JUnit can use this and other naming conventions to derive the test case names (more on that later). Notice also that the method in the Account class that we want to test, called createSub(), gets exercised by a method named testCreateSub()we prepend the word "test" to the method name, and capitalize the now-no-longer-first letter. Again, JUnit will use this naming convention, along with introspection, to automatically derive the test names from the actual method names (more on that later, too). The naming conventions we've seen so far are summarized in Table 13.1.

Table 13.1. JUnit Naming
 

In your original code

In your test case

Class

MyClass

MyClassTest

Method

myMethod

testMyMethod


Let's take a quick look at the code. We import the framework for JUnit test cases, so that the compiler can resolve the names that deal with JUnit stuff. The TestCase class that we extend is part of that JUnit stuff. It's an abstract class that defines much of what we use for testing. We just fill in what we need.

The TestCase class defines a method called setUp(). The setUp() method is called not just once, but before every test method is called. That way you can initialize variables and get into a known state before each test. Since it's already defined in the TestCase class, we can override it (as in our example) to do what we want, or we can not include it in our class and get the default behavior from TestCase (which is to do nothing).

There is also a method named tearDown() which you can override if you need to close things up at the end of a test case (e.g., close a database connection). As with setUp(), its default behavior, as defined in TestCase, is to do nothing.

The test case itselfthe method where we will exercise our classis called testCreateSub (since we want to test our createSub() method). Inside such a method (and we could have more than one) we write code which uses the objects in our application. Then at various junctures in the code we make assertions about the state of thingsfor example, this variable should be non-null, or this expression should have this particular value.

Those assertions are, to our way of thinking, the tests. We're testing to see if the subaccount was created, or if the main account did, indeed, use up all of its dollars in allocation to the subaccounts. But they are not what is called tests by JUnit. Rather, each individual method in a test class is considered a single test. Such test methods are, typically, a collection of assertions surrounding the use of a single (application) method. So in our example, the method testCreateSub() is a single JUnit test which asserts various conditions about various invocations of the createSub() method. Note that all of the assertions encountered in the execution of the test class must pass for the test to pass.

So what happens if an assertion fails? The assert method will throw an exception, reporting the failure. In JUnit terminology, a failure is a test that didn't pass, whereas an error is a problem with the running of the test. A missing class or a null pointer exception are errors, whereas an assertNotNull() call failing is considered a test failure.

The handy thing about the exceptions that the assert methods throw is that they are, technically speaking, not java.lang.Exception throwables but rather belong to the java.lang.Error type of throwable. (Don't confuse this technical Java use of the word "error" with our more informal use in the previous discussion of failure versus error.) To quote from the Javadoc page for java.lang.Error:

A method is not required to declare in its throws clause any subclasses of Error that might be thrown during the execution of the method but not caught, since these errors are abnormal conditions that should never occur.

So the use of Error by JUnit's various assert methods is done simply as a convenience for us test developers, so that we don't have to put throws ... clauses on all of our method declarations.

13.5.1. JUnit Assertions

These are the various test assertions available with JUnit:

  • assertEquals(), comparing

  • boolean with boolean

  • char with char

  • short with short

  • int with int

  • long with long

  • float with float

  • double with double

  • Object with Object

  • String with String

  • assertTrue( boolean expression )

  • assertFalse( boolean expression )

  • assertNull (Object)

  • assertNotNull (Object)

  • assertSame (Object1, Object2)

  • assertNotSame (Object1, Object2)

  • fail()

Each of the assert methods comes in two "flavors," one with a message String and one without. For example, there is a method assertTrue() which takes a boolean as its parameter; typically it would be used with an expression, for example:[1]

[1] Yes, the extra parentheses are not needed; they just make the point that this is a boolean expression being passed as the argument to assertTrue(). We could also have written it as:

 boolean result = (sample actual); assertTrue(result); 

Again, the extra parentheses are used just to make it clearer.

 assertTrue( (sample actual) ); 

If the condition is not true, an AssertionFailedError is thrown. That means, among other things, that if/when your test fails, it will stop executing at that point. The tearDown() method, though, will still be executed before proceeding to the next test.

There is also a method of the same name, assertTrue(), but with a slightly different signatureit adds a String as its first parameter. The string is the message to be included in the error report. Using this variation on assertTrue(), our example would become:

 assertTrue("Sample too small", (sample actual)); 

In the same way, assertFalse() has two versionsassertFalse(boolean) and assertFalse(String, boolean)and so on for all other assert methods.

The String message is very helpful when you get large numbers of comparisons and assertions inside your test cases. It can help you identify which assert in which test failed.

Tip

When writing your assertions, keep in mind the difference between assertEquals() and assertSame(). The latter will test if the two arguments refer to the very same instance of an object, whereas the former only checks to see that their values are equal. So any two references to objects that are the same will also be equal, but not vice versa. For example:

 String sample = "value"; String others = "more value".substring(5); assertEquals(sample, others); // will pass assertSame(sample, others); // will fail 


Digging a little deeper into how all this works, it might be worth pointing out that the JUnit TestCase class, while an abstract class itself, is also an extension of another class, the Assert class. The Assert class is the class that defines all these public static methods for asserting the various conditions (see the list above). That is why you don't need any qualifiers on the various assert calls. They are all part of your test case by virtue of it extending TestCase. It also means that you could override any of them to get special behavior. This might be useful for assertEquals(Object, Object), to allow you to compare objects of your own kinds, but we don't recommend this. You are better off overriding the equals() method of your own object than messing with the JUnit methods. And remember that if you override those behaviors, your tests will only be as good as your implementation of the assert mechanisms.

13.5.2. Running a Test Case

Recall how we ran the JUnit self-tests after installation. We can now use a similar command to execute our own test case. With the CLASSPATH still set as above, try compiling and running the test case:

 $ javac net/multitool/core/AccountTest.java $ java junit.textui.TestRunner net.multitool.core.AccountTest 

The TestRunner will use introspection and reflection to dig information out of the AccountTest class. It will find all the public methods that begin with test and have no parameters. It will execute setUp(), then one of the test methods, then tearDown(); then setUp(), then another test method, then tearDown(), and so on. Our example has only one test method, testCreateSub(), so that will be the one test method it runs.

The result of running the test should look like this:

 $ java junit.textui.TestRunner net.multitool.core.AccountTest . Time: 0.071 OK (1 test) $ 



    Java Application Development with Linux
    Java Application Development on Linux
    ISBN: 013143697X
    EAN: 2147483647
    Year: 2004
    Pages: 292

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net