背景:在看apache commons-validator源码包时,发现测试用例继承了TestCase,这里总结一下。
两者都位于junit.framework包下且都实现了Test接口,功能相近,区别是前者只能运行一个test,后者可以运行多个test。
Test: 该接口有两个待实现方法,分别是countTestCases()和run(TestResult)
TestCase: 该类接收一个参数fName,其为TestCase类对象中待运行的测试方法名。该参数可以在构造函数中初始化,也可以通过setName方法来设置。
该类有四个protected方法,分别是createResult(),runTest,setUp(),tearDown()。其中setUp(),tearDown()是空方法,可以被子类override,主要作用是在测试用例运行前后进行一些初始化/清理操作。TestCase被abstract修饰,虽然没有abstract方法,应该有框架设计者的某种意图。createResult()用于新建并返回一个TestResult,TestCase的run()方法第一句就是这个命令,接着就调用run(TestResult result),然后返回。run(TestResult result)只有一条命令result.run(this),也就是一条回调函数,这样做的目的是使用TestResult类对象完成测试前以及测试后通知TestListener(可能有多个)的作用。TestResult.run(TestCase test)函数体中前后是完成通知功能,中间是调用TestCase的runBare()命令。TestListener是个接口,具体是怎么被实例化并添加的,需要再看下junit的runner机制。TestCase.runBare()函数体前后分别是setUp()和tearDown,中间是runTest()。该方法会assert一下名为fName的方法是否存在,然后调用getClass().getMethod(fName)方法得到method,再invoke。getClass()是class类的native方法,用于获得类的运行时类型。getMethod()方法用于获得所有方法,getDeclaredMethod()方法用于获得非继承方法。
TestCase只能运行一个TestCase中的方法,由上面的分析,其调用路径为new TestCase(fName)->TestCase.run()->TestCase.run(TestResult result)->result.run(TestCase)->TestResult.run(TestCase test)->TestCase.runBare()->TestCase.runTest()。
TestSuite: 该类有6个构造函数,分别是无参,一个参数String,一个参数Class<?>,参数Class<?>和参数String,一个参数Class<? extends TestCase>[],参数Class<? extends TestCase>[]和String。该类也有一个属性名为fName。
无参或只有一个String(用于初始化fName)的参数会创建一个空的TestSuite,空是指fTests(其为Vector<Test>)这个属性为空。空的TestSuite可通过addTestSuite(Class<? extends TestCase> testClass)来添加测试用例,这个函数只有一行代码,即addTest(new TestSuite(testClass))。TestSuite构造函数只有一个参数且为Class<?>类型时,会调用addTestsFromTestCase(Class)(这个方法会先判断Class是否为public的,如为public且Class为Test的子类则循环Class类中的所有方法,顺序是先遍历这个类的DeclaredMethod,再遍历Class的superClass(须为Test类型,否则循环终止)的DeclaredMethod,直到最底层不为Test类型的superClass),该方法会在循环中调用addTestMethod(Method, List<String>, Class),这个方法通过isPublicTestMethod()判断是否为public且为test开头的方法名,如果是,则通过createTest(Class,name)将特定的测试用例方法名生成为Test,再加到Vector<Test>中。addTestSuite方法的发现过程是通过在intellij中对addTestsFromTestCase(Class)这个方法ctl+alt+h,得到方法调用层次,这个方法很有意义,可以把一个Test类中所有以test开头的方法加到Vector<Test>中,这个方法却只被一个构造方法所调用,就是 TestSuite(Class<? extends TestCase>),继续点开层次图可以看到这个构造方法又被TestSuite.addTestSuite(Class<? extends TestCase>)调用,于是得出空的TestSuite可通过addTestSuite(Class<? extends TestCase> testClass)来添加测试用例,当然非空也可以添加。
参数为Class和String的构造函数,其Class参数为Class<? extends TestCase>,该构造函数有两条命令,一条是调用单参数且为Class类型的构造方法,一条是setName(fName),由此可以看出该构造函数也能将参数Class中的所有public且为test开头的方法加入到测试用例中。同时也能看出TestSuite中虽然有和TestCase同名的fName属性,还是有所不同的。TestSuite中这个属性只作为一个标记性的suite name,但是在TestCase中这个属性用于指定哪个方法作为用例。
参数为Class<?>... 的构造函数,调用testCaseForClass(Class<?>),该方法会判断若参数为TestCase类型,则new一个单参数为Class类型的TestSuite。然后将TestSuite加入到Vector<Test>。很明显,这种方式也能将Class<?>...这个数组中的每个public类中的public且以test开头的方法作为测试用例。
最后一种构造函数参数类型,Class<? extends TestCase>[] classes和String name,该函数只有两条命令,一条调用上面的参数为Class<?>... 的构造函数,一条setName(fName)。
以上,通过下载框架源码并导入到intellij,阅读源码得到。