51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 1859|回复: 1
打印 上一主题 下一主题

[讨论] 单元测试之Junit功能介绍

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2018-4-2 13:33:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
一.注解
与JUnit3不同,JUnit4通过注解的方式来识别测试方法。目前支持的主要注解有:
@BeforeClass 全局只会执行一次,而且是第一个运行
@Before 在测试方法运行之前运行
@Test 测试方法
@After 在测试方法运行之后允许
@AfterClass 全局只会执行一次,而且是最后一个运行
@Ignore 忽略此方法
二.Assert
Junit3和Junit4都提供了一个Assert类(虽然package不同,但是大致差不多)。Assert类中定义了很多静态方法
来进行断言。列表如下:

assertTrue(String message, boolean condition) 要求condition == true
assertFalse(String message, boolean condition) 要求condition == false
fail(String message) 必然失败,同样要求代码不可达
assertEquals(String message, XXX expected,XXX actual) 要求expected.equals(actual)
assertArrayEquals(String message, XXX[] expecteds,XXX [] actuals) 要求expected.equalsArray(actual)
assertNotNull(String message, Object object) 要求object!=null
assertNull(String message, Object object) 要求object==null
assertSame(String message, Object expected, Object actual) 要求expected == actual
assertNotSame(String message, Object unexpected,Object actual) 要求expected != actual
assertThat(String reason, T actual, Matcher matcher) 要求matcher.matches(actual) == true

三.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。

四.Assume
Assume直译为假设,是JUnit提供的一套用于判断测试用例的入参是否有业务含义的工具,如果入参不符合预
期时会抛出AssumptionViolatedException,默认的BlockJUnit4ClassRunner及其子类会捕获这个异常并跳过当
前测试,如果使用自定义的Runner则无法保证行为,视Runner的实现而定。
Assume提供的验证方法包括: assumeTrue/assumeFalse、 assumeNotNull、 assumeThat、 assumeNoExcept
ion 。具体含义都比较简单。假设我们有一个打印姓名和年龄的测试用例,使用Theories提供多套参数测试,
由于实际年龄不存在负数所以使用Assume排除不合理的数据:
[java] view plain copy
@RunWith(Theories.class)  
  1. public class AssumeTest  
  2. {  
  3.    
  4.     @DataPoints  
  5.     public static String[] names = {"LiLei", "HanMeiMei"};  
  6.       
  7.     @DataPoints  
  8.     public static int[] ages = {10, -2, 12};  
  9.       
  10.     @Theory  
  11.     public void printAge(String name, int age)  
  12.     {  
  13.         Assume.assumeTrue(age > 0);  
  14.         System.out.println(String.format("%s's Name is %s.", name, age));  
  15.     }  
  16. }  
  17. 打印结果中排除了负数的年龄,并且JUnit测试通过:
  18. [plain] view plain copy
  19. LiLei's Name is 10.  
  20. LiLei's Name is 12.  
  21. HanMeiMei's Name is 10.  
  22. HanMeiMei's Name is 12.  
复制代码

五.其他注解功能
1.@RunWith
Runner:Runner是一个抽象类,是JUnit的核心组成部分。用于运行测试和通知Notifier运行的结果。JUnit使
用@RunWith注解标注选用的Runner,由此实现不同测试行为。
BlockJUnit4ClassRunner:这个是JUnit的默认Runner,平时我们编写的JUnit不添加@RunWith注解时使用的
都是这个Runner。
Suit:没错,Suit就是个Runner!用来执行分布在多个类中的测试用例,比如我存在SimpleFunctionTest和Co
mplexFunctionTest类分别测试Person的简单和复杂行为,在茫茫的测试用例中如何一次执行所有与Person有
关的测试呢——使用Suit。代码如下:其中ComplexFunctionTest和SimpleFunctionTest就是两个普通的测试
用例类,这里忽略。
[java] view plain copy
@RunWith(Suite.class)  
@SuiteClasses({ComplexFunctionTest.class, SimpleFunctionTest.class})  
public class TestSuitMain  
{  

}  
在执行TestSuitMain --> “Run As JUnit Test"的时候会把ComplexFunctionTest和SimpleFunctionTest的用例全
部执行一遍。
Parameterized:Parameterized继承自Suit,从这个身世和名字应该可以猜到一些因果了。Parameterized是在
参数上实现了Suit——修饰一个测试类,但是可以提供多组构造函数的参数用于测试不同场景。略微有点抽
象,用代码说话:
[java] view plain copy
@RunWith(Parameterized.class)  
  1. public class TestGenerateParams  
  2. {  
  3.       
  4.     private String greeting;  
  5.       
  6.     public TestGenerateParams(String greeting)  
  7.     {  
  8.         super();  
  9.         this.greeting = greeting;  
  10.     }  
  11.    
  12.     @Test  
  13.     public void testParams()  
  14.     {  
  15.         System.out.println(greeting);  
  16.     }  
  17.       
  18.     /**
  19.      * 这里的返回至少是二维数组
  20.      * @return
  21.      */  
  22.     @Parameters  
  23.     public static List<String[]> getParams()  
  24.     {  
  25.         return  
  26.                 Arrays.asList(new String[][]{{"hello"},   
  27.                         {"hi"},   
  28.                         {"good morning"},  
  29.                         {"how are you"}});  
  30.     }  
  31. }  
  32. 输出结果:
  33. [plain] view plain copy
  34. hello  
  35. hi  
  36. good morning  
  37. how are you  
复制代码

