写点什么

原创 | TDD 工具集:JUnit、AssertJ 和 Mockito (十八) 编写测试 - 测试执行顺序\嵌套的测试

发布于: 2020 年 06 月 05 日
原创 | TDD工具集:JUnit、AssertJ和Mockito (十八)编写测试-测试执行顺序\嵌套的测试







本文分享在编写测试中“测试执行顺序、嵌套的测试”两节内容的方法。

测试执行顺序



重要性:★★★☆☆



如果一个测试类中有多个测试方法,缺省情况下,虽然JUnit会根据一个内在的算法确定这些方法的执行顺序(为了支持“可重复构建”的目标),但是对用户来说,其顺序并不是明显的——测试方法的名称和在测试类中出现的顺序都不是排序的依据。



正常情况下测试类中的每一个测试方法都应该是独立的——它的执行与否以及测试结果都不应该依赖于其他测试方法的预先执行。一个测试方法不应该依赖另一个测试方法为它预设测试条件(例如订单CRUD测试中,查询订单测试不应该依赖于创建订单测试为它先在数据库中创建订单,而应该在自身的setup阶段向数据库中插入测试数据)。



但是有的时候,尤其是在集成测试中,因为要与外部系统如数据库等进行交互,测试类中的每个测试方法在setup阶段都要启动数据库并向其中插入数据,就会导致严重的性能问题。这个时候我们可以接受安排测试方法的执行顺序,例如在订单CRUD测试中,安排创建订单测试先执行,向数据库中插入一到多条订单数据,随后执行查询订单测试和修改订单测试,最后执行删除订单测试。



当需要访问外部基础设施(如数据库、消息中间件等等)或者采用PER_CLASS模式执行测试时,定义测试方法的执行顺序比较有意义。



为了定义测试执行顺序,你需要:



  • @TestMethodOrder注解测试类或测试接口,指定一个MethodOrderer接口的实现类。

  • 如果采用MethodOrdererMethodOrderer.OrderAnnotation类,在每个要排序的测试方法上添加@Order注解。@Order的值越小,方法越先执行。



下面的代码示例根据@Order注解进行排序:



package yang.yu.tdd.exec_order;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class MethodOrderTest {
@Test
@Order(20)
void last() {
System.out.println("Me last");
}
@Test
@Order(-6)
void first() {
System.out.println("Me first");
}
@Test
@Order(0)
void second() {
System.out.println("Me second");
}
@Test
@Order(2)
void third() {
System.out.println("Me third");
}
}



还可以根据方法名称的字母表顺序进行排序:



package yang.yu.tdd.exec_order;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
@TestMethodOrder(MethodOrderer.Alphanumeric.class)
class AlphanumericOrderTest {
@Test
void last() {
System.out.println("Named last but second in fact");
}
@Test
void first() {
System.out.println("Me first");
}
@Test
void second() {
System.out.println("Named second but third in fact");
}
@Test
void third() {
System.out.println("Named third but last in fact");
}
}



还可以通过将测试类注解为:



@TestMethodOrder(MethodOrderer.Random.class)



来让测试方法伪随机排序执行。不过通常而言这种方式没啥必要。



嵌套的测试



重要性:★★★☆☆



有时候一个测试类中的测试方法太多,有必要根据内聚性原则将它们分组。



可以在测试类的内部定义嵌套内类(不能是静态内类)并注解为@Nested将测试进行分组。在顶层类和嵌套类中都可以定义测试方法和生命周期方法。



嵌套的层级数量没有限制。



如果测试生命周期是PER_METHOD的,嵌套类不允许定义@BeforeAll@AfterAll生命周期方法。如果测试生命周期是PER_CLASS的,嵌套类中可以定义@BeforeAll@AfterAll生命周期方法。



如果外层测试类中有定义@BeforeEach@AfterEach生命周期方法,那么它们也会在嵌套类的每个测试方法前后执行一次。



示例代码如下:



package yang.yu.tdd.nested;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.EmptyStackException;
import java.util.Stack;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@DisplayName("A stack")
class TestingAStackDemo {
Stack<Object> stack;
@Test
@DisplayName("is instantiated with new Stack()")
void isInstantiatedWithNew() {
new Stack<>();
}
@Nested
@DisplayName("when new")
class WhenNew {
@BeforeEach
void createNewStack() {
stack = new Stack<>();
}
@Test
@DisplayName("is empty")
void isEmpty() {
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("throws EmptyStackException when popped")
void throwsExceptionWhenPopped() {
assertThrows(EmptyStackException.class, stack::pop);
}
@Test
@DisplayName("throws EmptyStackException when peeked")
void throwsExceptionWhenPeeked() {
assertThrows(EmptyStackException.class, stack::peek);
}
@Nested
@DisplayName("after pushing an element")
class AfterPushing {
String anElement = "an element";
@BeforeEach
void pushAnElement() {
stack.push(anElement);
}
@Test
@DisplayName("it is no longer empty")
void isNotEmpty() {
assertFalse(stack.isEmpty());
}
@Test
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPopped() {
assertEquals(anElement, stack.pop());
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("returns the element when peeked but remains not empty")
void returnElementWhenPeeked() {
assertEquals(anElement, stack.peek());
assertFalse(stack.isEmpty());
}
}
}
}



执行测试结果显示如下,可见输出也是层级化的:





一种可行的组织测试代码的方式是:为每个被测试的产品类创建一个对应的测试类。在测试类内部,针对被测类的每个工作单元(一般是方法)分别创建一个注解为@Nested的嵌套类,包含针对这个工作单元的所有测试。



这一节就讲到这里,下一节我们讲讲"编写测试中的依赖注入、测试接口以及重复测试"







发布于: 2020 年 06 月 05 日阅读数: 63
用户头像

高级架构师,技术顾问,交流公号:编程道与术 2020.04.28 加入

杨宇于2020年创立编程道与术,致力于研究领域分析与建模、测试驱动开发、架构设计、自动化构建和持续集成、敏捷开发方法论、微服务、云计算等顶尖技术领域。 了解更多公众号:编程道与术

评论

发布
暂无评论
原创 | TDD工具集:JUnit、AssertJ和Mockito (十八)编写测试-测试执行顺序\嵌套的测试