JUnit 5中的测试执行顺序

一般实践认为,自动化测试应能够独立运行且无特定顺序,并且测试结果不应依赖于先前测试的结果。 但是在某些情况下,可以证明特定的测试执行顺序是正确的,尤其是在集成或端到端测试中。

默认情况下,在JUnit 5中,测试方法的执行在构建之间是可重复的,因此具有确定性,但是该算法是故意不明显的(作为库状态的作者)。 幸运的是,可以使用内置方法定购器或通过创建自定义定购器来调整执行顺序以满足我们的需求。

org.junit.jupiter.api.TestMethodOrder

为了更改测试执行顺序,我们需要使用org.junit.jupiter.api.TestMethodOrder注释测试类,并将方法排序器的类型作为参数传递。 从JUnit 5.4开始,有三个内置的方法排序器: OrderAnnotation , Alphanumeric和Random 。 通过实现org.junit.jupiter.api.MethodOrderer接口,我们还可以轻松创建自己的自定义方法org.junit.jupiter.api.MethodOrderer器。

MethodOrderer.OrderAnnotation使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package pl.codeleak.samples.junit5.basics;  
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 TestExecutionOrderWithOrderAnnotation {
@Order ( 1 )
@Test
void aTest() {}
@Order ( 2 )
@Test
void bTest() {}
@Order ( 3 )
@Test
void cTest() {} }

MethodOrderer.Alphanumeric

1
2
3
4
5
6
7
8
@TestMethodOrder (MethodOrderer.Alphanumeric. class )  
class AlphanumericTestExecutionOrder {
@Test
void aTest() {}
@Test
void bTest() {}
@Test
void cTest() {} }

MethodOrderer.Random

1
2
3
4
5
6
7
@TestMethodOrder (MethodOrderer.Random. class )  class AlphanumericTestExecutionOrder { 
@Test
void aTest() {}
@Test
void bTest() {}
@Test
void cTest() {} }

