Junit的使用和源码分析

Junit的使用和源码分析

Junit是一个编写可重复测试的Java测试框架,代码编写非常有技巧性,值得反复阅读。

跟着官方文档学习Junit

官方文档往往是学习最好的资料。

简单测试例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class FirstTest {

public int add(int a, int b) {
return a + b;
}

@Test
public void testAdd(){
FirstTest firstTest = new FirstTest();
int result = firstTest.add(1,2);
// assertEquals(4,result);
assertEquals(3,result);
}

<!--
- 如果判断不相等的时候,后台报错信息
- java.lang.AssertionError:
- Expected :4
- Actual :3
- <Click to see difference>
-->

}

这只是简单的例子,实际上的单元测试要比这个复杂的多,在实际应用上单元测试十分有必要,编写后台代码时能够尽快检验代码的正确性。

Assertions 断言

Junit提供了所有基本数据类型,Object类和数组的断言,参数是 预期值后面是实际值,可选项,第一个参数可以是断言失败时输出的内容,与其他断言稍有不同的是,AssertThat的参数是 失败输出的内容,实际值和一个Matcher
Object。

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public class AssertTests {
@Test
public void testAssertArrayEquals() {
byte[] expected = "trial".getBytes();
byte[] actual = "trial".getBytes();
assertArrayEquals("failure - byte arrays not same", expected, actual);
}

@Test
public void testAssertEquals() {
assertEquals("failure - strings are not equal", "text", "text");
}

@Test
public void testAssertFalse() {
assertFalse("failure - should be false", false);
}

@Test
public void testAssertNotNull() {
assertNotNull("should not be null", new Object());
}

@Test
public void testAssertNotSame() {
assertNotSame("should not be same Object", new Object(), new Object());
}

@Test
public void testAssertNull() {
assertNull("should be null", null);
}

@Test
public void testAssertSame() {
Integer aNumber = Integer.valueOf(768);
assertSame("should be same", aNumber, aNumber);
}

// JUnit Matchers assertThat
@Test
public void testAssertThatBothContainsString() {
assertThat("albumen", both(containsString("a")).and(containsString("b")));
}

@Test
public void testAssertThatHasItems() {
assertThat(Arrays.asList("one", "two", "three"), hasItems("one", "three"));
}

@Test
public void testAssertThatEveryItemContainsString() {
assertThat(Arrays.asList(new String[] { "fun", "ban", "net" }), everyItem(containsString("n")));
}

// Core Hamcrest Matchers with assertThat
@Test
public void testAssertThatHamcrestCoreMatchers() {
assertThat("good", allOf(equalTo("good"), startsWith("good")));
assertThat("good", not(allOf(equalTo("bad"), equalTo("good"))));
assertThat("good", anyOf(equalTo("bad"), equalTo("good")));
assertThat(7, not(CombinableMatcher.<Integer> either(equalTo(3)).or(equalTo(4))));
assertThat(new Object(), not(sameInstance(new Object())));
}

@Test
public void testAssertTrue() {
assertTrue("failure - should be true", true);
}
}

异常测试

下面分为两种方式来完成对异常的测试

期待的异常

如何检测程序是否如期的抛出异常,junit可以使用注解的参数来实现。

1
2
3
4
@Test(expected = IndexOutOfBoundsException.class)
public void testException(){
new ArrayList<Integer>().get(0);
}

深入的异常

上述方法对于简单的情况很有用,但它有其局限性。例如,您无法在异常中测试消息的值,也无法在抛出异常后测试域对象的状态

  • try/catch 语句
1
2
3
4
5
6
7
8
9
@Test
public void testTryCatch(){
try {
new ArrayList<Integer>().get(0);
fail("失败信息");
}catch (IndexOutOfBoundsException indexOutOfBoundsExecption){
assertThat(indexOutOfBoundsExecption.getMessage(), is("Index: 0, Size: 0"));
}
}
  • rule 规则
1
2
3
4
5
6
7
8
9
10
@Rule
public ExpectedException thrown = ExpectedException.none();

@Test
public void testExpectException(){
List<Object> list = new ArrayList<Object>();
thrown.expect(IndexOutOfBoundsException.class);
thrown.expectMessage("Index: 0, Size: 0");
list.get(0); // execution will never get past this line
}

Matchers and assertThat [ 匹配器和assertThat ]

新加入了assertThat断言机制 assertThat([value], [matcher statement]);

1
2
3
4
assertThat(x, is(3));
assertThat(x, is(not(4)));
assertThat(responseString, either(containsString("color")).or(containsString("colour")));
assertThat(myList, hasItem("3"));