在这个用例里,我们首先需要用@RunWith(Parameterized.class)来修饰我们的测试类;接下来提供一组参数,
还记得JUnit的生命周期吗?在每次运行测试方法的时候都会调用Constructor来创建一个实例,这里参数就是
通过Constructor的参数传入的。因此如你所见我们需要一个含有参数的构造函数用于接收参数,这个参数需
要用于跑测试用例所以把它保存做类的变量;然后用@Parameters修饰我们提供参数的静态方法,它需要返回
List<Object[]>,List包含的是参数组,Object[]即按顺序提供的一组参数。
Category:Category同样继承自Suit,Category似乎是Suit的加强版,它和Suit一样提供了将若干测试用例类
组织成一组的能力,除此以外它可以对各个测试用例进行分组,使你有机会只选择需要的部分用例。举个例
子Person有获取age和name的方法也有talk和walk方法,前者用于获取属性后者是Person的行为,Category使
我们可以只运行属性测试,反之亦然。
首先修改最初的测试用例PersonTest,添加Category信息,代码如下在每个用例上添加了@Category信息标识
它们是用作Attribute还是Behavior的测试,这不会影响原有用例测运行。
[java] view plain copy
@Category(AttributeFun.class)  
@Test  
  1. public void testGetAge()  
  2. {  
  3.     int age = person.getAge();  
  4.     assertEquals(3, age);  
  5. }  
  6.    
  7. @Category(AttributeFun.class)  
  8. @Test  
  9. public void testGetName()  
  10. {  
  11.     String name = person.getName();  
  12.     assertEquals("Willard", name);  
  13. }  
  14.    
  15. @Category(BehaviorFun.class)  
  16. @Test  
  17. public void testTalk()  
  18. {  
  19.     String message = person.talkTo("Jimy");  
  20.     assertNotNull(message);  
  21. }  
  22.    
  23. @Category(BehaviorFun.class)  
  24. @Test(timeout=200)  
  25. public void testWalk()  
  26. {  
  27.     person.walk();  
  28. }  
  29. 接下来编写我们的Category测试类,代码如下:
  30. [java] view plain copy
  31. @RunWith(Categories.class)  
  32. @SuiteClasses(PersonTest.class)  
  33. public class CategoryTest  
  34. {  
  35.    
  36. }  
复制代码

Runner选用Categories,SuitClass使用PersonTest.class,这时Categories与Suit拥有完全一致的效果。
Theories:意为原理或者推测的意思,我觉得这里应该是取推测。Theories继承自BlockJUnit4ClassRunner,
提供了除Parameterized之外的另一种参数测试解决方案——似乎更强大。Theories不再需要使用带有参数的
Constructor而是接受有参的测试方法,修饰的注解也从@Test变成了@Theory,而参数的提供则变成了使
用@DataPoint或者@Datapoints来修饰的变量,两者的唯一不同是前者代表一个数据后者代表一组数据。T
heories会尝试所有类型匹配的参数作为测试方法的入参(有点排列组合的意思)。看一个使用Theories的
例子:
[java] view plain copy
@RunWith(Theories.class)  
  1. public class TheoriesTest  
  2. {  
  3.    
  4.     @DataPoint  
  5.     public static String nameValue1 = "Tony";  
  6.       
  7.     @DataPoint  
  8.     public static String nameValue2 = "Jim";  
  9.       
  10.     @DataPoint  
  11.     public static int ageValue1 = 10;  
  12.       
  13.     @DataPoint  
  14.     public static int ageValue2 = 20;  
  15.       
  16.     @Theory  
  17.     public void testMethod(String name, int age)  
  18.     {  
  19.         System.out.println(String.format("%s's age is %s", name, age));  
  20.     }  
  21. }  
  22. 上面的例子打印结果如下:
  23. [plain] view plain copy
  24. Tony's age is 10  
  25. Tony's age is 20  
  26. Jim's age is 10  
  27. Jim's age is 20  
复制代码

同样使用@DataPoints可以获得一样的效果:
[java] view plain copy
@RunWith(Theories.class)  
public class TheoriesTest  
{  

    @DataPoints  
    public static String[] names = {"Tony", "Jim"};  

    @DataPoints  
    public static int[] ageValue1 = {10, 20};  

    @Theory  
    public void testMethod(String name, int age)  
    {  
        System.out.println(String.format("%s's age is %s", name, age));  
    }  
}  
注:执行顺序
1. 获取元数据信息的顺序
@BeforeClass -> @AfterClass -> ClassRule -> @Test(拿元数据里的expect Exception) -> @Test(拿元数据里
的timeout信息) -> @Before -> @After -> @Rule,
2. 注解所标注的方法执行顺序
@ClassRule(TestRule.apply()) -> @BeforeClass -> @Rule(TestRule.apply())  -> @Before -> @Test(test meth
od1) ->@After -> if existed @Rule, then Statement.evaluate() -> @Rule(TestRule.apply()) -> @Before -> @
Test(test method2) -> @After -> if existed @Rule, then Statement.evaluate() … -> @AfterClass -> if existe
d @ClassRule, then Statement.evaluate()
通过Statement.evaluate()执行他们的方法实体,最终执行测试方法的主体。

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏
回复

使用道具 举报

本版积分规则

关闭

站长推荐上一条 /1 下一条

小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

GMT+8, 2024-11-25 15:52 , Processed in 0.060065 second(s), 22 queries .

Powered by Discuz! X3.2

© 2001-2024 Comsenz Inc.

快速回复 返回顶部 返回列表