...
The JUnit framework helps streamlining unit tests, and is supported by a number of development environments (IDEs). With it, writing a unit test can be as easy as creating a method that compares the results of running the tested method against expected values. For instance, the below would be a reasonable test method for the `java.lang.String.substring(int, int)` method:
Code Block |
---|
public void testSubstring() { String testString = "teststring"; assertEquals("Legal substring should be allowed", "str", testString.substring(4, 7)); assertEquals("Substring from start should be possible","test", testString.substring(0, 4)); assertEquals("Substring to end should be possible", "ring", testString.substring(6, testString.length())); assertEquals("Substring of the empty string should be possible", "", "".substring(0, 0)); try { testString.substring(-1, 5); fail("Substring with negative start should be impossible"); } catch (IndexOutOfBoundsException e) { assertTrue("Error message should contain illegal index value", e.getMessage().contains("-1")); } try { testString.substring(7, 5); fail("Substring with end before start should be impossible"); } catch (IndexOutOfBoundsException e) { assertTrue("Error message should contain illegal index difference", e.getMessage().contains("-2")); } try { testString.substring(1, 100); fail("Substring with end too far out should be impossible"); } catch (IndexOutOfBoundsException e) { assertTrue("Error message should contain illegal index value", e.getMessage().contains("100")); } } |
The standard method name testTestedMethodName is used by JUnit to find tests to run, and by IntelliJ/Eclipse to allow navigation to and direct execution of individual tests. This test first checks standard (successful) usage, on examples of increasing complexity, then goes on to check the error scenarios, making sure that the right exception with the right message is thrown. The ~+`assertEquals`+~, ~+`assertTrue`+~ and ~+`fail`+~ methods are provided by the !TestCase class in JUnit, and take care of formatting an error message in a readable manner. As an example, here is the (first part of the) output of running the testing with the third assertEquals only substringing out to ~+`testString.length() - 1`+~:
Code Block |
---|
junit.framework.ComparisonFailure: Substring to end should be possible Expected:ring Actual :rin at dk.netarkivet.tools.UploadTester.testSubstring(UploadTester.java:44) ... |
...
Code Block |
---|
public class DomainExtractor { /** This method extracts domain names from URLs. * * @param URL A string containing a URL, e.g. http://netarkivet.dk/index.html * @returns A string that contains the domain name found in the URL, e.g. netarkivet.dk */ public String extract(String URL) { return null; } } |
Next, we create a test class for this method (using JUnit) and implement tests for the functionality. When implementing tests, we should be in the most evil mindtest possible, seeking any way we can think of to make the method do something other than it claims it does.
Code Block |
---|
public class DomainExtractorTester extends TestCase { public void testExtract() { DomainNameExtractor dne = new DomainNameExtractor(); assertEquals("Must extract simple domain", "netarkivet.dk", dne.extract("http://netarkivet.dk/index.html")); assertEquals("Must extract long domains", "news.bbc.co.uk", dne.extract("http://news.bbc.co.uk/frontpage")); assertEquals("Must not depend on trailing slash", "test.com", dne.extract("http://test.com")); assertEquals("Must keep www part", "www.test.com", dne.extract("http://www.test.com")); } } |
The ~+`assertEquals`+~ method inherited from test case takes three arguments: An explanatory message that tells us what we're testing for, the value that we expect to get from the test, and the actual value that the test gave us (in this case the return value of a method call).
At this point, we may realize that the method API does not specify what happens if we give it something that is not a URL, like "www.test.com". Does it throw an exception? Does it return null? Does it return some arbitrary part of the argument? Specifying error behaviour is as much a part of specifying the methods behaviour as saying what it does on the "good" cases. Also, what if the URL is not an HTTP URL, like "[[mailto:owner@test.com|mailto:owner@test.com]]"? Possibly we were really just thinking of HTTP URLs, but then we need to specify that, too. These realizations should go into the javadoc at once, and the test should be expanded to check them (not shown here).
...
'''Don't try to prove a negative.''' It's tempting to test that a method call don't change things it's not supposed to, but you can't really do that. Any method can change all manner of things, if it really wants to, and you cannot check them all. Only if the !JavaDoc or other design contract explicitly states that some parts are unchanged should that be checked.
How How do you make a unit test for X?
...