JUnit 是由 Erich Gamma 和 Kent Beck 编写的一个开源的单元测试框架。它属于白盒测试,只要将待测类继承 TestCase 类,就可以利用 JUnit 的一系列机制进行便捷的自动测试了。
JUnit 的设计精简,易学易用,但是功能却非常强大,这归因于它内部完善的代码结构。 Erich Gamma 是著名的 GOF 之一,因此 JUnit 中深深渗透了扩展性优良的设计模式思想。 JUnit 提供的 API 既可以让您写出测试结果明确的可重用单元测试用例,也提供了单元测试用例成批运行的功能。在已经实现的框架中,用户可以选择三种方式来显示测试结果,并且显示的方式本身也是可扩展的。
JUnit 的完整生命周期分为 3 个阶段:初始化阶段、运行阶段和结果捕捉阶段。下面就从这三个阶段详细分析源代码。
初始化阶段
初始化的入口有三个,分别是junit.textui.TestRunner,junit.swingui.TestRunner,junit.awtui.TestRun
ner,他们的不同就是ui不同,下面以textui为例:
public static void main(String[] args) {
TestRunner aTestRunner = new TestRunner();
try {
TestResult r = aTestRunner.start(args);
if (!r.wasSuccessful()) {
System.exit(FAILURE_EXIT);
}
System.exit(SUCCESS_EXIT);
} catch (Exception e) {
System.err.println(e.getMessage());
System.exit(EXCEPTION_EXIT);
}
}
public TestResult start(String[] args) throws Exception {
String testCase = "";
String method = "";
boolean wait = false;
...
try {
if (!method.equals("")) {
return runSingleMethod(testCase, method, wait);
}
//初始化
Test suite = getTest(testCase);
return doRun(suite, wait);
} catch (Exception e) {
throw new Exception("Could not create and run test suite: " + e);
}
}
start函数所做的事就是将传入的参数中的类名提取出来(也就是你要测试哪个类)存在testcase中,并且根据你传入的参数确定运行模式,拿到testcase后,就调用getTest方法。
public Test getTest(String suiteClassName) {
...
try {
//首先根据类名获取Class信息
testClass = loadSuiteClass(suiteClassName);
...
try {
suiteMethod = testClass.getMethod(SUITE_METHODNAME);
} catch (Exception e) {
// try to extract a test suite automatically
clearStatus();
//然后根据Class信息获取所有的测试方法
return new TestSuite(testClass);
...
}
如果这里没有通过getMethod方法拿到suiteMethod的话,就会调用TestSuite构造器
public TestSuite(final Class<?> theClass) {
addTestsFromTestCase(theClass);
}
private void addTestsFromTestCase(final Class<?> theClass) {
...
Class<?> superClass = theClass;
List<String> names = new ArrayList<String>();
//判断superClass是不是Test类的子类
while (Test.class.isAssignableFrom(superClass)) {
for (Method each : MethodSorter.getDeclaredMethods(superClass)) {
addTestMethod(each, names, theClass);
}
superClass = superClass.getSuperclass();
}
if (fTests.size() == 0) {
addTest(warning("No tests found in " + theClass.getName()));
}
}
用迭代的形式从被测试的类开始,到被测试的类继承的TestCase,最后到Object类,这个循环从这几个类中所有符合条件的测试方法加入到名为names的list中。
private void addTestMethod(Method m, List<String> names, Class<?> theClass) {
String name = m.getName();
if (names.contains(name)) {
return;
}
//判断是否是public方法
if (!isPublicTestMethod(m)) {
//判断是否是以test开头的函数
if (isTestMethod(m)) {
addTest(warning("Test method isn't public: " + m.getName() + "(" + theClass.getCanonicalName() + ")"));
...
}
点进addTestMethod方法中,可以发现,加入的方法,第一必须是public的,第二签名必须以test开头,形如testXXX()
测试运行阶段
public TestResult doRun(Test suite, boolean wait) {
TestResult result = createTestResult();
result.addListener(fPrinter); //将打印结果的对象加为观察者
long startTime = System.currentTimeMillis();
suite.run(result);
long endTime = System.currentTimeMillis();
long runTime = endTime - startTime;
fPrinter.print(result, runTime);
pause(wait);
return result;
}
初始化之后,开始运行
public void run(TestResult result) {
for (Test each : fTests) {
if (result.shouldStop()) {
break;
}
runTest(each, result);
}
}
循环调用每一个测试的方法
runMethod.invoke(this);
最终由这行代码调用了被测试方法
结果捕捉阶段
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);
}
}
将错误与失败加入链表中,然后由fPrinter打印在控制台上,这里使用的是观察者模式
参考资料:
https://www.ibm.com/developerworks/cn/java/j-lo-junit-src/#icomments