Junit5 集成测试及与单元测试区别

Junit5集成测试

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

  • JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。

  • JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行。

  • JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎。

在这里插入图片描述


需要的jar (以下使用离线gradle进行管理的,如果是有网络下的maven包的导入简单很多)

  • junit-jupiter-5.6.2.jar
  • junit-jupiter-api-5.6.2.jar
  • junit-jupiter-engine-5.6.2.jar
  • junit-jupiter-params-5.6.2.jar
  • junit-platform-launcher-1.6.2.jar
  • junit-platform-runner-1.6.2.jar
  • junit-vintage-engine-5.3.2.jar
  • mockito-core-3.9.0.jar

注意事项
jar包版本很容易出现问题,不一定是哪两个jar包之间的不兼容问题,目前我使用这些jar包的组合没有问题

所有的jar包必须放在与模块同级libs下 ,并在build.gradle中填写相应的依赖信息


基本注解

@Test : 表示方法是测试方法。注意一定要 import org.junit.jupiter.api.Test;

@BeforeEach : 表示在每个单元测试之前执行,多用于初始化

@BeforeEach
void setUp(){
     //init somethings....
     mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
}

@BeforeAll : 表示在所有单元测试之前执行,作用同BeforeEach

@AfterEach : 表示在每个单元测试之后执行

@AfterEach
public void cleanUpEach(){
    //destory & clean somethings
	System.out.println("After Each cleanUpEach() method called");
}

@AfterAll : 表示在所有单元测试之后执行

@Disabled : 表示测试类或测试方法不执行,类似于JUnit4中的@Ignore

@Timeout : 表示测试方法运行如果超过了指定时间将会返回错误

@ExtendWith : 为测试类或测试方法提供扩展类引用


断言

JUnit5 使用了新的断言类:org.junit.jupiter.api.Assertions

1.断言异常Junit5支持Lambda表达式

@Test
@DisplayName("异常测试")
public void exceptionTest() {
    ArithmeticException exception = Assertions.assertThrows(
    //扔出断言异常
    ArithmeticException.class, () -> System.out.println(1 % 0));
}

2. 超时断言

@Test
@DisplayName("超时测试")
public void timeoutTest() {
    //如果测试方法时间超过1s将会异常
    Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}

3.参数化测试

参数化测试使使用不同参数多次运行测试成为可能。它们与常规的@Test方法一样被声明,但是使用 @ParameterizedTest注释。

@ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型

@NullSource: 表示为参数化测试提供一个null的入参

@ParameterizedTest
@ValueSource(strings = { "123", "345", "qwer" })
void palindromes(String str) {
    Assertions.assertEquals(String,class,str.getClass());
}
//以上测试结果均为真

4.其余必需参数

@WebAppConfiguration

集成测试所需要的WebApplicationContext容器

@ContextConfiguration

@ContextConfiguration括号里的locations = {“classpath*:application*.xml”},就表示将class路径里的application.xml文件包括进来,里面自动扫描的bean就都可以拿到,此时就可以在测试类中使用**@Autowired**解来获取之前自动扫描包下的所有bean,

没指定xml文件时,默认寻找文件名为 当前类名+“-context.xml"后缀的配置文件

:@WebAppConfiguration必须与 @ContextConfiguration单个测试类或测试类层次结构一起使用。

@WebAppConfiguration
@ContextConfiguration(locations = {"classpath:application.xml"})
class MyTest{
    //...
}

@SpringJunitWebConfig

这个注解类似Junit4中的 @RunWith(SpringJUnit4ClassRunner.class) ,为了让测试在Spring容器环境下执行.

总结:

  • @WebAppConfiguration为测试容器

  • @ContextConfiguration为容器的配置文件

  • @SpringJunitWebConfig指定为集成测试,使用web容器执行测试


综上可知:

​ 集成测试是需要启动web容器,等同于将整个服务器运行起来,所以正常启动项目所需要的资源在测试的容器中同样不可或缺,web容器只会读取自己test模块下的数据库配置信息,等其他配置(一定要保证数据库配置和你预期的数据是一个数据库,不然查处的数据会有差异)


  • Controller层测试样例

准备工作

查看模拟接口的完整URL,请求方式,携带参数形式,返回内容,编码格式,请求格式等…

controller层接口

@GetMapping("/plus")
public String plus(@RequestParam(value = "str1") String str1,@RequestParam(value = "str2") String str2){

    return serviceTest.plus(str1,str2);
    
}

@PostMapping("/minus")
public String minus(@RequestBody Map map,@RequestParam String beijing){

    System.out.println(map.get("shandong").toString());
    return map.get("shandong").toString() + beijing;
    
}
  • get请求:参数只有通过url传递的形式,后端通过 url获取,也可通过

代码样例:

get方式

@SpringJunitWebConfig
@WebAppConfiguration
@ContextConfiguration(locations = {"classpath:application.xml"})
class MyTest(){
    // MockMvc实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller的调用,这样可以使得测试速度快、不依赖网络环境,而且提供了一套验证的工具,这样可以使得请求的验证统一而且很方便。
    private MockMvc mockMvc;
	//这个@Autowired不可以丢失,否则null
    @Autowired
    private WebApplicationContext context;
    