Mokito RestTemplate的exchange 例子

  • RestTemplate 的exchange 方法
    1
    2
    public <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException {

    若mokito 不对,因为RestTemplate 中exchange 上面方法重载有4个

RestTemplate 中exchange重载方法
就会出现以下错误

1
2
3
getting error like org.mockito.exceptions.misusing.InvalidUseOfMatchersException: 
Invalid use of argument matchers!
4 matchers expected, 3 recorded:
  • Mokito 例子
    1
    2
    3
    4
    5
    Mockito.doReturn(ResponseEntity.ok(new Object )).when(restTemplate)
    .exchange(Mockito.anyString(),Mockito.eq(HttpMethod.GET),
    (org.springframework.http.HttpEntity<?>) Mockito.any(),
    (Class<Object>) Mockito.any());

junit5 + spring boot

  • 代码SpringJunitTest5 分支main,jdk11
  • unit test code
    junit5与junit 4不同
    spring boot整合junit5 test
    junit5要使用@SpringJUnitConfig和@SpringBootTest 注解
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    @SpringJUnitConfig
    @SpringBootTest(classes = SpringJunitTest5Application.class)
    @TestInstance(TestInstance.Lifecycle.PER_CLASS)
    public class HelloControllerTest {

    protected MockMvc mockMvc;

    @Autowired
    protected WebApplicationContext webApplicationContext;

    @BeforeEach
    public void setup() throws Exception {
    Assert.notNull(webApplicationContext, "'webApplicationContext' must not be null");
    MockitoAnnotations.openMocks(this);
    this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }
    @Test
    public void hello() throws Exception {
    MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get("/hello")
    .contentType(MediaType.APPLICATION_FORM_URLENCODED)
    .param("name","world");
    ResultActions resultActions = mockMvc.perform(requestBuilder);
    resultActions.andDo(MockMvcResultHandlers.print());
    resultActions.andExpect(status().is(200));
    resultActions.andExpect(content().string("hello world"));

    }
    }

关于TestInstance.Lifecycle.PER_CLASS

为了允许隔离执行单个的测试方法,并避免由于可变测试实例状态而产生的意外副作用,JUnit在执行每个测试方法之前创建每个测试类的新实例 只需使用@TestInstance(Lifecycle.PER_CLASS)对您的测试类进行注解即可。当使用这种模式时,每个测试类将创建一个新的测试实例。因此,如果您的测试方法依赖于存储在实例变量中的状态,则可能需要在@BeforeEach或@AfterEach方法中重置该状态。

如果测试类或测试接口没有用@TestInstance注解,JUnit Jupiter将使用默认的生命周期模式。标准默认模式是PER_METHOD

验证插入数据库的对象数据是否正确

  • 测试的目的:
    程序是否调用了一次HelloDaoImpl的hello 方法
    并验证传入参数值是否是world?
  • dao 类
    1
    2
    3
    4
    5
    6
    public class HelloDaoImpl implements IHelloDao {
    @Override
    public String hello(String name) {
    return name;
    }
    }
  • service 类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Service
    public class HelloServiceImpl implements IHelloService {
    @Autowired
    IHelloDao helloDao;
    @Override
    public String hello(String name) {
    helloDao.hello(name);
    return name;
    }
    }
  • test verify data 类
    实现VerificationMode类的verify 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class HelloServiceVerification implements VerificationMode {
private String name;
final int wantedCount;
public HelloServiceVerification(int wantedNumberOfInvocations,String name) {
this.wantedCount=wantedNumberOfInvocations;
this.name=name;
}

public void verify(VerificationData data) {
   /** 程序是否没有调用HelloDaoImpl的hello 方法
   若
   没有调用则抛出异常
*/
       if (wantedCount > 0) {
MissingInvocationChecker.checkMissingInvocation(data.getAllInvocations(),data.getTarget());
}
List<Invocation> invocations=data.getAllInvocations();
if(invocations!=null) {
for (Invocation invocation : invocations) {
Object[] args = invocation.getArguments();
if(data.getTarget().hasSameMethod(invocation)) {
System.out.println("invocation.getMethod():"+invocation.getMethod());
if (args != null) {
for (Object arg : args) {
//验证传入参数
this.verifyData(arg);
}
}
}
}
}

//程序是否调用HelloDaoImpl的hello 方法 ,且    调用次数只有1次 NumberOfInvocationsChecker.checkNumberOfInvocations(data.getAllInvocations(),data.getTarget(),wantedCount);
}

// 验证参数是否正确
  public void verifyData(Object args) {
System.out.println("args="+args);
Assert.notNull(args, "args must not be null");
Assertions.assertEquals(name,args);
}
}
  • unit test类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Test
    public void hello() throws Exception {
    MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get("/hello")
    .contentType(MediaType.APPLICATION_FORM_URLENCODED)
    .param("name","world");
    ResultActions resultActions = mockMvc.perform(requestBuilder);
    resultActions.andDo(MockMvcResultHandlers.print());
    resultActions.andExpect(status().is(200));
    resultActions.andExpect(content().string("hello world"));
    Mockito.verify(helloDao, new HelloServiceVerification(1, "world")).hello(Mockito.any(String.class));
    }

junit4 +spring boot

  • 代码SpringbootJunit4 分支main,jdk8
  • unit test code
    spring boot整合junit4 test
    要使用@RunWith(SpringJUnit4ClassRunner.class)
    @SpringBootTest注解
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringBootTest(classes = SpringbootJunit4Application.class)
    public class HelloWorldControllerTest {

    protected MockMvc mockMvc;

    @Autowired
    protected WebApplicationContext webApplicationContext;

    @Before
    public void setup() throws Exception {
    Assert.notNull(webApplicationContext, "'webApplicationContext' must not be null");
    MockitoAnnotations.initMocks(this);
    this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }
    @Test
    public void hello() throws Exception {
    MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get("/hello")
    .contentType(MediaType.APPLICATION_FORM_URLENCODED)
    .param("name", "world");
    ResultActions resultActions = mockMvc.perform(requestBuilder);
    resultActions.andDo(MockMvcResultHandlers.print());
    resultActions.andExpect(status().is(200));
    resultActions.andExpect(content().string("hello world"));
    }
    }

Spring+junit 4

  • controller code ,jdk8
    1
    2
    3
    4
    5
    6
    7
    8
    @Controller
    public class HelloController {
    @GetMapping("/hello")
    public @ResponseBody String hello(String name){
    return "hello "+name;
    }
    }

  • test code
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    @RunWith(SpringJUnit4ClassRunner.class)
    @WebAppConfiguration
    @ContextConfiguration(locations ={"classpath*:/application-context.xml"})
    public class HelloControllerTest {
    @Autowired
    protected WebApplicationContext webApplicationContext;
    protected MockMvc mockMvc;
    @Before
    public void setup() throws Exception {
    Assert.notNull(webApplicationContext, "'webApplicationContext' must not be null");
    MockitoAnnotations.initMocks(this);
    this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
    .build();
    }
    @Test
    public void hello() throws Exception {
    MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get("/hello")
    .contentType(MediaType.APPLICATION_FORM_URLENCODED)
    .param("name", "world");
    ResultActions resultActions = mockMvc.perform(requestBuilder);
    resultActions.andDo(MockMvcResultHandlers.print());
    resultActions.andExpect(status().is(200));
    resultActions.andExpect(content().string("hello world"));
    }
    }