Junit4的应用

1. Junit4概述

JUnit4是JUnit框架有史以来的最大改进,其主要目标便是利用Java5的Annotation特性简化测试用例的编写。
新特性:
(1)、使用junit4.x版本进行单元测试时,不用测试类继承TestCase父类,因为,junit4.x全面引入了Annotation来执行我们编写的测试。(关于Annotation介绍请详见附件:Java注解(Annotation))
(2)、junit4.x版本,引用了注解的方式,进行单元测试;
(3)、junit4.x版本我们常用的注解:
A、@Before 注解:与junit3.x中的setUp()方法功能一样,在每个测试方法之前执行;
B、@After 注解:与junit3.x中的tearDown()方法功能一样,在每个测试方法之后执行;
C、@BeforeClass 注解:在所有方法执行之前执行;
D、@AfterClass 注解:在所有方法执行之后执行;
JUnit4的用例执行顺序是:@BeforeClass> @Before> @Test1> @After> @Before> @Test2> @After….. @AfterClass
E、@Test(timeout = xxx) 注解:设置当前测试方法在一定时间内运行完,否则返回错误;
F、@Test(expected = Exception.class) 注解:设置被测试的方法是否有异常抛出。抛出异常类型为:Exception.class;
G、@Ignore 注解:注释掉一个测试方法或一个类,被注释的方法或类,不会被执行。

2. 环境搭建

选中Project >右键Properties >Java Build Path>Libraries >Junit,如下图所示:

选择JUnit4,如下图所示:

创建一个Junit Case,New  >JUnit Test Case,如下图所示:
选择下图中的“Class Under test”可以选择要进行单元测试的类

3. 高级应用

3.1 Junit4的执行逻辑

1 Junit4Builder编译器会构造Test运行器,如BlockJUnit4ClassRunner,BlockJUnit4ClassRunner 会通过自己的构造器,把当前测试类传到runner里。
2 运行ParentRunner.run()方法,run 方法会获取测试类的Description信息,Description信息会包含测试类的信息,然后执行classBlock(RunNotifier),这个方法获取Statement信息,首先构造一个childrenInvoker,然后在Statement的evaluate()方法调用
runChildren()方法,用来真正地执行test方法,这个步骤会等到测试真正执行后开始做。现在是先获取Statement会处理三种注解,@Before,@After,@Rule,把标注这些注解的方法分别放在集合里,以便后面能够处理这些方法。
3 准备工作都做好之后,会执行步骤2里从classBlock(RunNotifier)获取到的Statement的evaluate()方法,这个方法用来对Statement来说是开了一个口,用户可以自定义Statement的方法,不过在这里,evaluate()主要是来执行步骤2调用的runChildren()方法。
4 runChildren()方法的作用是:遍历测试类的所有测试方法(getFilteredChildren),开启线程调度fScheduler,调度线程给每一个测试方法,然后执行Runnable.run()方法,让测试执行器,让测试执行器可以执行测试类的测试方法(runChild)。
5 执行测试方法首先会判断方法是否含有@Ignore注解,如果有,那么忽略它的测试。如果没有那么执行runLeaf()方法。这个方法是真正我们执行测试的开始。
6 RunLeaf(Statement, Description, RunNotifier),首先Statement是有methodBlock(FrameworkMethod)产生,FrameworkMethod 存着当前要执行的测试方法,在这个方法里,用了反射机制。这时候Junit4会构造RunRules会把Statement,
Description apply()到MethodRules, TestRule进去,也就是说,我们在外部构造的带有@Rule,@Before, @After等就在这里执行,如:
@Rule
public final TestRule testRule = new CommonRule();
此时,我们就可以在我们新构建的CommonRule类里面的apply()方法,做一些对test的预处理,比如预处理连接数据库字符串,读取方法上元数据的信息等;
7 然后就真正执行RunLeaf(Statement, Description, RunNotifier),通过传进来的参数构建EachTestNotifier,执行fireTestStarted,然后,再打开对话,statement.evaluate(),这个执行过程中,会用到InvokeMethod类,然后调用InvokeMethod.evaluate(),最后调用Method.invoke()方法真正地执行了test实体。测试开始了。
备注:就如刚所说的,真正开始测试前,我们可以用apply()进行一些处理,同样地,我们可以在apply()方法中,创建用户Statement,这样就能在evaluate()方法中,做一些操作。如执行脚本,日志等。