    //初始化web容器
    @BeforeEach
    void setUp(){
        mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
    }
    @Test
    void plus() throws Exception {
        //构造一个get request
       	MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/hello/class")
                .param("str1", "1234").param("str2", "qwer").accept(MediaType.APPLICATION_JSON).contentType("application/json;charset=UTF-8");
     	 MvcResult mvcResult = mockMvc.perform(get).andExpect(status().isOk()).andDo(MockMvcResultHandlers.print()).andReturn(); 
       //.andExpect(status().isOk()) 表示预计的状态码为200,如果不同则报错
       //.andReturn() 返回模拟请求的结果mvcResult,这个参数中包含了request参数,也包括respone参数
       //使用断言,判断返回的参数是否与预期相同
    	Assertions.assertEquals("1234qwer",mvcResult.getResponse().getContentAsString());
    }
}

post方式:

该方式大致有两中请求方式:

1.通过URL或.param()传递参数,后端必须用@RequestParam接收参数

2.通过json格式进行参数传递,模拟该参数时应将其放在MockMvcRequestBuilders.post(url).content()中,后端必须用@RequestBody接收

@Test
void show() throws Exception {
	   MvcResult mvcResult1 = mockMvc.perform(MockMvcRequestBuilders.post("/hellpo/minqus")
	           .content("{\"shandong\":\"1234\"}").accept(MediaType.APPLICATION_JSON).contentType("application/json;charset=UTF-8")
	           .param("beijing","5678")
	           )
	           .andExpect(status().isOk())
	           .andDo(MockMvcResultHandlers.print())
	           .andReturn();
	   System.out.println(mvcResult1.getResponse().getContentAsString());
	   Assertions.assertEquals("12347778",mvcResult1.getResponse().getContentAsString());
}
  • andDo(MockMvcResultHandlers.print())
    该方法打印了MockMvc模拟请求的参数格式:
MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /hello/minus
       Parameters = {beijing=[7788]}
          Headers = [Content-Type:"application/json;charset=UTF-8", Accept:"application/json", Content-Length:"19"]
             Body = {"shandong":"1234"}
    Session Attrs = {}

Handler:
             Type = com.zc.jutest.controller.HelloController
           Method = com.zc.jutest.controller.HelloController#minus(Map, String)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Type:"application/json", Content-Length:"8"]
     Content type = application/json
             Body = 12347788
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

Service层,Dao层,工具类测试样例

Service层,Dao层,工具类样例比较好写了,这里不再写demo了
不过值得注意的是,在做service层的测试或者Dao层测试时,其中涉及到很复杂HttpServletRequest请求,里面的参数很难人为去构造,所以我直接将WebApplicationContext 容器加载进来,使用mvcResult获取request参数,再将request作为Service层,Dao层的方法参数;另外测试工具类时,因为大多数工具类多为静态的,需要使用PowerMock(好像是这个),但是这个并不适配Junit5,望周知.


可能遇到的bug/问题

  • 显示读取不到application.xml文件

reload一下gradle就可以了

  • 数据库查询失败/依赖导入失败

不要把Test类写在模块下的Test中,因为数据库的配置文件在最外层的Test模块下的,模块内的测试只能读取到本模块内的资源,使用右键用Generate创建的Test类模式是建立在当前模式下的,在setting->Others Setting -> Generate 中可以修改Generate生成Test方法的默认路径(需要安装Generate插件)


单元测试和集成测试区别:

  • 单元测试:不需要web容器,只对当前方法进行小范围测试,但是有一些注意事项,比如说需要测试一个controller层接口方法,一般情况下该方法内会调用service层或者其他方法进行数据处理,这又分两种情况:
    • 被调用的方法为本类的其他的方法,这里就不需要处理,当前类下的方法是可以调用到的
    • 被调用的方法在其他类或者模块,必须使用 Mockito.when(mockObject.method()).thenReturn(object)模拟该方法行为,将你想要该方法返回的结果值放入object,并且模拟方法行为必须在测试controller层方法前,即提前模拟需要用到的方法.
  • 集成测试:需要web容器,和正常启动服务器没区别,测试web容器也需要自己的数据库配置(测试自己的配置),如果同样测试一个controller层接口方法,我们只需要模拟该接口的请求即可,至于方法内调用了哪些方法,怎么执行的,我们不需要关心,只关系最后的返回值;因为集成测试已经启动了整个web应用,所以方法内能正常的执行所有方法调用,返回;

综上所述
单元测试开销更小,速度更快,涉及参数会比较简单,逻辑处理比较简单,适合开发初期测试
集成测试开销会很大,启动整个容器,速度慢,但是可以逻辑复杂,参数冗长的测试,比较适合中后期开发测试

免责声明

以上内容均为本人短期内学习的成果,所写内容为本人个人愚见,都是个人理解,如与事实有出入,轻喷.


每个人都会有一段异常艰难的时光 。 生活的压力 , 工作的失意 , 学业的压力。 爱的惶惶不可终日。 挺过来的 ,人生就会豁然开朗。 挺不过来的 ,时间也会教你 ,怎么与它们握手言和 ,所以不必害怕的。 ——杨绛

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值