• No results found

JUNIT: FRAMEWORK FOR UNIT TESTING

Unit Testing

Step 6: Repeat steps 2–5 until the story is fully implemented.

3.8 JUNIT: FRAMEWORK FOR UNIT TESTING

Unit tests provide a safety net of regression tests and validation tests so that XP programmers can refactor and integrate effectively.

In XP, the code is being developed by two programmers working together side by side. The concept is called pair programming. The two programmers sit side by side in front of the monitor. One person develops the code tactically and the other one inspects it methodically by keeping in mind the story they are implementing. It is similar to the two-person inspection strategy proposed by Bisant and Lyle [26]. Code inspection is carried out by an author–examiner pair in discrete steps, examining a small part of the implementation of the story in isolation, which is key to the success of the code review process.

3.8 JUNIT: FRAMEWORK FOR UNIT TESTING

The JUnit is a unit testing framework for the Java programming language designed by Kent Beck and Erich Gamma. Experience gained with JUnit has motivated the development of the TDD [22] methodology. The idea in the JUnit framework has been ported to other languages, including C# (NUnit), Python (PyUnit), Fortran (fUnit) and C++(CPPUnit). This family of unit testing frameworks is collectively referred to as xUnit. This section will introduce the fundamental concepts of JUnit to the reader.

Suppose that we want to test the individual methods of a class called Planet- Class. Let Move() be a method in PlanetClass such that Move() accepts only one input parameter of typeinteger and returns a value of type integer. One can follow the following steps, illustrated using pseudocode in Figure 3.4, to test Move():

• Create an object instance of PlanetClass. Let us call the instance Mars. Now we are interested in testing the method Move() by invoking it on object Mars.

• Select a value for all the input parameters of Move()—this function has just one input parameter. Let us represent the input value to Move() byx. • Know the expected value to be returned by Move(). Let the expected

returned value bey.

:

Planet Mars = new Planet(); // Instantiate class Planet to create // an object Mars.

x = ... ; // Select a value for x.

y = ... ; // The expected value to be returned by the call Move(x). z = Mars.Move(x); // Invoke method Mars() on object Mars.

if (z == y) print("Test passed"); else print("Test failed."); :

• Invoke method Move() on object Mars with input value x. Letz denote the value returned by Move().

• Now compare y withz. If the two values are identical, then the method Move() in object Mars passes the test. Otherwise, the test is said to have failed.

In a nutshell, the five steps of unit testing are as follows: • Create an object and select a method to execute. • Select values for the input parameters of the method. • Compute the expected values to be returned by the method.

• Execute the selected method on the created object using the selected input values.

• Verify the result of executing the method.

Performing unit testing leads to a programmer consuming some resources, especially time. Therefore, it is useful to employ a general programming framework to code individual test cases, organize a set of test cases as atest suite, initialize a test environment, execute the test suite, clean up the test environment, and record the result of execution of individual test cases. In the example shown in Figure 3.4, creating the object Mars is a part of the initialization process. The two print() statements are examples of recording the result of test execution. Alternatively, one can write the result of test execution to a file.

The JUnit framework has been developed to make test writing simple. The framework provides a basic class, called TestCase, to write test cases. Programmers need toextend the TestCase class to write a set of individual test cases. It may be noted that to write, for example, 10 test cases, one need not write 10 subclasses of the class TestCase. Rather, one subclass, say MyTestCase, of TestCase, can contain 10 methods —one for each test case.

Programmers need to make assertions about the state of objects while extend- ing the TestCase class to write test cases. For example, in each test case it is required to compare the actual outcome of a computation with the expected out- come. Though an if() statement can be used to compare the equality of two values or two objects, it is seen to be more elegant to write an assert statement to achieve the same. The class TestCase extends a utility class called Assert in the JUnit framework. Essentially, the Assert class provides methods, as explained in the fol- lowing, to make assertions about the state of objects created and manipulated while testing.