3.2 Runner ( 运行器 )

大家有没有想过这个问题,当你把测试代码提交给 JUnit 框架后,框架如何来运行你的代码呢?答案就是—— Runner 。在 JUnit 中有很多个 Runner ,他们负责调用你的测试代码,每一个 Runner 都有各自的特殊功能,你要根据需要选择不同的 Runner 来运行你的测试代码。可能你会觉得奇怪,前面我们写了那么多测试,并没有明确指定一个Runner 啊?这是因为 JUnit 中有一个默认 Runner ,如果你没有指定,那么系统自动使用默认 Runner 来运行你的代码。换句话说,下面两段代码含义是完全一样的:
Import  org.junit.internal.runners.TestClassRunner;
import  org.junit.runner.RunWith;
// 使用了系统默认的TestClassRunner,与下面代码完全一样 
public  class  CalculatorTest  {
...

@RunWith(TestClassRunner. class )
public   class  CalculatorTest  {
...
}
从上述例子可以看出,要想指定一个 Runner ,需要使用 @RunWith 标注,并且把你所指定的 Runner 作为参数传递给它。另外一个要注意的是, @RunWith 是用来修饰类的,而不是用来修饰函数的。只要对一个类指定了 Runner ,那么这个类中的所有函数都被这个 Runner 来调用。

3.3 新断言— assertThat

一般匹配符
1、assertThat( testedNumber, allOf( greaterThan(8), lessThan(16) ) );
注释: allOf匹配符表明如果接下来的所有条件必须都成立测试才通过,相当于“与”(&&)
2、assertThat( testedNumber, anyOf( greaterThan(16), lessThan(8) ) );
注释:anyOf匹配符表明如果接下来的所有条件只要有一个成立则测试通过,相当于“或”(||)
3、assertThat( testedNumber, anything() );
注释:anything匹配符表明无论什么条件,永远为true
4、assertThat( testedString, is( "developerWorks" ) );
注释: is匹配符表明如果前面待测的object等于后面给出的object,则测试通过
5、assertThat( testedString, not( "developerWorks" ) );
注释:not匹配符和is匹配符正好相反,表明如果前面待测的object不等于后面给出的object,则测试通过

字符串相关匹配符
1、assertThat( testedString, containsString( "developerWorks" ) );
注释:containsString匹配符表明如果测试的字符串testedString包含子字符串"developerWorks"则测试通过
2、assertThat( testedString, endsWith( "developerWorks" ) ); 
注释:endsWith匹配符表明如果测试的字符串testedString以子字符串"developerWorks"结尾则测试通过
3、assertThat( testedString, startsWith( "developerWorks" ) ); 
注释:startsWith匹配符表明如果测试的字符串testedString以子字符串"developerWorks"开始则测试通过
4、assertThat( testedValue, equalTo( expectedValue ) ); 
注释: equalTo匹配符表明如果测试的testedValue等于expectedValue则测试通过,equalTo可以测试数值之间,字符串之间和对象之间是否相等,相当于Object的equals方法
5、assertThat( testedString, equalToIgnoringCase( "developerWorks" ) ); 
注释:equalToIgnoringCase匹配符表明如果测试的字符串testedString在忽略大小写的情况下等于"developerWorks"则测试通过
6、assertThat( testedString, equalToIgnoringWhiteSpace( "developerWorks" ) );
注释:equalToIgnoringWhiteSpace匹配符表明如果测试的字符串testedString在忽略头尾的任意个空格的情况下等于"developerWorks"则测试通过,注意:字符串中的空格不能被忽略

数值相关匹配符
1、assertThat( testedDouble, closeTo( 20.0, 0.5 ) );
注释:closeTo匹配符表明如果所测试的浮点型数testedDouble在20.0±0.5范围之内则测试通过
2、assertThat( testedNumber, greaterThan(16.0) );
注释:greaterThan匹配符表明如果所测试的数值testedNumber大于16.0则测试通过
3、assertThat( testedNumber, lessThan (16.0) );
注释:lessThan匹配符表明如果所测试的数值testedNumber小于16.0则测试通过
4、assertThat( testedNumber, greaterThanOrEqualTo (16.0) );
注释: greaterThanOrEqualTo匹配符表明如果所测试的数值testedNumber大于等于16.0则测试通过
5、assertThat( testedNumber, lessThanOrEqualTo (16.0) );
注释:lessThanOrEqualTo匹配符表明如果所测试的数值testedNumber小于等于16.0则测试通过

collection相关匹配符
1、assertThat( mapObject, hasEntry( "key", "value" ) );
注释:hasEntry匹配符表明如果测试的Map对象mapObject含有一个键值为"key"对应元素值为"value"的Entry项则测试通过
2、assertThat( iterableObject, hasItem ( "element" ) );
注释:hasItem匹配符表明如果测试的迭代对象iterableObject含有元素“element”项则测试通过
3、assertThat( mapObject, hasKey ( "key" ) );
注释: hasKey匹配符表明如果测试的Map对象mapObject含有键值“key”则测试通过
4、assertThat( mapObject, hasValue ( "key" ) );
注释:hasValue匹配符表明如果测试的Map对象mapObject含有元素值“value”则测试通过
备注: 使用assertThat 要引入下面语句
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.CoreMatchers.*;  
然后在用例中使用assertThat,下面代码来断言String是否以hi开头,断言正确。
@Test
public void test() {
    String a="hi test";
    assertThat(a ,startsWith("hi"));
}

3.4 参数化测试

你可能遇到过这样的函数,它的参数有许多特殊值,或者说他的参数分为很多个区域。比如,一个对考试分数进行评价的函数,返回值分别为“优秀,良好,一般,及格,不及格”,因此你在编写测试的时候,至少要写 5 个测试,把这 5 中情况都包含了,这确实是一件很麻烦的事情。我们还使用我们先前的例子,测试一下“计算一个数的平方”这个函数,暂且分三类:
正数、 0 、负数。测试代码如下:

import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;

public class AdvancedTest...{
    private static Calculator calculator=new Calculator();