assertThat 更具有可读性和可输入性,并且有组合性,就像 is(not(4)) 任何Machers都可以组合起来使用

以前的assertEquals等也是可以用的,assertThat 在使用Matchers的时候需要使用 import static org.hamcrest.CoreMatchers.*;来引用。里面的方法非常多。。

junit源码跟读

使用junit流程

使用继承自TestCase类

下面通过运行junit的自带的test,源程序为:

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/**
* Some simple tests.
*/
public class SimpleTest extends TestCase {
protected int fValue1;
protected int fValue2;

@Override
protected void setUp() {
fValue1 = 2;
fValue2 = 3;
}

public static Test suite() {

/*
* the type safe way
*
TestSuite suite= new TestSuite();
suite.addTest(
new SimpleTest("add") {
protected void runTest() { testAdd(); }
}
);

suite.addTest(
new SimpleTest("testDivideByZero") {
protected void runTest() { testDivideByZero(); }
}
);
return suite;
*/

/*
* the dynamic way
*/
return new TestSuite(SimpleTest.class);
}

public void testAdd() {
double result = fValue1 + fValue2;
// forced failure result == 5
assertTrue(result == 6);
}

public int unused;

public void testDivideByZero() {
int zero = 0;
int result = 8 / zero;
unused = result; // avoid warning for not using result
}

public void testEquals() {
assertEquals(12, 12);
assertEquals(12L, 12L);
assertEquals(new Long(12), new Long(12));

assertEquals("Size", 12, 13);
assertEquals("Capacity", 12.0, 11.99, 0.0);
}

public static void main(String[] args) {
junit.textui.TestRunner.run(suite());
}
}

来看main方法,使用 junit.textui.TestRunner.run(suite()); 使用TestRunner运行test。首先先来看suite方法,有两种方法

  1. 静态的,需要手动在testSuite中添加test。
  2. 动态的,静态需要实现TestCase的runTest方法。而动态的只需要返回 TestSuite(SimpleTest.class);,下面来看这个TestSuite类

testSuite实际上就是运行test的集合,使用vector来存储test,其中这里使用到的TestSuite构造方法是:

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
public TestSuite(final Class<?> theClass) {
addTestsFromTestCase(theClass);
}
private void addTestsFromTestCase(final Class<?> theClass) {
fName = theClass.getName();
try {
getTestConstructor(theClass); // Avoid generating multiple error messages
} catch (NoSuchMethodException e) {
addTest(warning("Class " + theClass.getName() + " has no public constructor TestCase(String name) or TestCase()"));
return;

}
// 这个类是否是public的 如果不是 发出warning ,并且fail(message)
if (!Modifier.isPublic(theClass.getModifiers())) {
addTest(warning("Class " + theClass.getName() + " is not public"));
return;
}

Class<?> superClass = theClass;
List<String> names = new ArrayList<String>();
// 这句话说明的是如果这个类是superClass的超类,或者接口就返回true,否则返回false
while (Test.class.isAssignableFrom(superClass)) {
// 如果是true 有顺序的返回声明的方法
for (Method each : MethodSorter.getDeclaredMethods(superClass)) {
addTestMethod(each, names, theClass);
}
// 获得superclass类,递归的查找test方法
superClass = superClass.getSuperclass();
}
if (fTests.size() == 0) {
addTest(warning("No tests found in " + theClass.getName()));
}
}

最后提到的 fTests.size() == 0 这里会发生warning 然后fail。这个fTests 已经再前面声明
了,声明方法是:private Vector<Test> fTests = new Vector<Test>(10);

在对每个声明的方法循环的时候,使用到 addTestMethod 方法,来对每个方法进行处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private void addTestMethod(Method m, List<String> names, Class<?> theClass) {
// 获得方法名如果list数组中已经包含这个方法名,就直接退出
String name = m.getName();
if (names.contains(name)) {
return;
}
if (!isPublicTestMethod(m)) {
if (isTestMethod(m)) {
addTest(warning("Test method isn't public: " + m.getName() + "(" + theClass.getCanonicalName() + ")"));
}
return;
}
// 把这个方法名加入到names List中
names.add(name);
addTest(createTest(theClass, name));
}
//再来看外层的addTest方法 把拥有的test方法放到fTests中。
public void addTest(Test test) {
fTests.add(test);
}

