前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基于SpringBoot聊单元测试的分层

基于SpringBoot聊单元测试的分层

作者头像
周辰晨
发布2022-09-20 15:56:00
7160
发布2022-09-20 15:56:00
举报
文章被收录于专栏:软件测试架构师俱乐部

之前分享了关于质量内建的话题关于单元测试引起了大家的讨论,对于单元测试这件事情本身是比较熟悉的,但大家的反馈是比较难执行,矛盾在于很多测试做不了单元测试,或者让测试做性价比不是很高,这件事情推给开发之后又容易不了了之,其中一个很重要的点是,测试和开发没有同频对话的能力,各种细节难以敲定,落地的实际价值不容易度量,所以这篇文章我就基于常见的springboot框架,聊一聊单元测试分层的几种实践方式,从测试的视角给同学们一些知识面的拓展,也让大家熟悉下单元测试的常见玩法。

一.单元测试带来的好处

1.预防bug

为什么说可以预防bug呢,如果能够执行单元测试,说明开发已经具备一定的质量思维了,在写代码的时候会考虑如何测试,有哪些测试点等,通过这样的思维可以预防bug的产生。

2.快速定位Bug

单元测试意味着我们测试的前置以及测试颗粒度的细化,所以更容易在更小范围内锁定bug,能够带来效率的提升,相对于在测试阶段发现bug来说,会大量减少调试时间。

3.降低重构风险

快速的发现并解决问题不容易形成技术债,团队具备良好的质量把控意识会从根本上带来质量的提升,从而降低重构的可能性。

二.SpringBoot的测试库

SpringBoot提供了如下的类库,通过引入可以获取到测试的类方法。

代码语言:javascript
复制
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
</dependency>

Junit:Java应用程序单元测试标准类库

AssertJ:轻量级断言类库

Mockito: Java的Mock测试框架

JsonPath:JSON操作类库

JSONNAssert:基于JSON的断言库

三.快速创建单元测试

当我们引入spring-boot-starter-test相关的类库后,直接在工程项目中src/test/java中创建类即可,如下所示:

代码语言:javascript
复制
package com.example.demo;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class UnitTestDemoApplicationTests {
   @Test
   public void contextLoads() {
   }
}

@SpringBootTest:是SpringBoot用于测试的注解,可指定入口类和测试环境。

@RunWith(SpringRunner.class):让测试运行于Spring的测试环境。

@Test 表示为一个测试单元。

四:SpingBoot基础知识

先来简单看下我们如何访问springboot服务,当用户通过浏览器访问后端服务时,通过Controller层决定控制访问逻辑,Service层主要实现系统的业务逻辑,DAO层直接操作数据库的代码。

总结这三者,通过例子来解释:

Controller像是服务员,顾客点什么菜,菜上给几号桌,都是ta的职责;

Service是厨师,action送来的菜单上的菜全是ta做的;

Dao是厨房的小工,和原材料打交道的事情全是ta管。

五.单元测试的分层实践

1.基于Controller层的单元测试

关于实践就直接通过代码演示,首先可以在controller层实现一下demo,在src/test/java下完成

代码语言:javascript
复制
package com.example.demo.controller;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
    @RequestMapping("/hello")
    public String hello(String name){
        return "hello "+name;
    }
   }

解释下:

@RestController:代表这个类是REST风格的控制器,返回JSON或者XML的类型数据。

@RequestMapping:用于配置URL和方法之间的映射,可用在类和方法上。

编写测试代码:

代码语言:javascript
复制
package com.example.demo.controller;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.junit.Assert.*;

@SpringBootTest
@RunWith(SpringRunner.class)
public class HelloControllerTest {
    //启用web上下文
    @Autowired
    private WebApplicationContext webApplicationContext;
    private MockMvc mockMvc;