assertTrue(Boolean condition): This assertion passes if the condition istrue; otherwise, it fails.

assertEquals(Object expected, Object actual): This assertion passes if the expected and the actual objects are equal according to the equals() method; otherwise, the assertion fails.

assertEquals(int expected, int actual): This assertion passes if expected and actual are equal according to the= =operator; otherwise, the assertion

3.8 JUNIT: FRAMEWORK FOR UNIT TESTING 75 fails. For each primitive type int, float, double, char, byte, long, short, and boolean, the assertion has an overloaded version.

assertEquals(double expected, double actual, double tolerance): This asser- tion passes if the absolute value of the difference between expected and actual is less than or equal to the tolerance value; otherwise, the assertion fails. The assertion has an overloaded version for float inputs.

assertSame(Object expected, Object actual): This assertion passes if the expected and actual values refer to the same object in memory; otherwise, the assertion fails.

assertNull(Object testobject): This assertion passes if testobject is null; oth- erwise the assertion fails.

assertFalse(Boolean condition): This is the logical opposite of assertTrue(). The reader may note that the above list of assertions is not exhaustive. In fact, one can build other assertions while extending the TestCase class. When an assertion fails, a programmer may want to know immediately the nature of the failure. This can be done by displaying a message when the assertion fails. Each assertion method listed above accepts an optionalfirstparameter of type String— if the assertion fails, then the String value is displayed. This facilitates the programmer to display a desired message when the assertion fails. As an aside, upon failure, the assertEquals() method displays a customized message showing the expected value and the actual value. For example, an assertEquals() method can display the following:

junit.framework.AssertionFailedError: expected: <2006> but was:<2060>.

At this point it is interesting to note that only failed tests are reported. Failed tests can be reported by various means, such as displaying a message, displaying an identifier for the test case, and counting the total number of failed test cases. Essen- tially, an assertion method throws an exception, called AssertionFailedError, when the assertion fails, and JUnit catches the exception. The code shown in Figure 3.5 illustrates how the assertTrue() assertion works: When the JUnit framework catches an exception, it records the fact that the assertion failed and proceeds to the next test case. Having executed all the test cases, JUnit produces a list of all those tests that have failed.

In Figure 3.6, we show an example of a test suite containing two test cases. In order to execute the two test cases, one needs to create an object instance of

static public void assertTrue(Boolean condition) { if (!condition)

throw new AssertionFailedError(); }

import TestMe; // TestMe is the class whose methods are going //to be tested.

import junit.framework.*; // This contains the TestCase class. public class MyTestSuite extends TestCase { // Create a subclass

//of TestCase

public void MyTest1() { // This method is the first test case TestMe object1 = new TestMe( ... ); // Create an

//instance of TestMe with //desired parameters int x = object1.Method1(...); // invoke Method1

//on object1

assertEquals(365, x); // 365 and x are expected and //actual values, respectively }

public void MyTest2() { // This method is the second test case TestMe object2 = new TestMe( ... ); // Create another

//instance of TestMe //with desired parameters double y = object2.Method2(...); // invoke Method2

//on object2

assertEquals(2.99, y, 0.0001d); // 2.99 is the expected // value; y is the actual // value; 0.0001 is tolerance // level

} }

Figure 3.6 Example test suite.

MyTestSuite and invoke the two methods MyTest1() and MyTest2(). Whether or not the two methods, namely Method1() and Method()2, are to be invoked on two different instances of the class TestMe depends on the individual objectives of those two test cases. In other words, it is the programmer who decides whether or not two instances of the class TestMe are to be created.

This section is by no means a thorough exposition of the capabilities of the JUnit framework. Readers are referred to other sources, such asJUnit Recipes by Rainsberger [27] andPragmatic Unit Testingby Hunt and Thomas [28]. In addition, tools such as Korat [29], Symstra [30], and Eclat [31] for Java unit testing are being developed and used by researchers.

Outline

Related documents