转自https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaUnitTesting.html
yet another insignificant programming notes... | HOME
TABLE OF CONTENTS (SHOW)
Java Unit Testing - & TestNG
1. Introduction to Unit Testing Framework
The various type of software testings include:
- Unit Test: Test individual component/class in isolation.
- Integration Test: Test a group of associated components/classes.
- Acceptance Test (or Functional Test): operate on a fully integrated system, testing against the user interface (e.g., HTML for browser or XML/JSON for web services).
- Regression Test: Tests to ensure the a change (such as enhancement, patches or configuration change) does not break the system or introduce new faults.
Unit Testing is concerned about testing individual programs/classes to ascertain that each program/class runs as per specification. Prior to the arrival of "unit testing framework", programmers tends to write test expressions which print to the console or a trace file (the amount of output is sometimes controlled by a trace-level or debug-level flag). This approach is not satisfactory because it requires human judgment to analyze the results produced. Too many print statements cause the dreaded Scroll Blindness.
JDK 1.4 provides an assertion feature (read Assertion), which enables you to test (or assert) your assumptions about your program logic (such as pre-conditions, post-conditions, and invariants). Nonetheless, assertion is primitive compared with the unit testing framework.
With a proper Unit Testing framework, you can automate the entire unit testing process. Your job becomes designing proper test cases to excite the program. Furthermore, the unit testing process can be integrated into the build process. In this case, the build process not only checks for syntax errors, but semantic errors as well.
1.1 Extreme Programming
Extreme programming (@ www.xprogramming.com) advocates "write test first, before writing codes".
1.2 xUnit Unit Testing Framework
xUnit is the family name given to a group of unit testing frameworks that share the same architecture, such as JUnit (for Java), NUnit (for .NET), CppUnit (for C++), PHPUnit (for PHP) and many others.
The xUnit architecture has these common components:
- Test case / Test suites:
- Test fixture:
- Test runner:
- Test result formatter:
- Assertions:
2. JUnit
JUnit (@ http://junit.org/) is an open-source Java Unit Testing Framework designed by Kent Beck, Erich Gamma. It is the de facto standard for Java Unit Testing. JUnit is not included in JDK, but included in most of the IDEs such as Eclipse and NetBeans.
2.1 Installing and Using JUnit
Installing JUnit: Goto http://junit.org/ ⇒ "Download and Install Guide" ⇒ Download the "junit.jar
" and "hamcrest-core.jar
". You could download the API documentation as well as the source code.
Using JUnit: To use the JUnit, include JUnit jar-files "junit-4.##.jar
" and "hamcrest-core-1.##.jar
" in your CLASSPATH
.
2.2 Using JUnit under Eclipse
Include JUnit Library in your Java Project: Create a new Java project ⇒ right-click on the project ⇒ Properties ⇒ Java Build Path ⇒ "Libraries" tab ⇒ Add Library ⇒ JUnit ⇒ In "JUnit library version", choose "JUnit 4" ⇒ In "current location" use the eclipse's JUnit or your own download. [Alternatively, when you create a new test case or test suite (as describe below), Eclipse will prompt you to include the JUnit library.]
Create Test case (or Test Suite): To create a new JUnit test case (or test suite, which contains many test cases) ⇒ File ⇒ Others ⇒ Java ⇒ JUnit ⇒ JUnit test case (or JUnit test suite).
Run Test case (or Test Suite): To run a test case (or test suite), right-click the file ⇒ Run As ⇒ JUnit Test.
3. JUnit 4
There are two versions of JUnit, version 3 and version 4, which are radically different. JUnit 4 uses the annotation feature (since JDK 1.5) to streamline the process and drop the strict naming conventions of test methods.
3.1 Getting Started with an Example
Suppose that we wish to carry out unit testing on the following Java program, which uses static
methods to perform arithmetic operations on two integers. Take note that divide throws anIllegalArgumentException
for divisor of zero.
1 |
/** |
First Test Case
Let's do it under Eclipse.
- Create a new Eclipse Java project called "
JUnitTest
". - Create a new class called "
Calculator
" under "src
" folder, with the above program code. - Create a new folder called "
test
" for storing test scripts ⇒ Right-click on the project ⇒ New ⇒ Folder ⇒ In folder name, enter "test
". Make "test
" a source folder by right-click on "test
" ⇒ Build Path ⇒ Use as source folder. - Create the first test case called "
AddSubTest
" ⇒ Right-click on folder "test
" ⇒ New ⇒ Other ⇒ Java ⇒ JUnit ⇒ JUnit Test Case ⇒ New JUnit 4 test ⇒ In Name, enter "AddSubTest
". Enter the following codes:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30import static org.junit.Assert.*;
import org.junit.Test; public class AddSubTest {
@Test
public void testAddPass() {
// assertEquals(String message, long expected, long actual)
assertEquals("error in add()", 3, Calculator.add(1, 2));
assertEquals("error in add()", -3, Calculator.add(-1, -2));
assertEquals("error in add()", 9, Calculator.add(9, 0));
} @Test
public void testAddFail() {
// assertNotEquals(String message, long expected, long actual)
assertNotEquals("error in add()", 0, Calculator.add(1, 2));
} @Test
public void testSubPass() {
assertEquals("error in sub()", 1, Calculator.sub(2, 1));
assertEquals("error in sub()", -1, Calculator.sub(-2, -1));
assertEquals("error in sub()", 0, Calculator.sub(2, 2));
} @Test
public void testSubFail() {
assertNotEquals("error in sub()", 0, Calculator.sub(2, 1));
}
} - To run the test case, right-click on the file ⇒ Run as ⇒ JUnit Test. The test result is shown in the JUnit panel. 4 tests were run and all succeeded. Study the test results.
- Try modify one of the test to force a test failure and observe the test result, e.g.,
@Test
public void testAddPass() {
assertEquals("error in add()", 0, Calculator.add(1, 2));
.....
}
Explanation
- A test case contains a number of tests, marked with annotation of "
@org.junit.Test
". Each of the test is run independently from the other tests. - Inside the test method, we can use static methods
assertXxx()
(in classorg.unit.Assert
) to assert the expected and actual test outcomes, such as:public static void assertEquals([String message,] long expected, long actual)
// int and long: expected == actual
public static void assertEquals([String message,] double expected, double actual, double epsilon)
// double: expect == actual within tolerance of epsilon
public static void assertEquals([String message,] Object expected, Object actual)
// Object: expected.equals(actual)
public static void assertNotEquals(.....) public static void assertSame([String message,] Object expected, Object actual)
// Same Object: expected == actual
public static void assertNotSame(.....) public static void assertTrue([String message,] boolean condition)
// boolean: condition == true
public static void assertFalse([String message,] boolean condition)
// boolean: condition == false public static void assertNull([String message,] Object object)
// object == null
public static void assertNotNull(......) public static void assertArrayEquals([String message,], int[] expecteds, int[] actuals)
public static void assertArrayEquals([String message,], byte[] expecteds, byte[] actuals)
public static void assertArrayEquals([String message,], char[] expecteds, char[] actuals)
public static void assertArrayEquals([String message,], long[] expecteds, long[] actuals)
public static void assertArrayEquals([String message,], byte[] expecteds, byte[] actuals)
public static void assertArrayEquals([String message,], short[] expecteds, short[] actuals)
public static void assertArrayEquals([String message,], Object[] expecteds, Object[] actuals) public static <T> void assertThat([String message,], T actual, org.hamcrest.Matcher<T> matcher)
Run Test Standalone via Test Runner
To run your test standalone (outside Eclipse), you could write a Test Runner as follows:
1 |
import org.junit.runner.JUnitCore; |
You can include more than one test cases using runClasses(test1.class, test2.class, ...)
.
Run Test in Command-line
JUnit also provides a console version of test-runner called org.junit.runner.JUnitCore for you to run the tests in command-line, with the following syntax:
// Need to include JUnit's jar-file in CLASSPATH.
$ java org.junit.runner.JUnitCore TestClass1 [TestClass2 ...]
- Copy all your classes into one folder (for simplicity).
- Set the
CLASSPATH
to include the JUnit jar-files:// Unix/Linux/Ubuntu/Mac (for this bash session only)
$ export CLASSPATH=.:$CLASSPATH:/path/to/junit/junit-4.11.jar:/path/to/junit/hamcrest-core-1.3.jar // Windows (for this CMD session only)
> set CLASSPATH=.;%CLASSPATH%;x:\path\to\junit\junit-4.11.jar;x:\path\to\junit\hamcrest-core-1.3.jar - Compile all the source files:
$ cd /path/to/source-files
$ javac Calculator.java AddSubTest.java - Run the test:
$ java org.junit.runner.JUnitCore AddSubTest
JUnit version 4.11
....
Time: 0.006
OK (4 tests)
Second Test Case
Let's write another test case to test the divide methods, which throw an Exception for divisor of zero. Furthermore, the method divReal()
returns a double which cannot be compared with absolute precision.
1 |
import static org.junit.Assert.*; |
Run the test and observe the test result. Change testDivRealPass()
's expected value from 0.333333 to 0.333330 and check the test result.
Explanation
- To test for exception, use annotation
@Test
with attributeexpected = ExceptionClassName
. - To compare doubles, use a tolerance (epsilon) as shown.
First Test Suite
A test suite comprises many test cases.
To create a test suite under Eclipse ⇒ right-click on the test
folder ⇒ New ⇒ other ⇒ Java ⇒ JUnit ⇒ JUnit Test Suite ⇒ In Name, enter "AllTests
" ⇒ Select test cases to be included - AddSubTest and DivTest.
The following test script will be created:
1 |
import org.junit.runner.RunWith; |
Take note that the test suite class is marked by annotation @RunWith
and @SuiteClasses
with an empty class body.
To run the test suite ⇒ right-click on the file ⇒ Run as ⇒ JUnit Test. Observe the test results produced.
You can also run the test suite via Test Runner org.junit.runner.JUnitCore
, just like running test cases (as described earlier).
3.2 Testing Java Classes By Example
Instead of testing static methods in a Java class, let's carry out unit test on a proper self-contained and encapsulated Java class with its own private variables and public operations.
Suppose that we have a class called MyNumber
that represents a number, and capable of performing arithmetic operations.
Again, we shall work under Eclipse.
- Create a Java project called "
JUnitTest2
" - Create a new Java class called "
MyNumber
", as follow:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37/**
* The class MyNumber represent a number, and capable
* of performing arithmetic operations.
*/
public class MyNumber {
int number; // Constructor
public MyNumber() {
this.number = 0;
} public MyNumber(int number) {
this.number = number;
} // Getter and setter
public int getNumber() {
return number;
} public void setNumber(int number) {
this.number = number;
} // Public methods
public MyNumber add(MyNumber rhs) {
this.number += rhs.number;
return this;
} public MyNumber div(MyNumber rhs) {
if (rhs.number == 0) throw new IllegalArgumentException("Cannot divide by 0!");
this.number /= rhs.number;
return this;
}
} - Create a new source folder called "
test
" for storing test scripts. Make it a source folder by right-click ⇒ Build Path ⇒ Use as source folder. - Create the first test case called MyNumberTest (under "
test
" folder), as follows:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49import static org.junit.Assert.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test; public class MyNumberTest {
private MyNumber number1, number2; // Test fixtures @Before
public void setUp() throws Exception {
System.out.println("Run @Before"); // for illustration
number1 = new MyNumber(11);
number2 = new MyNumber(22);
} @After
public void tearDown() throws Exception {
System.out.println("Run @After"); // for illustration
} @Test
public void testGetterSetter() {
System.out.println("Run @Test testGetterSetter"); // for illustration
int value = 33;
number1.setNumber(value);
assertEquals("error in getter/setter", value, number1.getNumber());
} @Test
public void testAdd() {
System.out.println("Run @Test testAdd"); // for illustration
assertEquals("error in add()", 33, number1.add(number2).getNumber());
assertEquals("error in add()", 55, number1.add(number2).getNumber());
} @Test
public void testDiv() {
System.out.println("Run @Test testDiv"); // for illustration
assertEquals("error in div()", 2, number2.div(number1).getNumber());
assertEquals("error in div()", 0, number2.div(number1).getNumber());
} @Test(expected = IllegalArgumentException.class)
public void testDivByZero() {
System.out.println("Run @Test testDivByZero"); // for illustration
number2.setNumber(0);
number1.div(number2);
}
} - Run the test and observe the result. Modify some lines to make the test fails and observe the result.
The output, used for illustrating the sequence of operations, is as follows:
Run @Before
Run @Test testDivByZero
Run @After
Run @Before
Run @Test testAdd
Run @After
Run @Before
Run @Test testDiv
Run @After
Run @Before
Run @Test testGetterSetter
Run @After
Test Fixtures, @Before and @After
A test fixtures is a fixed state of a set of objects used as a baseline for running tests. The purpose of a test fixture is to ensure that there is a well known and fixed environment in which tests are run so that results are repeatable.
In JUnit 4, fixtures are setup via the @Before
and @After
annotations.
- The
@Before
annotated method (known assetup()
) will be run before EACH test method (annotated with@Test
) to set up the fixtures. - The
@After
annotation method (known as tearDown()) will be run after EACH test.
We typically declare text fixtures as private instance variables; initialize via @Before
annotated method; and clean-up via @After
annotation method. Each test method runs on its own set of text fixtures with the same initial states. This ensures isolation between the test methods.
@BeforeClass and @AfterClass
Beside the @Before
and @After
, there is also @BeforeClass
and @AfterClass
.
- The
@BeforeClass
annotated method will be run once before any test, which can be used to perform one-time initialization tasks such as setting up database connection. - The
@AfterClass
annotated method will be run once after all tests, which can be used to perform housekeeping tasks such as closing database connection.
3.3 JUnit 4's Annotations
JUnit 4 defines the following six annotations in package org.junit
.
Annotation | Description |
---|---|
@Test |
The annotated method is to be run as a test method. |
@Before |
The annotated method is to be run before EACH of the test method. |
@After |
The annotated method is to be run after EACH of the test method. |
@BeforeClass |
The annotated method is to be run ONCE before any of the test method. |
@AfterClass |
The annotated method is to be run ONCE after all the test methods. |
@Ignore |
Ignore (don't run) the test method. This is a convenient way to mark out a test method (e.g. after some code changes that fail this test.) |
@Rule |
[TODO] |
Example of @Ignore
@Ignore("Under Construction")
@Test
public void someTest() {
......
}
3.4 JUnit - Exceptions Test
To test if the code throws a desired exception, use annotation @Test(expected = exception.class)
, as illustrated in the previous example.
3.5 JUnit - Timing Test
To handle or test timeout, use annotation @Test(timeout = msec)
. For example,
1 |
import org.junit.Test; public class TimeoutTest { |
java.lang.Exception: test timed out after 1000 milliseconds
......
3.6 JUnit - Parameterized Test
JUnit 4 introduces parameterized test which allows you to run the same test over and over again using different values. To use parameterized test:
- Annotate the test class with
@RunWith(Parameterized.class)
. - Create a public static method annotated with
@Parameters
that returns a list (Iterable<Object>
) as test data set. - Create a public constructor that takes its input from the
@Parameters
method to setup the test fixtures defined as instance variables. The constructor will be run before EACH test. - Create your tests case(s) using the instance variables as the source of the test data.
For example,
1 |
import static org.junit.Assert.*; |
Run @Parameters
Run constructor
-- number1=1 number2=2 result=3
Run @Before
-- number1=1 number2=2
Run @Test
Run constructor
-- number1=-1 number2=-2 result=-3
Run @Before
-- number1=-1 number2=-2
Run @Test
Run constructor
-- number1=3 number2=-3 result=0
Run @Before
-- number1=3 number2=-3
Run @Test
The output trace suggests that @Parameters
method is run once. For EACH test, the constructor is run first, followed by @Before
, @Test
and @After
methods.
3.7 Another Example
import org.junit.*;
import java.util.ArrayList;
import org.junit.runner.Result; public class ArrayListTest {
private ArrayList<String> lst; // Test fixtures // Initialize test fixtures before each test method
@Before
public void init() throws Exception {
lst = new ArrayList<String>();
lst.add("alpha"); // at index 0
lst.add("beta"); // at index 1
} // Test method to test the insert operation
@Test
public void insertTest() {
// assertEquals(String message, long expected, long actual)
Assert.assertEquals("wrong size", 2, lst.size());
lst.add(1, "charlie");
Assert.assertEquals("wrong size", 3, lst.size());
// assertEquals(String message, Object expected, Object actual)
// Use String.equals() for comparison
Assert.assertEquals("wrong element", "alpha", lst.get(0));
Assert.assertEquals("wrong element", "charlie", lst.get(1));
Assert.assertEquals("wrong element", "beta", lst.get(2));
} // Test method to test the replace operation
@Test
public void replaceTest() {
Assert.assertEquals("wrong size", 2, lst.size());
lst.set(1, "charlie");
Assert.assertEquals("wrong size", 2, lst.size());
Assert.assertEquals("wrong element", "alpha", lst.get(0));
Assert.assertEquals("wrong element", "charlie", lst.get(1));
} public static void main(String[] args) {
Result r = org.junit.runner.JUnitCore.runClasses(ArrayListTest.class);
System.out.println(r.wasSuccessful());
}
}
To run the test, you can either include a main()
method as above, or use the command-line.
3.8 JUnit Package org.junit
The core package for JUnit 4 is org.unit
, which is simple and elegantly designed.
-
Assert
class: containsstatic
methodsassertEquals()
,assertTrue()
,assertFalse()
,assertNull()
,assertNotNull()
,assertSame()
,assertNotSame()
,assertThat()
,assertArrayEquals()
. -
Assume
class: containsstatic
methodsassumeTrue()
,assumeNotNull()
,assumeThat()
,assumeNoException()
. -
@Test
: mark the method as a test method. -
@Test(expected=IOException.class)
: The test is expected to trigger this exception. -
@Test(timeout=1000)
: Treat the test as fail, if it exceeds the specified milliseconds. -
@Before
and@After
: mark the method as to be run before and after EACH test method, for initializing and cleaning-up test fixtures. -
@BeforeClass
and@AfterClass
: mark the method as to be run before and after ONCE for the class. -
@Ignore
: ignore this test method (annotated with@Test
). -
@Rule
: [TODO]
4. JUnit 3.8 (deprecated?)
JUnit 3.8, which uses strict naming convention to denote various entities, is probably deprecated. I suggest that you move to JUnit 4, which is more intuitive by using annotation.
4.1 Let's begin with an Example
Below is a Java program to be tested. Note that there is a logical error in the program.
1 |
public class Grade { |
The unit-test program (using JUnit framework) is as follows. Black-box test cases are set up to test typical values as well as boundary values.
1 |
import junit.framework.Test; |
Compile and execute the program (with JUnit JAR file included in the CLASSPATH
) as follows. Note that one of the unit-test cases catches the logical error.
> set CLASSPATH=.;%CLASSPATH%;c:\junit\junit-3.8.2.jar
> javac GradeUnitTest.java
> java GradeUnitTest
..F
Time: 0.006
There was 1 failure:
1) testBoundaries(GradeUnitTest)junit.framework.AssertionFailedError: wrong grade expected:<C> but was:<F>
at GradeUnitTest.testBoundaries(GradeUnitTest.java:23)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at GradeUnitTest.main(GradeUnitTest.java:34)
FAILURES!!!
Tests run: 2, Failures: 1, Errors: 0
4.2 JUnit Terminology
- Class
TestCase
: A class that contains test methods should derive from this superclass. EachTestCase
can include many test methods. - Test Methods: A test methods must be named
testXxx()
. This is because JUnit uses the reflection mechanism to find and run these methods. Inside a test method, you can use a variation of theassert()
method (e.g.,assertTrue()
,assertFalse()
,assertEquals()
) to compare the expected and actual results. - Test Fixture: The set of objects that a test method operates on. You declare these objects as a private variable, and initialize them by overriding the
setUp()
or via the constructor. You can perform clean-up operations by overridingtearDown()
. Each test method runs on its ownTestCase
instance with its own set of text fixtures. This ensures isolation between the test methods. - Class
TestSuite
: You can combine manyTestCases
(e.g., written by different person) into a test suite, and run them at once. - Class
TestRunner
: for running theTestCase
orTestSuite
.
4.3 Writing Tests
Step 1: Extend a subclass of junit.framework.TestCase
:
import junit.framework.*;
public class JUnit38DemoArrayList extends TestCase {
public JUnit38DemoArrayList(String name) { super(name); } // constructor
}
Step 2: If two or more test methods use a common set of test objects (called test fixtures), declare the test fixtures as instance variables. For example, suppose we are testing the classArrayList
.
private ArrayList<String> lst; // declare test fixture instance.
Step 3: Initialize the text fixture. You can override setUp()
or use the constructor. Each test method runs on its own TestCase
instance. This provides isolation between test methods. Each test method invoke the constructor to construct an instance of the TestCase
, followed by setUp()
, run the steps coded inside the test method, and the tearDown()
before exiting. The test methods may run concurrently. For example, let's initialize our test fixture ArrayList
with two String
elements.
// Initialize the test fixture used by all the test methods
protected void setUp() throws Exception {
lst = new ArrayList<String>();
lst.add("alpha"); // at index 0
lst.add("beta"); // at index 1
}
protected void tearDown() throws Exception { super.tearDown(); } // for clean-up operation
Step 4: Write the test methods for this TestCase
. All the test methods must be named testXxx()
, as JUnit uses reflection to find these test methods. For example,
// test method to test the insert operation
public void testInsert() {
assertEquals("wrong size", 2, lst.size()); // error message, expected, actual
lst.add(1, "charlie");
assertEquals("wrong size", 3, lst.size());
assertEquals("wrong element", "alpha", lst.get(0));
assertEquals("wrong element", "charlie", lst.get(1));
assertEquals("wrong element", "beta", lst.get(2));
}
// test method to test the replace operation
public void testReplace() {
assertEquals("wrong size", 2, lst.size());
lst.set(1, "charlie");
assertEquals("wrong size", 2, lst.size());
assertEquals("wrong element", "alpha", lst.get(0));
assertEquals("wrong element", "charlie", lst.get(1));
}
Step 5: You can now run the TestCase
, using JUnit-provided TestRunner
. There are two versions of TestRunner
: text-based junit.textui.TestRunner
, and GUI-basedjunit.swingui.TestRunner
. To use the text-based TestRunner
, you could include a main()
method as follows:
public static void main(String[] args) {
junit.textui.TestRunner.run(JUnit38DemoArrayList.class);
}
The expected outputs are:
..
Time: 0.001 OK (2 tests)
You can also invoke the TestRunner
from command-line:
> java junit.textui.TestRunner JUnit38DemoArrayList
You can invoke the GUI-based TestRunner
from command-line:
> java junit.swingui.TestRunner JUnit38DemoArrayList
Step 6: If there are many TestCase
s (could be written by different people), you can put them together into a TestSuite
and run all the TestCase
s at once. To do so, in each of theTestCase
s, create a static method suite()
to extract all the test methods as follows:
// In JUnit38DemoArrayList Class - do the same for other TestCase classes
public static Test suite() {
return new TestSuite(JUnit38DemoArrayList.class);
}
Next, write a class to include all the TestCase
s into a TestSuite
.
import java.framework.*;
public class AllTests {
public static void main(String[] args) {
junit.textui.TestRunner.run(suite());
}
public static Test suite() {
TestSuite suite = new TestSuite();
suite.addTest(JUnit38DemoArrayList.suite());
// other TestCase classes
//suite.addTest(OtherTestCase1.suite());
//suite.addTest(OtherTestCase2.suite());
return suite;
}
}
5. Automating Unit Testing with ANT or Maven
[TODO] To tidy up.
Apache's ANT is the de facto standard for automated building of Java applications (similar to Unix's "make
" utility). You can download ANT from ant.apache.org (download the ZIP version, and unzip it to a directory of your choice).
We shall use ANT to automate building and testing. First, create a configuration file called "build.xml"
as follows:
<?xml version="1.0"?>
<!-- to save as "build.xml" --> <project name="Black-Box Unit Test Demo" default="run" basedir=".">
<!-- build all classes in this directory -->
<!-- To run this: use "ant build" -->
<!-- need to include junit.jar in the classpath -->
<target name="build">
<javac srcdir="${basedir}"/>
<echo message="Build done" />
</target> <!-- Test and build all files -->
<!-- To run this: use "ant" (default) or "ant run" -->
<target name="run" depends="build">
<java taskname="Test" classname="GradeTestCase" fork="true" failonerror="true" />
<echo message="Unit Test done" />
</target> <!-- delete all class files -->
<!-- To run this: use "ant clean" -->
<target name="clean">
<delete>
<fileset dir="${basedir}" includes="*.class" />
</delete>
<echo message="clean done" />
</target>
</project>
To build using the above build file, run "ant
". (By default, it executes "ant run
", which is depends on "build
", "build
" get executed to compile the program, then "run
" get expected to run the testing. To run only the compilation, use "ant build
". To run only the cleanup, use "ant clean
".)
prompt> ant
Buildfile: build.xml build:
[javac] Compiling 4 source files
[echo] Build done run:
[Test] ..F
[Test] Time: 0.005
[Test] There was 1 failure:
[Test] 1) testBoundaries(GradeTestCase)junit.framework.AssertionFailedError: expected:<C> but was:<F>
[Test] at GradeTestCase.testBoundaries(GradeTestCase.java:23)
[Test] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[Test] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
[Test] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
[Test] at GradeTestCase.main(GradeTestCase.java:34)
[Test]
[Test] FAILURES!!!
[Test] Tests run: 2, Failures: 1, Errors: 0
[Test]
[echo] Unit Test done
[TODO] to be continued...
6. Unit Testing Best Practices
6.1 Writing Test Cases
How to test a program to ensure correctly? There are two techniques: white-box testing and black-box testing. White-box testing inspect the program codes and test the program logic. Black-box testing does not inspect the program codes, but looking at the input-output, treating the program as a black box.
For black-box testing, the most common approach is to partition the inputs, and design test cases for each input partition. The test cases could test on a typical input value as well as the boundaries.
For example, the following program converts a given mark (0-100) to a letter grade ('A' to 'F'). There is a logical error in the program.
1 |
import static org.junit.Assert.assertEquals; |
Try to run the above tests to find the logical error. Take note that assertEquals()
does not accept char
as arguments, but upcast to long
. That is, the output show the char
's numeric code.
6.2 Unit Testing Best Practices (From JUnit FAQ)
The followings are extracted from JUnit FAQ:
- When should the tests be written?
Tests should be written before the code. Good tests tell you how to best design the system for its intended use. They also prevent tendencies to over-build the software. When all the tests pass, you know you're done. Whenever a customer reports a bug, first write the necessary unit test(s) to expose the bug(s) and fix them. This make it almost impossible for the same bug to resurface later. - Do I have to write a test for everything?
No, just test things that could reasonably break. Don't write tests that turn out to be testing the operating system or environment or the compiler. For example,public class AClass {
int x;
public AClass(int x) { this.x = x; }
int getX() { return x; }
void setX() { this.x = x; }
}A test that testing
getX(setX(y)) == y
is merely testing forthis.y = y
, i,e, testing the compiler! This can't break unless the compiler or the interpreter break! - How often should I run my tests?
Run unit test as often as possible, ideally every time the code is changed. Run all your acceptance, integration, stress, and unit tests at least once per day (for your nightly-built).
7. TestNG
TestNG (Test Next Generation) (@ http://testng.org/) is a testing framework inspired from JUnit and NUnit (the xUnit family), but introduces new functionalities like dependency testing, grouping concept to make testing easier and more powerful.
TestNG is designed to cover all types of tests: unit, integration, functional, and etc.
7.1 Installing TestNG
Installing TestNG: From TestNG download site (@ http://testng.org/doc/download.html), download the "testng-6.#.zip
". Unzip the downloaded file. The binaries is kept in "testng-6.#.jar
"
To install TestNG Eclipse Plug-in ⇒ Launch Eclipse ⇒ Help ⇒ Install New Software ⇒ In Work with, enter http://beust.com/eclipse ⇒ Add ⇒ Select TestNG.
Using TestNG: To use TestNG, include the jar-files in the CLASSPATH
.
In Eclipse, right-click on the project ⇒ Add Library ⇒ TestNG.
API Documentation: The TestNG API documentation is available @ http://testng.org/javadocs/.
7.2 Getting Started with TestNG with Example
I shall assume that you are familiar with JUnit 4.
Let's use TestNG (instead of JUnit) to test the MyNumber
class written in the earlier section.
In Eclipse, right-click on the project ⇒ New ⇒ TestNG ⇒ TestNG class.
1 |
import static org.testng.Assert.*; |
To run the test case under Eclipse, right-click on the file ⇒ Run as ⇒ TestNG Test.
@BeforeClass - oneTimeSetUp
@BeforeMethod - setUp before each test
@Test - testAdd
@AfterMethod - tearDown before each test
@BeforeMethod - setUp before each test
@Test - testDiv with exception
@AfterMethod - tearDown before each test
@AfterClass - oneTimeTearDown
PASSED: testAdd
PASSED: testDiv ===============================================
Default test
Tests run: 2, Failures: 0, Skips: 0
===============================================
As seen from the output, the @BeforeClass
annotated method is run ONCE for one-time setup; the @AfterClass
is run ONCE for one-time tear down. The @BeforeMethod
and@AfterMethod
(called @Before
and @After
in JUnit 4) are run before and after EACH @Test
.
So far, everything is similar to JUnit 4, except some name changes.
7.3 Running TestNG Test Cases
There are several ways to run test case for TestNG:
- With an XML description file.
- With ANT build tool.
Via TestNG XML Description File
TestNG (compared with JUnit) introduces an XML description to describe test suite/test cases to provide more flexibility in running tests.
Prepare the following XML Description file (says "testing.xml
"), which describes a test suite comprising of test cases. Each test case comprises of many Java classes.
1 |
<?xml version="1.0" encoding="UTF-8"?> |
You can run the test suite in command-line:
// Set CLASSPATH to include the TestNG jar-file
// For Linux/Mac with bash shell
$ export CLASSPATH=.:$CLASSPATH:/path/to/testng-6.8/testng-6.8.jar // For Windows
> set CLASSPATH=.;%CLASSPATH%;x:\path\to\testng-6.8\testng-6.8.jar // Compile the Java test classes
$ javac MyNumberTestNGTest.java // Run the test thru XML description file
$ java org.testng.TestNG testing.xml
@BeforeClass - oneTimeSetUp
@BeforeMethod - setUp before each test
@Test - testAdd
@AfterMethod - tearDown before each test
@BeforeMethod - setUp before each test
@Test - testDiv with exception
@AfterMethod - tearDown before each test
@AfterClass - oneTimeTearDown ===============================================
MyNumberTestSuite
Total tests run: 2, Failures: 0, Skips: 0
===============================================
In Eclipse, to run a suite description file ⇒ Run ⇒ Run Configurations ⇒ Suite ⇒ Select the desired XML description file.
The XML description file has this syntax:
- The root tag is <suite>.
- The <suite> tag can contain one or more <test> tags.
- The <test> tag can contain one or more <classes> tags.
- The <classes> tag can contain one or more <method> tags.
Via ANT Script
[TODO]
7.4 TestNG's Annotations
Name | Description |
---|---|
@Test |
Mark a method (or class) as a test method (or class). |
@BeforeSuite / @AfterSuite |
Run ONCE before and after all tests in this suite. |
@BeforeClass / @AfterClass |
Run ONCE before and after all tests in this class. |
@BeforeMethod / @AfterMethod |
Run before and after EACH @Test method. |
@BeforeTest / @AfterTest |
[TODO] |
@BeforeGroups / @AfterGroups |
[TODO] |
@Parameters |
Mark this test method is to get its parameters from the XML description file. |
@DataProvider |
Mark the method, which return an Object[][] , as data source for a test method. |
@Factory |
[TODO] |
@Listeners |
[TODO] |
7.5 TestNG - Exception Test
Mark the test method that is expected to throw an exception with @Test(expectedExceptions = ExceptionName.class)
as seen in the above example.
JUnit 4 uses annotation @Test(expected = ExceptionName.class)
.
7.6 TestNG - Ignore Test
To ignore a test, mark it with annotation @Test(enabled=false)
.
JUnit 4 uses an dedicated annotation @Ignore
to override the @Test
annotation.
7.7 TestNG - Timing Test
To set a timeout (milliseconds) for a test, use annotation @Test(timeOut = msec)
(exactly the same as JUnit 4).
7.8 TestNG - Parameterized Test
Via @Parameters and the XML Description File <parameter> Tag
The test class is as follows:
1 |
import static org.testng.Assert.*; |
Mark the parameterized test method with annotation @Parameters(value=xxx)
, where xxx is a String[]. The values will be passed into the arguments of the method in the same order.
The parameters are fed from the XML description file with the <parameter> tag. For example,
1 |
<?xml version="1.0" encoding="UTF-8"?> |
Via the @DataProvider
The @Parameters
annotation can only used to pass simple type (such as String
and int
). To pass objects, you need to use @DataProvider
annotation.
For example,
1 |
import static org.testng.Assert.*; |
number1=11 number2=22 result=33
number1=111 number2=222 result=333
number1=1111 number2=2222 result=3333
PASSED: testAdd(MyNumber@1e53fc13, MyNumber@1bca52f3, 33)
PASSED: testAdd(MyNumber@74b1896c, MyNumber@33b54d4e, 333)
PASSED: testAdd(MyNumber@15e19d13, MyNumber@f0f559e, 3333) ===============================================
Default test
Tests run: 3, Failures: 0, Skips: 0
===============================================
7.9 TestNG - Method Dependency Test
TestNG (compared with JUnit) introduces test dependency. For example,
1 |
import static org.testng.Assert.*; |
@Test: method1
@Test: method2
@Test: method3
PASSED: method1
PASSED: method2
PASSED: method3 ===============================================
Default test
Tests run: 3, Failures: 0, Skips: 0
===============================================
In method1
, if we change to assertTrue(false)
to fail the test, method2
and method3
will not be run, but marked as skip (instead of fail as in JUnit 4), as shown in the following outputs:
@Test: method1
FAILED: method1
java.lang.AssertionError: expected [true] but found [false]
SKIPPED: method2
SKIPPED: method3 ===============================================
Default test
Tests run: 3, Failures: 1, Skips: 2
===============================================
7.10 TestNG - Group Test and Dependency
Each test method can be assigned to one or more groups. We can select one or more groups to test via XML description file. For example,
1 |
import org.testng.annotations.*; public class TestNGGroupTest { |
The XML description file to run methods in group "init" only.
1 |
<?xml version="1.0" encoding="UTF-8"?> |
Dependency on Groups
Instead of specifying dependency on individual method names as in the previous section, we can place related method (e.g., init methods) in groups, and specifying dependency on groups of methods. For example,
1 |
import static org.testng.Assert.*; |
If we use assertTrue(true)
in method1
, method3
will be run. However, if we use assertTrue(false)
in method1
, method3
will be skipped.
REFERENCES & RESOURCES
- JUnit mother site @ http://junit.org.
- TestNG mother site @ http://testng.org.
- JUnit API documentation @ http://junit.sourceforge.net/javadoc_40.
- Kent Beck and Erich Gamma, "JUnit Cookbook" @ http://junit.sourceforge.net/doc/cookbook/cookbook.htm.
- JUnit A Cook's Tour (for JUnit 3.8) @ http://junit.sourceforge.net/doc/cookstour/cookstour.htm.
Latest version tested: JDK 1.7, JUnit 4.11, TestNG 6.8
Last modified: October, 2013
Feedback, comments, corrections, and errata can be sent to Chua Hock-Chuan (ehchua@ntu.edu.sg) | HOME
Java Unit Testing - JUnit & TestNG的更多相关文章
-
10 Unit Testing and Automation Tools and Libraries Java Programmers Should Learn
转自:https://javarevisited.blogspot.com/2018/01/10-unit-testing-and-integration-tools-for-java-program ...
-
Java单元测试之JUnit 5快速上手
前言 单元测试是软件开发中必不可少的一环,但是在平常开发中往往因为项目周期紧,工作量大而被选择忽略,这样往往导致软件问题层出不穷.线上出现的不少问题其实在有单元测试的情况下就可以及时发现和处理,因此培 ...
-
[Java Basics3] XML, Unit testing
What's the difference between DOM and SAX? DOM creates tree-like representation of the XML document ...
-
通过junit/TestNG+java 实现自动化测试
第一步 安装JDK JDk1.7. 下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-188026 ...
-
Unit Testing of Classes in Java
Every class can have a main method. That is a handy trick for unit testing of classes. For example, ...
-
Java自动化测试框架-02 - TestNG之理论到实践
TestNG,即Testing, NextGeneration,下一代测试技术,是一套根据JUnit 和NUnit思想而构建的利用注释来强化测试功能的一个测试框架,即可以用来做单元测试,也可以用来做集 ...
-
C/C++ unit testing tools (39 found)---reference
http://www.opensourcetesting.org/unit_c.php API Sanity AutoTest Description: An automatic generator ...
-
Java单元测试框架 JUnit
Java单元测试框架 JUnit JUnit是一个Java语言的单元测试框架.它由Kent Beck和Erich Gamma建立,逐渐成为源于KentBeck的sUnit的xUnit家族中为最成功的一 ...
-
Unit Testing of Spring MVC Controllers: “Normal” Controllers
Original link: http://www.petrikainulainen.net/programming/spring-framework/unit-testing-of-spring-m ...
随机推荐
-
easyUi load方法重新加载表单的数据
1.表单回显数据的方法 <script> //方法一 function loadLocal(){ $('#ff').form('load',{ name:'myname', email:' ...
-
解决vi/vim中粘贴会在行首多很多缩进和空格的问题
解决vi/vim中粘贴会在行首多很多缩进和空格的问题 secureCRT会将你原来的文本原封不动的按照字符串的样式发送给服务器.所以当你的服务器上的vim设置为autoindent的话,在i模式下,那 ...
-
SynchronousQueue 的简单应用
SynchronousQueue是这样一种阻塞队列,其中每个 put 必须等待一个 take,反之亦然.同步队列没有任何内部容量,甚至连一个队列的容量都没有. 不能在同步队列上进行 peek ...
-
mongo复习
$pop:-1移除数组的第一个元素,1移除最后一个元素eg: db.c.update({"name" : "toyota"},{$pop:{"titl ...
-
OpenCMS integration with Spring MVC--reference
ref from:http://blogs.indrajitpingale.com/?p=8 http://blog.shinetech.com/2013/04/09/integrating-spri ...
-
java Enumeration用法
Enumeration是java.util中的一个接口类,在Enumeration中封装了有关枚举数据集合的方法. 在Enumeration中提供了方法hawMoreElement()来判断集合中是束 ...
-
用maven骨架生成项目速度慢的问题
最近从IntelliJ Idea 14的Community版本切换到Ultimate. 问题出现 最近从IntelliJ Idea 14的Community版本切换到Ultimate,key是从网络上 ...
-
20175221 MyCP(课下作业,必做)
MyCP(课下作业,必做) 任务详情 编写MyCP.java 实现类似Linux下cp XXX1 XXX2的功能,要求MyCP支持两个参数: - java MyCP -tx XXX1.txt XXX2 ...
-
20165234 《Java程序设计》第二周学习总结
第二周学习总结 教材学习内容总结 第二章 标识符与关键字 1.标识符 其本质是文件名字. 由字母.下画线.美元符号和数字组成,长度不受限制. 标识符的第一个字符不能为数字,标识符不能为关键字(如int ...
-
springMVC01,使用xml
unit03_01 unit03_02 springmvc流程图 案例:hello示例 /hello.do --> springmvc --> /WEB-INF/hello.jsp /he ...