Junit 3.8.1 源码分析(一)
写在前面:本文基于Junit3.8.1版本,因为这是我第一次进行源码学习,先从简单的源码开始学起
1. 示例代码
1.1 准备工作
- 下载Junit3.8.1的JAR包
- 需要下载
junit-3.8.1-sources.jar
和junit-3.8.1.jar
,前者是源码包,后者是项目中需要使用的Jar包;
1.2 项目中导入Junit相关jar包
- 使用Eclipse,新建项目
test
; test
右键,选中properties
1.3 编写示例代码
- JUnit4之前,JUnit明确要求测试方法名以"test"开头;
- JUnit 测试类需要继承 "TestCase";
// SampleCalculator.java,需要测试的类
public class SampleCalculator{
// 加法
private int add(int a, int b){
return a + b;
}
// 乘法
public int multiply(int a, int b){
return a * b;
}
//除法
public double divide(int a, int b){
return a/b;
}
}
// 编写测试类,该类需要继承 TestCase
public class TestSample extends TestCase{
private SampleCalculator cal = null;
protected void setUp() throws Exception{
super.setUp();
// 每个测试方法执行前,重新new一个对象,避免测试用例之间的依赖
cal = new SampleCalculator():
System.out.println("在每个测试方法执行前执行 ---setUp...");
}
// 测试源代码的私有方法
public void testAdd(){
System.out.println("测试方法testAdd...");
try{
// 使用反射,进行私有方法的调用
Class<SampleCalculator> clazz = SampleCalculator.class;
Method method = clazz.getDeclaredMethod("add", new Class[] { Integer.TYPE, Integer.TYPE});
method.setAccessible(true);
Object obj = method.invoke(cal, new Object[]{1, 2});
Assert.assertEquals(3, obj);
}catch(Exception e){
Assert.fail();
}
}
// 测试乘法
public void testMultiply(){
System.out.println("测试方法testMultiply...");
Assert.assertEquals.(3, cal.multiply(1, 2));
}
// 测试除法
public void testDivice(){
System.out.println("测试方法testDivide...");
Assert.assertEquals(2, cal.divide(1, 0));
}
@Override
protected void tearDown() throws Exception{
super.tearDown();
cal = null; // 在每个测试方法执行后,主动销毁对象
System.out.println("在每个测试方法执行后执行--tearDown...\n");
}
// main方法调用
public static void main(String[] args){
// 以文字输出的方式,显示运行结果
junit.textui.TestRunner.run(TestSample.class);
// 以图形化界面的方式,显示运行结果
// junit.swingui.TestRunner.run(TestSample.class);
// 以图形化界面的方式,显示运行结果
// junit.awtui.TestRunner.run(TestSample.class);
}
}
备注
- Failures:表示测试的期待结果与程序运行结果不相符;
- Errors: 表示测试程序执行过程中,抛出了异常;
2. JUnit运行流程(DEBUG模式运行)
- JUnit的完整生命周期分为三个阶段:初始化阶段,运行阶段和结果捕捉阶段
2.1 初始化阶段(创建TestCase及TestSuite)
- 图中红框处,采用了
Composite Pattern
;while
循环第一次,获取TestSample
中的方法,
第二次循环,获取其父类TestCase
中的方法:superClass = superClass.getSuperclass();
;
// TestSuite.java
public class TestSuite implements Test{
private Vector fTests = new Vector(10);
private String fName;
...(略)
// 将符合条件的测试方法转化为TestCase,并存入到集合中
public void addTestMethod(Method m, Vector names, Class theClass){
String name = m.getName();
if(names.contains(name))
return;
// 判断是否是公共方法
if(!isPublicTestMethod(m)){
// 判断是否是测试方法
if(isTestMethod(m))
addTest(warning("Test method isn't public: "+m.getName()));
return;
}
names.addElement(name);
// 将testXXX方法转化为 TestCase
addTest(createTest(theClass, name));
}
public void addTest(Test test){
fTests.addElement(test);
}
// 判断是否是公共方法
public boolean isPublicTestMethod(Method m){
return isTestMethod(m) && Modifier.isPublic(m.getModifiers());
}
// 判断是否是测试方法
// 1. 名称以"test"开头
// 2. 无参数,无返回值
public boolean isTestMethod(Method m){
String name = m.getName();
Class[] parameters = m.getParameterTypes();
Class returnType = m.getReturnType();
return parameters.length == 0 && name.startWith("test") && returnType.equals(Void.TYPE);
}
// 根据testXXX名称,创建对应的TestCase
static public Test createTest(Class theClass, String name){
Constructor constructor;
try{
constructor = getTestConstructor(theClass);
}catch(NoSuchMethodException e){
...(略)
}
Object test;
try{
if(constructor.getParameterTypes().length == 0){
test = constructor.newInstance(new Object[0]);
if(test instanceof TestCase)
((TestCase)test).setName(name);
}else{
test = constructor.newInstance(new Object[]{name});
}
}catch(Exception e){
...(略)
}
return (Test)test;
}
}
2.2 运行阶段(运行所有TestXXX型的测试方法)
- 创建
TestResult
实例; - 将
junit.textui.TestRunner
的监听器fPrinter
加入到result的监听器列表中(观察者模式); - 开始计时;
run(result)
测试运行;- 结束计时;
- 结果输出;
参考资料: