Fork me on GitHub

Junit 3.8.1 源码分析(一)

写在前面:本文基于Junit3.8.1版本,因为这是我第一次进行源码学习,先从简单的源码开始学起

1. 示例代码

1.1 准备工作

  • 下载Junit3.8.1的JAR包
  • 需要下载junit-3.8.1-sources.jarjunit-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)测试运行;
  • 结束计时;
  • 结果输出;


参考资料:

posted @ 2018-05-13 16:32  小a的软件思考  阅读(1055)  评论(0编辑  收藏  举报