    @Before
    public void clearCalculator() ...{
        calculator.clear();
    }

    @Test
    public void square1() ...{
        calculator.square(2);
        assertEquals(4, calculator.getResult());
    }

    @Test  
    public void square2() ...{
        calculator.square(0);
        assertEquals(0, calculator.getResult());
    }

    @Test  
    public void square3() ...{
        calculator.square(-3);
        assertEquals(9, calculator.getResult());
    }
}

为了简化类似的测试,JUnit4提出了“参数化测试”的概念,只写一个测试函数,把这若干种情况作为参数传递进去,一次性的完成测试。代码如下:

import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
import java.util.Collection;

@RunWith(Parameterized.class)
public class SquareTest...{
    private static Calculator calculator = new Calculator();
    private int param;
    private int result;

    @Parameters  
    public static Collection data() ...{
        return Arrays.asList(new Object[][]...{
            ...{2, 4},
            ...{0, 0},
            ...{-3, 9},
        });
    }

    // 构造函数,对变量进行初始化
    public SquareTest(int param,int result) ...{
        this.param = param;
        this.result = result;
    }

    @Test  
    public void square() ...{
        calculator.square(param);
        assertEquals(result, calculator.getResult());
    }
}

 下面我们对上述代码进行分析。首先,你要为这种测试专门生成一个新的类,而不能与其他测试共用同一个类,此例中我们定义了一个SquareTest类。然后,要为这个类指定一个Runner,而不能使用默认的Runner了,因为特殊的功能要用特殊的Runner。
@RunWith(Parameterized.class)这条语句就是为这个类指定了一个ParameterizedRunner。第二步,定义一个待测试的类,并且定义两个变量,一个用于存放参数,一个用于存放期待的结果。接下来,定义测试数据的集合,也就是上述的data()方法,该方法可以任意命名,但是必须使用@Parameters标注进行修饰。这个方法的框
架就不予解释了,大家只需要注意其中的数据,是一个二维数组,数据两两一组,每组中的这两个数据,一个是参数,一个是你预期的结果。比如我们的第一组{2, 4},2就是参数,4就是预期的结果。这两个数据的顺序无所谓。之后是构造函数,其功能就是对先前定义的两个参数进行初始化。在这里你可要注意一下参数的顺序了,要和上面的数据集合
的顺序保持一致。如果前面的顺序是{参数,期待的结果},那么你构造函数的顺序也要是“构造函数(参数, 期待的结果)”,反之亦然。最后就是写一个简单的测试例了,和前面介绍过的写法完全一样,在此就不多说。

3.5 打包测试

通过前面的介绍我们可以感觉到,在一个项目中,只写一个测试类是不可能的,我们会写出很多很多个测试类。可是这些测试类必须一个一个的执行,也是比较麻烦的事情。鉴于此,JUnit为我们提供了打包测试的功能,将所有需要运行的测试类集中起来,一次性的运行完毕,大大的方便了我们的测试工作。具体代码如下:

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
 
@RunWith(Suite.class)
@Suite.SuiteClasses(...{CalculatorTest.class, SquareTest.class})
public class AllCalculatorTests ...{}

大家可以看到,这个功能也需要使用一个特殊的Runner,因此我们需要向@RunWith标注传递一个参数Suite.class。同时,我们还需要另外一个标注@Suite.SuiteClasses,来表明这个类是一个打包测试类。我们把需要打包的类作为参数传递给该标注就可以了。有了这两个标注之后,就已经完整的表达了所有的含义,因此下面的类已经无关紧要,随便起一个类名,内容全部为空既可。

3.6 控制用例执行顺序

JUnit是通过@FixMethodOrder注解(annotation)来控制测试方法的执行顺序的。@FixMethodOrder注解的参数是org.junit.runners.MethodSorters对象,在枚举类org.junit.runners.MethodSorters中定义了如下三种顺序类型: 

MethodSorters.JVM
Leaves the test methods in the order returned by the JVM. Note that the order from the JVM may vary from run to run (按照JVM得到的方法顺序,也就是代码中定义的方法顺序)

MethodSorters.DEFAULT(默认的顺序)
Sorts the test methods in a deterministic, but not predictable, order() (以确定但不可预期的顺序执行)

MethodSorters.NAME_ASCENDING
Sorts the test methods by the method name, in lexicographic order, with Method.toString() used as a tiebreaker (按方法名字母顺序执行)

用例执行实例

import static org.junit.Assert.*;
import org.junit.After;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class OrderDemo {

    @Before
    public void setUp() throws Exception {
    }

    @After
    public void tearDown() throws Exception {
    }

    @Test
    public void case1_login() {

        assertTrue(true);
    }

    @Test
    public void case2_scenario2() {
        assertTrue(true);
    }

    @Test
    public void case4_scenario4() {
        assertTrue(false);
    }

    @Test
    public void case5_logout() {
        assertTrue(true);
    }

    @Test
    public void case3_scenario3() {
        assertTrue(false);
    }
}

执行结果

可以看到运行的结果,根据case1、case2…case5 自动排序

3.6 Rule

JUnit4中包含两个注解@Rule和@ClassRule用于修饰Field或返回Rule的 Method,Rule是一组实现了TestRule接口的共享类,提供了验证、监视TestCase和外部资源管理等能力。JUnit提供了以下几个Rule实现,必要时也可以自己实现Rule。
Verifier: 验证测试执行结果的正确性。
ErrorCollector: 收集测试方法中出现的错误信息,测试不会中断,如果有错误发生测试结束后会标记失败。
ExpectedException: 提供灵活的异常验证功能。
Timeout: 用于测试超时的Rule。
ExternalResource: 外部资源管理。
TemporaryFolder: 在JUnit的测试执行前后,创建和删除新的临时目录。
TestWatcher: 监视测试方法生命周期的各个阶段。
TestName: 在测试方法执行过程中提供获取测试名字的能力。
简单的说就是提供了测试用例执行过程中一些通用功能的共享的能力,使我们不必重复编写一些功能类似的代码。JUnit用于标注Rule的注解包括@Rule和@ClassRule,区别在于作用域不同@Rule的作用域是测试方法,@ClassRule则是测试Class。

自定义Rule实例:
自定义一个rule, 实现循环执行一个方法,代码如下:

import java.util.Arrays;
import org.junit.rules.MethodRule;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;

class RepeatableRule implements MethodRule {
    int times = 1;
    String[] testMethods = null;