    @Before
    public void setUp() throws Exception{
        //使用上下文构建mockMvc
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }
    @Test
    public void hello() throws Exception {
       // 得到MvcResult自定义验证
      // 执行请求
        MvcResult mvcResult= mockMvc.perform(MockMvcRequestBuilders.get("/hello")
        //.post("/hello") 发送post请求
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                //传入参数
                .param("name","cctester")
               // .accept(MediaType.TEXT_HTML_VALUE))
                //接收的类型
                .accept(MediaType.APPLICATION_JSON_UTF8))
                //等同于Assert.assertEquals(200,status);
                //判断接收到的状态是否是200
                .andExpect(MockMvcResultMatchers.status().isOk())
                 //等同于 Assert.assertEquals("hello cctetser",content);
                .andExpect(MockMvcResultMatchers.content().string("hello cctester"))
                .andDo(MockMvcResultHandlers.print())
        //返回MvcResult
        .andReturn();
        //得到返回代码
        int status=mvcResult.getResponse().getStatus();
        //得到返回结果
        String content=mvcResult.getResponse().getContentAsString();
        //断言,判断返回代码是否正确
        Assert.assertEquals(200,status);
        //断言,判断返回的值是否正确
        Assert.assertEquals("hello cctester",content);
    }
}

通过相应的输出可以完成校验,可以看到controller级别的单元测试跟接口测试比较接近

2.Service层的单元测试

我们还是先来创建demo,先来一个实体类

代码语言:javascript
复制
package com.example.demo.entity;
import lombok.Data;
@Data
public class User {
    private String name;
    private  int age;
}

再创建服务类,@Service来标注服务类

代码语言:javascript
复制
package com.example.demo.service;
import com.example.demo.entity.User;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    public User getUserInfo(){
        User user = new User();
        user.setName("cctester");
        user.setAge(28);
        return user;
    }
}

编写测试用例

代码语言:javascript
复制
package com.example.demo.service;
import com.example.demo.entity.User;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import static org.hamcrest.CoreMatchers.*;
//表明要在测试环境运行,底层使用的junit测试工具
@RunWith(SpringRunner.class)
// SpringJUnit支持,由此引入Spring-Test框架支持!
//启动整个spring的工程
@SpringBootTest
public class UserServiceTest {
@Autowired
private UserService userService;
    @Test
    public void getUserInfo() {
        User user = userService.getUserInfo();
        //比较实际的值和用户预期的值是否一样
        Assert.assertEquals(18,user.getAge());
        Assert.assertThat(user.getName(),is("cctester"));
    }
}

运行结果:

3.DAO层的单元测试

DAO层主要用于对数据的增删改查操作,同样可以进行单元测试,并使用@Transactional注解进行回滚操作,我们也来简单演示下

代码语言:javascript
复制
package com.example.demo.repository;

import com.example.demo.entity.Card;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.List;
import static org.junit.Assert.*;

@RunWith(SpringRunner.class)
// SpringJUnit支持,由此引入Spring-Test框架支持!
//启动整个spring的工程
@SpringBootTest
//@DataJpaTest
@Transactional
//@Rollback(false)
public class CardRepositoryTest {
    @Autowired
    private  CardRepository  cardRepository;
    @Test
  public void testQuery() {
   // 查询操作
  List<Card> list = cardRepository.findAll();
        for (Card card : list) {
            System.out.println(card);
        }
   }
    @Test
    public void testRollBank() {
        // 写入操作
        Card card=new Card();
        card.setNum(3);
        cardRepository.save(card);
        //throw new RuntimeException();
    }

运行testRollBack,可以看到输出台

代码语言:javascript
复制
hibernate: insert into cardtestjpa (num) values (?)
2022-04-17 09:21:25.866  INFO 27907 --- [           main] o.s.t.c.transaction.TransactionContext   : Rolled back transaction for test: [DefaultTestContext@3bb9a3ff testClass = CardRepositoryTest, testInstance = com.example.demo.repository.CardRepositoryTest@2bf3ec4, testMethod = testRollB
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 架构师影响力 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档