===== JUnit v4 Basics ===== ==== Introduction ==== JUnit is an open-source testing framework. It provides a way to write, organize, and run repeatable test. This page provides a brief introduction to v4. Information is also available about [[student:junit:v5 | version 5 (Jupiter) ]]. ==== Creating Test Classes ==== For each class you want to test, you must create a file. It is common to use naming system //ClassName//Test.java where //ClassName// denotes the name of the class being tested. So, for example, if you want to test the class named ''%%Atom%%'' you would create a file named ''%%AtomTest.java%%''. The //ClassName//Test.java file must begin with the following line: import static org.junit.Assert.*; import org.junit.Test; and must include a class declaration of the following form: ''public class //ClassName//Test'' So, continuing with the example above, ''%%AtomTest.java%%'' would initially contain the following: import static org.junit.Assert.*; import org.junit.Test; public class AtomTest { } (Note: The ''%%import static%%'' allows you to refer to static members in the ''%%org.junit.Assert%%'' package without having to include the class name.) The file //ClassName//Test.java contains methods that can be used to test the class defined in //ClassName//.java. Each such method is preceded by an ''%%@Test%%'' annotation. (Note: An annotation provides information about a program but is not part of the program. Annotations have no effect on the operation of the program. Instead, they are used to provide information to tools that might use the program as input.) So, still continuing with the example above, if the ''%%Atom%%'' class contains a ''%%public int getAtomicNumber()%%'' method, then ''%%AtomTest.java%%'' might include the following method: /** * Unit tests for the getAtomicNumber() method */ @Test public void testGetAtomicNumber() { } Note that the name of this method is completely arbitrary, but, as always, descriptive names are always a good idea. Some people simply use the name of the method being tested (as above), while others add a prefix or suffix that includes some form of the word "test" (e.g., ''%%testGetAtomicNumber()%%'', ''%%getAtomicNumber_Test()%%'', etc...). The body of the methods in the //ClassName//Test.java must contain the test cases for the corresponding methods in the //ClassName// class. These test cases often involve some "setup" code and a call to the ''%%Assert.assertEquals()%%'' method. So, for example, the ''%%getAtomicNumber%%'' method in the ''%%Atom%%'' class is supposed to return the atomic number of the calling ''%%Atom%%'' object. To partially test this method one might implement the ''%%testGetAtomicNumber()%%'' method in the ''%%AtomTest%%'' class as follows: /** * Unit tests for the getAtomicNumber() method */ @Test public void testGetAtomicNumber() { Atom o; o = new Atom("O", 8, 16); assertEquals("Oxygen", 8, o.getAtomicNumber()); // Note: // This is a call to Assert.assertEquals() but the class name // isn't needed because of the static import } In the "setup" portion, this method creates an ''%%Atom%%'' objects (for oxygen). It then calls the ''%%Assert.assertEquals()%%'' method to tell JUnit to do some testing. This particular version of the ''%%Assert.assertEquals()%%'' method is passed three parameters and has the following syntax: ''Assert.assertEquals(//Description//, //ExpectedValue//, //ActualValue//);'' The //Description// is a human-readable ''%%String%%'' that provides information that enables the tester to understand the test in the event that the code fails the test. (Note that the tester will also be provided with the name of the test method so it isn't necessary to include information about the method being tested in the description if the test method is named well.) The //ExpectedValue// contains the correct value (i.e., the value that the tester expects if the method named //MethodName// in //ClassName// is working correctly. The //ActualValue// contains the value that was actually generated by the method named //MethodName// in //ClassName//. In the example above, the ''%%getAtomicNumber()%%'' method in the ''%%Atom%%'' class is being tested. A call to ''%%o.getAtomicNumber()%%'' should return ''%%8%%''. As another example, suppose the ''%%Atom%%'' class contains a ''%%public boolean equals(Atom other)%%'' method, then ''%%AtomTest.java%%'' might implement the following method: /** * Unit tests for the equals(Atom) method */ @Test public void testEquals() { } Note that, even though the ''%%equals()%%'' method in the ''%%Atom%%'' class is passed an ''%%Atom%%'' object and returns a ''%%boolean%%'', the ''%%testEquals()%%'' method in ''%%AtomTest.java%%'' has no parameters and does not return anything. The ''%%equals()%%'' method in the ''%%Atom%%'' class is supposed to compare the calling ''%%Atom%%'' object with the given ''%%Atom%%'' object and return ''%%true%%'' if the two have the same attributes and ''%%false%%'' otherwise. To partially test the ''%%equals()%%'' method, one might implement the ''%%testEquals()%%'' method in the ''%%AtomTest%%'' class as follows: /** * Unit tests for the equals(Atom) method */ @Test public void testEquals() { Atom h, hh, o; h = new Atom("H", 1, 1); hh = new Atom("H", 1, 1); o = new Atom("O", 8, 16); assertEquals("Two H atoms", true, h.equals(hh)); assertEquals("H and O", false, h.equals(o)); assertEquals("O and H", false, o.equals(h)); } The ''%%equals(Atom)%%'' method in the ''%%Atom%%'' class is being tested. A call to ''%%h.equals(o)%%'' should return ''%%false%%'' as should a call to ''%%h.equals(o)%%''. ==== More About the Assert.assertEquals() Method ==== The ''%%Assert.assertEquals()%%'' method can be used to compare the expected and actual values of a wide variety of different types. In the examples above it is used to compare ''%%int%%'' values and ''%%boolean%%'' values. It can also be used to compare ''%%String%%'' objects and other objects, but you have to be careful when doing so. When ''%%assertEquals()%%'' is used to compare primitive types, it uses the ''%%==%%'' operator. When ''%%assertEquals()%%'' is used to compare class types, it uses the ''%%.equals()%%'' method (with special handling for ''%%null%%'' references). When using ''%%Assert.assertEquals()%%'' to compare floating point numbers (e.g., ''%%double%%'' values), one must remember that the ''%%==%%'' operator must be used with care because of the less-than-perfect precision of operations on ''%%double%%'' values. In JUnit, the implication of this is that one should check to see if ''%%double%%'' values are within a tolerance value of each other. Hence, when comparing ''%%double%%'' values one should use the following: ''Assert.assertEquals(//Description//, //ExpectedValue//, //ActualValue//, //tolerance//);'' ==== Testing Methods that Throw Exceptions ==== There are several ways to test for thrown exceptions in JUnit. The easiest (though not the most flexible) is to use the optional ''%%expected%%'' element of the ''%%@Test%%'' annotation. For example, suppose the constructor of the ''%%Atom()%%'' class is required to throw an ''%%IllegalArgumentException%%'' when the numeric parameters are negative. One might test this is follows. @Test(expected = IllegalArgumentException.class) public void testConstructor_IllegalArguments() { Atom o; o = new Atom("O", -8, -16); } Note that ''%%IllegalArgumentException%%'' is an unchecked exception. Hence, this code will compile even though the test method does not specify that it re-throws the exception. If you are testing for a checked exception then the test method must specify that it re-throws the exception. ==== Other Useful Methods in the Assert Class ==== The ''%%Assert%%'' class contains other useful methods including: ''%%assertArrayEquals()%%'' for arrays of different types\\ ''%%assertFalse()%%'' and ''%%assertTrue()%%'' for boolean values\\ ''%%assertNull()%%'' for references\\ ''%%assertSame()%%'' for references\\ Using these methods, it would be better to write the test of the ''%%equals()%%'' method above as follows. /** * Unit tests for the equals(Atom) method */ @Test public void testEquals() { Atom h, hh, o; h = new Atom("H", 1, 1); hh = new Atom("H", 1, 1); o = new Atom("O", 8, 16); assertTrue("Two H atoms", h.equals(hh)); assertFalse("H and O", h.equals(o)); assertFalse("O and H", o.equals(h)); } ==== The @Before Annotation ==== Sometimes several tests need to use the same objects. This can be accomplished using the ''%%@Before%%'' annotation, which instructs JUnit to run the methods with this annotation before each method with the ''%%@Test%%'' annotation. The ''%%@Before%%'' annotation is very useful when an individual test changes the state of an object since, in general, one wants test to be independent. (Note that, in general, you should not assume that the tests themselves will be run in a particular order.) To use the ''%%@Before%%'' annotation you must import ''%%org.junit.Before%%''. ==== Downloading JUnit ==== Information about how to download and install JUnit is available from [[ https://github.com/junit-team/junit/wiki/Download-and-Install | junit.org ]]. ==== Compiling and Running JUnit Tests from a Command Shell ==== === Using JUnit with the BASH Shell === == Configuration == The Java compiler needs to be able to "find" the JUnit classes and the Java intepreter needs to be able to find both the JUnit classes and the Hamcrest classes. For this discussion, we will assume that the JUnit classes are in ''%%junit.jar%%'' and the Hamcrest classes are in ''%%hamcrest-core.jar%%'' (though the actual names will vary). You can either identify the location of these files each time you use the compiler/interpreter or you can set an operating system environment variable named ''%%CLASSPATH%%''. == Using the CLASSPATH Environment Variable == You can set the ''%%CLASSPATH%%'' as follows: export CLASSPATH=.://directory//junit.jar://directory//hamcrest-core.jar:$CLASSPATH where //directory// denotes the name of the directory/folder that contains the files ''%%junit.jar%%'' and ''%%hamcrest-core.jar%%''. For example, assuming that the two ''%%.jar%%'' fiels are in the current working directory, you can set the ''%%CLASSPATH%%'' as follows: export CLASSPATH=.:junit.jar:hamcrest-core.jar:$CLASSPATH After you have set the ''%%CLASSPATH%%'' you can compile your classes (including the test classes) and the usual way, and run a JUnit test as follows: java org.junit.runner.JUnitCore //ClassName//Test where //ClassName// is the name of the class being tested. For example, to run ''%%AtomTest%%'': java org.junit.runner.JUnitCore AtomTest == Not Using the CLASSPATH Environment Variable == If you don't use set the ''%%CLASSPATH%%'' you must tell Java where to find JUnit every time you compile and/or run a test. To compile: ''javac -cp .:junit.jar //ClassName//Test.java'' where //Name// represents tha name of the class being tested (and can contain wildcards like *). To run: ''java -cp .:junit.jar:hamcrest-core.jarorg.junit.runner.JUnitCore //ClassName//Test'' where //Name// represents the name of the class being tested. For example, to compile and run ''%%AtomTest%%'': javac -cp .:junit.jar AtomTest.java java -cp .:junit.jar hamcrest-core.jarorg.junit.runner.JUnitCore AtomTest == Test Output == If all of the tests are successful, an "OK" message will be printed. If not, a long error message will be printed. Buried in the message will be information about where the errors occurred and what the errors were. To find them, look for the descriptions that you included in your calls to ''%%assertEquals()%%''. === Using JUnit with the MS-Windows CMD Shell === == Configuration == The Java compiler needs to be able to "find" the JUnit classes and the Java intepreter needs to be able to find both the JUnit classes and the Hamcrest classes. For this discussion, we will assume that the JUnit classes are in ''%%junit.jar%%'' and the Hamcrest classes are in ''%%hamcrest-core.jar%%'' (though the actual names will vary). You can either identify the location of these files each time you use the compiler/interpreter or you can set an operating system environment variable named ''%%CLASSPATH%%''. == Using the CLASSPATH Environment Variable == You can set the ''%%CLASSPATH%%'' as follows: ''set CLASSPATH=.;//directory//junit.jar;//directory//hamcrest.jar;%CLASSPATH%'' where //directory// denotes the name of the directory/folder that contains the files ''%%junit.jar%%'' and ''%%hamcrest-core.jar%%''. For example, assuming that the two ''%%.jar%%'' files are in the current working directory, you can set the ''%%CLASSPATH%%'' as follows: set CLASSPATH=.;junit.jar;hamcrest-core.jar;%CLASSPATH% After you have set the ''%%CLASSPATH%%'' you can compile your classes (including the test classes) and the usual way, and run a JUnit test as follows: java org.junit.runner.JUnitCore //ClassName//Test where //ClassName// is the name of the class being tested. For example, to run ''%%AtomTest%%'': java org.junit.runner.JUnitCore AtomTest == Not Using the CLASSPATH Environment Variable == If you don't use set the ''%%CLASSPATH%%'' you must tell Java where to find JUnit every time you compile and/or run a test. To compile: ''javac -cp .;junit.jar //ClassName//Test.java'' where //Name// represents tha name of the class being tested (and can contain wildcards like *). To run: java -cp .;junit.jar;hamcrest-core.jarorg.junit.runner.JUnitCore //ClassName//Test where //Name// represents the name of the class being tested. For example, to compile and run ''%%AtomTest%%'': javac -cp .;junit.jar AtomTest.java java -cp .;junit.jar;hamcrest-core.jarorg.junit.runner.JUnitCore AtomTest == Test Output == If all of the tests are successful, an "OK" message will be printed. If not, a long error message will be printed. Buried in the message will be information about where the errors occurred and what the errors were. To find them, look for the descriptions that you included in your calls to ''%%assertEquals()%%''. ==== Integrating JUnit into a IDE ==== * [[ http://help.eclipse.org/luna/index.jsp?topic=%2Forg.eclipse.jdt.doc.user%2FgettingStarted%2Fqs-junit.htm | Eclipse ]] * jGRASP * [[ https://netbeans.org/kb/docs/java/junit-intro.html | NetBeans ]] ==== For More Information ==== More information is available at the [[ http://junit.org/faq.html | JUnit FAQ ]] and the [[ https://github.com/junit-team/junit/wiki | JUnit wiki ]].