    RepeatableRule(int times, String[] testMethods) {
        this.times = times;
        this.testMethods = testMethods;
    }

    @Override
    public Statement apply(final Statement base, final FrameworkMethod method, Object target) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                int loopTime = 1;
                if (Arrays.asList(testMethods).contains(method.getName())) {
                    loopTime = times;
                }
                for (int i = 0; i < loopTime; i++)
                    base.evaluate();
            }
        };
    }
}

调用自定义的rule:

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.MethodRule;

public class TestCase {

    @Rule
    public MethodRule rule = new RepeatableRule(5, new String[] { "test1" });

    @Before
    public void setUp() throws Exception {
    }

    @After
    public void tearDown() throws Exception {
    }

    @Test
    public void test() {
        System.out.println("test");
    }

    @Test
    public void test1() {
        System.out.println("test1");
    }

}

 执行用例可以看到用例 test1被执行了5次 。

3.7 Junit4与TestNG比较

TestNG按照官方的定义:
TestNG是一个测试框架,其灵感来自JUnit和NUnit,但引入了一些新的功能,使其功能更强大,使用更方便。
TestNG是一个开源自动化测试框架;TestNG表示下一代(Next Generation的首字母)。 TestNG类似于JUnit(特别是JUnit 4),但它不是JUnit框架的扩展。它的灵感来源于JUnit。它的目的是优于JUnit,尤其是在用于测试集成多类时。 TestNG的创始人是Cedric 
Beust
(塞德里克·博伊斯特)。
TestNG消除了大部分的旧框架的限制,使开发人员能够编写更加灵活和强大的测试。 因为它在很大程度上借鉴了Java注解(JDK5.0引入的)来定义测试,它也可以显示如何使用这个新功能在真实的Java语言生产环境中。
官网:http://testng.org/doc/

