Академический Документы
Профессиональный Документы
Культура Документы
Unit Testing
A unit test is a piece of code written by a developer that executes a specific functionality in the code under test. Unit tests ensure that code is working as intended and validate that this is still the case after code changes.
What is JUnit
JUnit is a member of the xUnit testing framework family and now the de facto standard testing framework for Java development. JUnit, originally created by Kent Beck and Erich Gamma, is an API that enables developers to easily create Java test cases. It provides a comprehensive assertion facility to verify expected versus actual results. For those interested in design patterns, JUnit is also a great case study because it is very pattern-dense. Figure 4.1 shows the UML model. The abstract TestCase class is of most interest to us. JUnit is linked as a JAR at compile-time; the framework resides under packages junit.framework for JUnit 3.8 and earlier and under org.junit for JUnit 4 and later. JUnit features include: Assertions for testing expected results Test fixtures for sharing common test data Test suites for easily organizing and running tests Graphical and textual test runners
Unit Testing with JUnit 4.x JUnit 4.x is a test framework which uses annotations to identify methods that are test methods. JUnit assumes that all test methods can be executed in an arbitrary order. Therefore tests should not depend on other tests. To write a test with JUnit
Annotate a method with @org.junit.Test Use a method provided by JUnit to check the expected result of the code execution versus the actual result
Using JUnit in Eclipse:Create a new project de.vogella.junit.first. We want to create the unit tests in a separate folder. The creation of a separate folder for tests is not mandatory. But it is a good practice to keep the code separated from the regular code. You might even create a separate project for the test classes, but we skip this step to make this example simpler. Create a new source folder test via right-clicking on your project, select "Properties" and choose the "Java Build Path". Select the "Source" tab.
Press "Add folder" then press "Create new folder". Create the folder "test".
Alternatively you can add a new source folder by right-clicking on a project and selecting New Source Folder. Create a Java class In the "src" folder, create the de.vogella.junit.first package and the following class.
package de.vogella.junit.first;
Create a JUnit test Right click on your new class in the Package Explorer and select New JUnit Test Case. Select "New JUnit 4 test" and set the source folder to "test", so that your test class gets created in this folder.
Press "Next" and select the methods which you want to test.
If the JUnit library in not part of your classpath, Eclipse will prompt you to do so.
import org.junit.Test;
@Test
public void testMultiply() { MyClass tester = new MyClass(); assertEquals("Result", 50, tester.multiply(10, 5)); } }
JUnit Features
The second signature for each datatype allows a message to be inserted into the results, which makes clear identification of which assertion failed. i. The following assertion states that the test expected.equals(actual) returns true, or both objects are null. The equality test for a double also lets you specify a range, to cope with floating point errors better.There are overloaded versions of this method for all Javas primitive types. assertEquals(expected, actual) assertEquals(String message, expected, actual)
ii.
The following method asserts that an object reference equals null. assertNull(Object object), assertNull(String message, Object object) This asserts that an object reference is not null. assertNotNull(Object object), assertNotNull(String message, Object)
iii.
iv.
Asserts that the two objects are the same. This is a stricter condition than simple equality, as it compares the object identities using expected == actual. assertSame(Object expected, Object actual), assertSame(String message, Object expected, Object actual)
v.
This assertion fails if the condition is false, printing a message string if supplied. The assertTrue methods were previously named simply assert, but JDK 1.4 introduces a new assert keyword. You may encounter source using the older method names and receive deprecation warnings during compilation. assertTrue(boolean condition), assertTrue(String message, boolean condition)
vi.
The following assertion forces a failure. This is useful to close off paths through the code that should not be reached. JUnit uses the term failure for a test that fails expectedly, meaning that an assertion was not valid or a fail was encountered. The term error refers to an unexpected error (such as a NullPointerException). We will use the term failure typically to represent both conditions as they both carry the same show-stopping weight when encountered during a build. fail(), fail(String message)
public static void suite(){ TestSuite suite = new TestSuite(); suite.addTest(new BookTest("testEquals")); suite.addTest(new BookTest("testBookAdd")); return suite; }
Since JUnit 2.0 there is an even simpler way to create a test suite, which holds all testXXX() methods. You only pass the class with the tests to a TestSuite and it extracts the test methods automatically. Note: If you use this way to create a TestSuite all test methods will be added. If you do not want all test methods in the TestSuite use the normal way to create it. Example:
What's new in JUnit 4 Thanks to Java 5 annotations, JUnit 4 is more lightweight and flexible than ever. It has dropped its strict naming conventions and inheritance hierarchies in favor of some exciting new functionality. Here's a quick list of what's new in JUnit 4: Parametric tests Exception tests Timeout tests
Flexible fixtures An easy way to ignore tests A new way to logically group tests
Out with the old Prior to the addition of Java 5 annotations in JUnit 4, the framework had established two conventions that were essential to its ability to function. The first was that JUnit implicitly required that any method written to function as a logical test begin with the word test. Any method beginning with that word, such as testUserCreate, would be executed according to a well-defined test process that guaranteed the execution of a fixture both before and after the test method. Second, for JUnit to recognize a class object containing tests, the class itself was required to extend from JUnit's TestCase (or some derivation thereof). A test that violated either of these two conventions would not run. Listing 1 shows a JUnit test written prior to JUnit 4. import java.util.regex.Matcher; import java.util.regex.Pattern; import junit.framework.TestCase; public class RegularExpressionTest extends TestCase { private String zipRegEx = "^\\d{5}([\\-]\\d{4})?$"; private Pattern pattern; protected void setUp() throws Exception { this.pattern = Pattern.compile(this.zipRegEx); } public void testZipCode() throws Exception{ Matcher mtcher = this.pattern.matcher("22101"); boolean isValid = mtcher.matches(); assertTrue("Pattern did not validate zip code", isValid); } }
Listing 2 shows the same test seen in Listing 1 but redefined using annotations: Listing 2. Testing with annotations import java.util.regex.Matcher; import java.util.regex.Pattern; import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.assertTrue; public class RegularExpressionTest { private static String zipRegEx = "^\\d{5}([\\-]\\d{4})?$"; private static Pattern pattern; @BeforeClass public static void setUpBeforeClass() throws Exception { pattern = Pattern.compile(zipRegEx); } @Test public void verifyGoodZipCode() throws Exception{ Matcher mtcher = this.pattern.matcher("22101"); boolean isValid = mtcher.matches(); assertTrue("Pattern did not validate zip code", isValid); } } The test in Listing 2 may not be any easier to code, but it certainly is easier to comprehend.
1.Testing with annotations Java 5 annotations make JUnit 4 a notably different framework from previous versions. In this section, I familiarize you with using annotations in key areas such as test declaration and exception testing, as well as for timeouts and ignoring unwanted or unusable tests.
Test declaration
Declaring a test in JUnit 4 is a matter of decorating a test method with the @Test annotation. Note that you need not extend from any specialized class, as Listing 3 shows:
Listing 3. Test declaration in JUnit 4 import java.util.regex.Matcher; import java.util.regex.Pattern; import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.assertFalse; public class RegularExpressionTest { private static String zipRegEx = "^\\d{5}([\\-]\\d{4})?$"; private static Pattern pattern; @BeforeClass public static void setUpBeforeClass() throws Exception { pattern = Pattern.compile(zipRegEx); } @Test public void verifyZipCodeNoMatch() throws Exception{ Matcher mtcher = this.pattern.matcher("2211"); boolean notValid = mtcher.matches(); assertFalse("Pattern did validate zip code", notValid); } }
A note about static imports Java 5's static import feature is used to import the Assert class's assertFalse() method in Listing 3. This is because test classes do not extend from TestCase as they did in previous versions of JUnit. Other Examples:1. @Before and @After Use @Before and @After annotations for setup and tearDown methods respectively. They run before and after every test case. @Before public void runBeforeEveryTest() { simpleMath = new SimpleMath(); }
@After public void runAfterEveryTest() { simpleMath = null; } 2. @BeforeClass and @AfterClass Use @BeforeClass and @AfterClass annotations for class wide setup and tearDown respectively. Think them as one time setup and tearDown. They run for one time before and after all test cases. @BeforeClass public static void runBeforeClass() { // run for one time before all test cases } @AfterClass public static void runAfterClass() { // run for one time after all test cases } 2.Testing for exceptions As with previous versions of JUnit, it's usually a good idea to specify that your test throws Exception. The only time you want to ignore this rule is if you're trying to test for a particular exception. If a test throws an exception, the framework reports a failure.If you'd actually like to test for a particular exception, JUnit 4's @Test annotation supports an expected parameter, which is intended to represent the exception type the test should throw upon execution. A simple comparison demonstrates what a difference the new parameter makes. Exception testing in JUnit 3.8 The JUnit 3.8 test in Listing 4, aptly named testZipCodeGroupException(), verifies that attempting to obtain the third group of the regular expression I've declared will result in an IndexOutOfBoundsException: Listing 4. Testing for an exception in JUnit 3.8 import java.util.regex.Matcher; import java.util.regex.Pattern; import junit.framework.TestCase; public class RegularExpressionTest extends TestCase { private String zipRegEx = "^\\d{5}([\\-]\\d{4})?$";
private Pattern pattern; protected void setUp() throws Exception { this.pattern = Pattern.compile(this.zipRegEx); } public void testZipCodeGroupException() throws Exception{ Matcher mtcher = this.pattern.matcher("22101-5051"); boolean isValid = mtcher.matches(); try{ mtcher.group(2); fail("No exception was thrown"); }catch(IndexOutOfBoundsException e){ } } } This older version of JUnit requires me to do quite a bit of coding for such a simple test -- namely writing a try/catch and failing the test if the exception isn't caught. Exception testing in JUnit 4 The exception test in Listing 5 is no different from the one in Listing 4, except that it uses the new expected parameter. (Note that I was able to retrofit the test from Listing 4 by passing in the IndexOutOfBoundsException exception to the @Test annotation.) Listing 5. Exception testing with the 'expected' parameter import java.util.regex.Matcher; import java.util.regex.Pattern; import org.junit.BeforeClass; import org.junit.Test; public class RegularExpressionJUnit4Test { private static String zipRegEx = "^\\d{5}([\\-]\\d{4})?$"; private static Pattern pattern; @BeforeClass public static void setUpBeforeClass() throws Exception { pattern = Pattern.compile(zipRegEx); } @Test(expected=IndexOutOfBoundsException.class) public void verifyZipCodeGroupException() throws Exception{ Matcher mtcher = this.pattern.matcher("22101-5051"); boolean isValid = mtcher.matches();
mtcher.group(2); } }
3.Testing with timeouts In JUnit 4, a test case can take a timeout value as a parameter. As you can see in Listing 6, the timeout value represents the maximum amount of time the test can take to run: if the time is exceeded, the test fails. Listing 6. Testing with a timeout value @Test(timeout=1) public void verifyFastZipCodeMatch() throws Exception{ Pattern pattern = Pattern.compile("^\\d{5}([\\-]\\d{4})?$"); Matcher mtcher = pattern.matcher("22011"); boolean isValid = mtcher.matches(); assertTrue("Pattern did not validate zip code", isValid); } Testing with timeouts is easy: Simply decorate a method with @Test followed by a timeout value and you've got yourself an automated timeout test! 4.Ignoring tests Prior to JUnit 4, ignoring broken or incomplete tests was a bit of a pain. If you wanted the framework to ignore a particular test, you had to alter its name so as to not follow the test nomenclature. For instance, I often found myself placing a "_" in front of a test method to indicate that the test wasn't made to be run at the current moment. JUnit 4 has introduced an annotation aptly dubbed @Ignore, which forces the framework to ignore a particular test method. You can also pass in a message documenting your decision for unsuspecting developers who happen upon the ignored test. The @Ignore annotation Listing 7 shows how easy it is to ignore a test whose regular expression isn't yet working: Listing 7. Ignore this test @Ignore("this regular expression isn't working yet") @Test public void verifyZipCodeMatch() throws Exception{
Pattern pattern = Pattern.compile("^\\d{5}([\\-]\\d{4})"); Matcher mtcher = pattern.matcher("22011"); boolean isValid = mtcher.matches(); assertTrue("Pattern did not validate zip code", isValid); } Attempting to run this test in Eclipse (for example) will report an ignored test, as shown in Figure 1: Figure 1. How an ignored test shows up in Eclipse