暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

单元测试之Junit功能介绍

JAVA的学习之路 2021-02-20
1061

一.注解

与JUnit3不同,JUnit4通过注解的方式来识别测试方法。目前支持的主要注解有:
1、@BeforeClass 全局只会执行一次,而且是第一个运行
2、@Before 在测试方法运行之前运行
3、@Test 测试方法
4、
@After 在测试方法运行之后允许

5、@AfterClass 全局只会执行一次,而且是最后一个运行
6、@Ignore 忽略此方法


二.Assert

Junit3和Junit4都提供了一个Assert类(虽然package不同,但是大致差不多)。Assert类中定义了很多静态方法来进行断言。列表如下:
1、assertTrue(String message, boolean condition) 要求condition == true
2、assertFalse(String message, boolean condition) 要求condition == false
3、fail(String message) 必然失败,同样要求代码不可达
4、assertEquals(String message, XXX expected,XXX actual) 要求expected.equals(actual)
5、assertArrayEquals(String message, XXX[] expecteds,XXX [] actuals) 要求expected.equalsArray(actual)
6、assertNotNull(String message, Object object) 要求object!=null
7、assertNull(String message, Object object) 要求object==null
8、assertSame(String message, Object expected, Object actual) 要求expected == actual
9、assertNotSame(String message, Object unexpected,Object actual) 要求expected != actual
10assertThat(String reason, T actual, Matcher matcher) 要求matcher.matches(actual) == true


三.Rule

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


四.Assume

Assume直译为假设,是JUnit提供的一套用于判断测试用例的入参是否有业务含义的工具,如果入参不符合预期时会抛出AssumptionViolatedException,默认的BlockJUnit4ClassRunner及其子类会捕获这个异常并跳过当前测试,如果使用自定义的Runner则无法保证行为,视Runner的实现而定。 
Assume提供的验证方法包括:assumeTrue/assumeFalse、 assumeNotNull、 assumeThat、 assumeNoException 。具体含义都比较简单。假设我们有一个打印姓名和年龄的测试用例,使用Theories提供多套参数测试,由于实际年龄不存在负数所以使用Assume排除不合理的数据:

@RunWith(Theories.class)
public class AssumeTest{


@DataPoints
public static String[] names = {"LiLei", "HanMeiMei"};


@DataPoints
public static int[] ages = {10, -2, 12};


@Theory
    public void printAge(String name, int age){
Assume.assumeTrue(age > 0);
System.out.println(String.format("%s's Name is %s.", name, age));
   }
}



打印结果中排除了负数的年龄,并且JUnit测试通过:

LiLei's Name is 10.
LiLei's Name is 12.
HanMeiMei's Name is 10.


五.其他注解功能

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

@RunWith(Suite.class)
@SuiteClasses({ComplexFunctionTest.class, SimpleFunctionTest.class})
public class TestSuitMain{


}

在执行TestSuitMain --> “Run As JUnit Test"的时候会把ComplexFunctionTest和SimpleFunctionTest的用例全部执行一遍。
Parameterized:Parameterized继承自Suit,从这个身世和名字应该可以猜到一些因果了。Parameterized是在参数上实现了Suit——修饰一个测试类,但是可以提供多组构造函数的参数用于测试不同场景。略微有点抽象,用代码说话:



@RunWith(Parameterized.class)
public class TestGenerateParams{


private String greeting;


    public TestGenerateParams(String greeting){
super();
           this.greeting = greeting;
}


@Test
    public void testParams(){
System.out.println(greeting);
}


    /**
* 这里的返回至少是二维数组
* @return
*/
@Parameters
    public static List<String[]> getParams(){
        return Arrays.asList(new String[][]{{"hello"}, 
{"hi"},
{"good morning"},
{"how are you"}});
}
}



输出结果:

hello
hi
good morning


在这个用例里,我们首先需要用@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的测试,这不会影响原有用例测运行。



@Category(AttributeFun.class)
@Test
public void testGetAge(){
int age = person.getAge();
assertEquals(3, age);
}


@Category(AttributeFun.class)
@Test
public void testGetName(){
String name = person.getName();
assertEquals("Willard", name);
}


@Category(BehaviorFun.class)
@Test
public void testTalk(){
String message = person.talkTo("Jimy");
assertNotNull(message);
}


@Category(BehaviorFun.class)
@Test(timeout=200)
public void testWalk(){
person.walk();
}


接下来编写我们的Category测试类,代码如下:



@RunWith(Categories.class)
@SuiteClasses(PersonTest.class)
public class CategoryTest{


}


Runner选用Categories,SuitClass使用PersonTest.class,这时Categories与Suit拥有完全一致的效果。
Theories:意为原理或者推测的意思,我觉得这里应该是取推测。Theories继承自BlockJUnit4ClassRunner,提供了除Parameterized之外的另一种参数测试解决方案——似乎更强大。Theories不再需要使用带有参数的Constructor而是接受有参的测试方法,修饰的注解也从@Test变成了@Theory,而参数的提供则变成了使用@DataPoint或者@Datapoints来修饰的变量,两者的唯一不同是前者代表一个数据后者代表一组数据。Theories会尝试所有类型匹配的参数作为测试方法的入参(有点排列组合的意思)。看一个使用Theories的例子:

@RunWith(Theories.class)
public class TheoriesTest{


@DataPoint
public static String nameValue1 = "Tony";


@DataPoint
public static String nameValue2 = "Jim";


@DataPoint
public static int ageValue1 = 10;


@DataPoint
public static int ageValue2 = 20;


@Theory
    public void testMethod(String name, int age){
System.out.println(String.format("%s's age is %s", name, age));
}
}


上面的例子打印结果如下:

Tony's age is 10
Tony's age is 20
Jim's age is 10
Jim's age is 20

同样使用@DataPoints可以获得一样的效果:



@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 method1) ->@After -> if existed @Rule, then Statement.evaluate() -> @Rule(TestRule.apply()) -> @Before -> @Test(test method2) -> @After -> if existed @Rule, then Statement.evaluate() … -> @AfterClass -> if existed @ClassRule, then Statement.evaluate()
通过Statement.evaluate()执行他们的方法实体,最终执行测试方法的主体。




- THE END -

作者简介

Mr.W

白天搬砖,晚上砌梦想。

相信每个人有故事,程序员更是有许多事故,书写最接地气的程序员故事,为大家找出更好的资料。



文章转载自JAVA的学习之路,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论