TestNG与JUnit的相同点:
1. 使用annotation,且大部分annotation相同。
2. 都可以进行单元测试(Unit test)。
3. 都是针对Java测试的工具。

TestNG与JUnit的不同点:
1. JUnit只能进行单元测试,TestNG可以进行单元测试,功能测试,端到端测试,集成测试等。
2. TestNG需要一个额外的xml配置文件,配置测试的class、method甚至package。
3. TestNG的运行方式更加灵活:命令行、ant和IDE,JUnit只能使用IDE。
4. TestNG的annotation更加丰富,比如@ExpectedExceptions、@DataProvider等。
5. 测试套件运行失败,JUnit4会重新运行整个测试套件。TestNG运行失败时,会创建一个XML文件说明失败的测试,利用这个文件执行程序,就不会重复运行已经成功的测试。

TestNG比JUnit4灵活性的体现:
1. JUnit4中必须把@BeforeClass修饰的方法声明为public static,这就限制了该方法中使用的变量必须是static。而TestNG中@BeforeClass修饰的方法可以跟普通函数完全一样。
2. JUnit4测试的依赖性非常强,测试用例间有严格的先后顺序。前一个测试不成功,后续所有的依赖测试都会失败。TestNG 利用@Test 的dependsOnMethods属性来应对测试依赖性问题。某方法依赖的方法失败,它将被跳过,而不是标记为失败。
3. 对于n个不同参数组合的测试,JUnit4要写n个测试用例。每个测试用例完成的任务基本是相同的,只是受测方法的参数有所改变。TestNG的参数化测试只需要一个测试用例,然后把所需要的参数加到TestNG的xml配置文件中。这样的好处是参数与测试代码分离,非程序员也可以修改参数,同时修改无需重新编译测试代码。
4. 为了测试无法用String或原语值表示的复杂参数化类型,TestNG提供的@DataProvider使它们映射到某个测试方法。
5. JUnit4的测试结果通过Green/Red bar体现,TestNG的结果除了Green/Red bar,还有Console窗口和test-output文件夹,对测试结果的描述更加详细,方便定位错误。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值