使用createTest来针对test方法创建一个Test类

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
static public Test createTest(Class<?> theClass, String name) {
Constructor<?> constructor;
try {
constructor = getTestConstructor(theClass);
} catch (NoSuchMethodException e) {
return warning("Class " + theClass.getName() + " has no public constructor TestCase(String name) or TestCase()");
}
Object test;
// 通过反射方式获得方法的test实例
try {
if (constructor.getParameterTypes().length == 0) {
test = constructor.newInstance(new Object[0]);
if (test instanceof TestCase) {
((TestCase) test).setName(name);//如果继承TestCase
}
} else {
test = constructor.newInstance(new Object[]{name});
}
} catch (InstantiationException e) {
return (warning("Cannot instantiate test case: " + name + " (" + Throwables.getStacktrace(e) + ")"));
} catch (InvocationTargetException e) {
return (warning("Exception in constructor: " + name + " (" + Throwables.getStacktrace(e.getTargetException()) + ")"));
} catch (IllegalAccessException e) {
return (warning("Cannot access test case: " + name + " (" + Throwables.getStacktrace(e) + ")"));
}
return (Test) test;
}

经过以上的步骤获得了这个类中及其父类中的所有方法的Test。

使用testRunner.run运行test

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
public static void main(String[] args) {
junit.textui.TestRunner.run(suite());
}
// 使用TestRunner的run的静态方法 返回一个TestResult用来返回结果。
static public TestResult run(Test test) {
TestRunner runner = new TestRunner();
return runner.doRun(test);
}
// 调用runner的doRun方法
public TestResult doRun(Test test) {
return doRun(test, false);
}
public TestResult doRun(Test suite, boolean wait) {
// 用来返回结果的TestResult
TestResult result = createTestResult();
// 注册一个TestListener
result.addListener(fPrinter);
long startTime = System.currentTimeMillis();
// test.run方法
suite.run(result);
long endTime = System.currentTimeMillis();
long runTime = endTime - startTime;
fPrinter.print(result, runTime);
pause(wait);
return result;
}

运行Test的核心方法 返回TestResult返回结果。

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
/**
* Creates the TestResult to be used for the test run.
*/
protected TestResult createTestResult() {
return new TestResult();
}

public synchronized void addListener(TestListener listener) {
// protected List<TestListener> fListeners;
fListeners.add(listener);
}
// suite.run() fTests中的每一个test
public void run(TestResult result) {
for (Test each : fTests) {
if (result.shouldStop()) {
break;
}
runTest(each, result);
}
}
/**
* Runs the test case and collects the results in TestResult.
* 调用testResult的run方法
*/
public void run(TestResult result) {
result.run(this);
}
// 运行test
protected void run(final TestCase test) {
startTest(test);
Protectable p = new Protectable() {
public void protect() throws Throwable {
test.runBare();//执行runBare方法执行test用例
}
};
runProtected(test, p);

endTest(test);
}
public void startTest(Test test) {
final int count = test.countTestCases();
synchronized (this) {
fRunTests += count;
}
for (TestListener each : cloneListeners()) {
each.startTest(test);
}
}

在resultPriter中使用继承TestListener中的startTest方法。

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
public void startTest(Test test) {
getWriter().print(".");//运行一个则加一个点
if (fColumn++ >= 40) {
getWriter().println();
fColumn = 0;
}
}

// 运行一个空的测试序列
public void runBare() throws Throwable {
Throwable exception = null;
// 设置装置。比如打开网络连接等,在执行测试之前调用方法。
setUp();
try {
// 看下面的runTest方法
runTest();//执行test
} catch (Throwable running) {
exception = running;
} finally {
try {
tearDown();
} catch (Throwable tearingDown) {
if (exception == null) exception = tearingDown;
}
}
if (exception != null) throw exception;
}

protected void runTest() throws Throwable {
assertNotNull("TestCase.fName cannot be null", fName); // Some VMs crash when calling getMethod(null,null);
Method runMethod = null;
try {
runMethod = getClass().getMethod(fName, (Class[]) null);
} catch (NoSuchMethodException e) {
fail("Method \"" + fName + "\" not found");
}
// .......... runMethod.invoke(this); 执行方法
try {
runMethod.invoke(this);
} catch (InvocationTargetException e) {
// 如果方法执行错误,会触发这个异常 会连续被上层捕捉到
e.fillInStackTrace();
throw e.getTargetException();
} catch (IllegalAccessException e) {
e.fillInStackTrace();
throw e;
}
}

如果测试失败,则最终将被runProtected中的try/catch捕捉到后输出错误信息。

1
2
3
4
5
6
7
8
9
10
11
12
public void runProtected(final Test test, Protectable p) {
try {
p.protect();
} catch (AssertionFailedError e) {
// 验证失败,添加一条失败信息
addFailure(test, e);
} catch (ThreadDeath e) { // don't catch ThreadDeath by accident
throw e;
} catch (Throwable e) {
addError(test, e);
}
}

经过以上的步骤执行完一个test。

-------------本文结束感谢您的阅读-------------