【单元测试】一文读懂java单元测试
创始人
2024-09-25 02:21:15
0

目录

  • 1. 什么是单元测试
  • 2. 为什么要单元测试
  • 3. 单元测试框架 - JUnit
    • 3.1 JUnit 简介
    • 3.2 JUnit 内容
    • 3.3 JUnit 使用
      • 3.3.1 Controller 层单元测试
      • 3.3.2 Service 层单元测试
      • 3.3.3 Dao 层单元测试
      • 3.3.4 异常测试
      • 3.3.5 测试套件测多个类
      • 3.3.6 idea 中查看单元测试覆盖率
      • 3.3.7 JUnit 插件自动生成单测代码
      • 3.3.8 常用注解和配置

1. 什么是单元测试

单元测试是软件开发中常用的一种测试方法,用于验证代码的单个功能单元是否按照预期工作。
测试方法:

  • 白盒测试(White Box Testing):在白盒测试中,测试人员了解代码的内部结构和实现细节,编写测试用例来覆盖不同的代码路径和逻辑条件。
  • 黑盒测试(Black Box Testing):黑盒测试不考虑代码的内部实现,而是基于需求规格说明或功能规范编写测试用例,测试程序的输入和输出是否符合预期。
  • 单元测试框架:使用单元测试框架可以简化单元测试的编写和执行。常见的单元测试框架包括JUnit(Java)、NUnit(.NET)、pytest(Python)等。
  • 断言(Assertion):在单元测试中,断言用于检查预期结果和实际结果是否匹配。测试人员可以使用断言来验证程序的特定行为和结果。
  • 边界值测试(Boundary Value Testing):边界值测试通过选择测试用例中的边界条件,例如最小值、最大值、临界值等,来验证程序在边界情况下的行为。
  • 异常处理测试(Exception Handling Testing):异常处理测试用于验证程序在遇到异常情况时是否能够正确地捕获和处理异常,并保证系统的稳定性和可靠性。
  • 参数化测试(Parameterized Testing):参数化测试允许在单个测试用例中使用不同的参数进行多次测试,以增加测试覆盖率和复用性。
    这些方法都可以根据具体的需求和开发环境选择使用。单元测试的目标是尽可能地覆盖代码,确保每个单元都能按照预期工作,并提高软件的质量和可维护性。

这些方法都可以根据具体的需求和开发环境选择使用。单元测试的目标是尽可能地覆盖代码,确保每个单元都能按照预期工作,并提高软件的质量和可维护性。

2. 为什么要单元测试

(1)单元测试意义:

程序代码都是由基本单元不断组合成复杂的系统,底层基本单元都无法保证输入输出正确性,层级递增时,问题就会不断放大,直到整个系统崩溃无法使用。所以单元测试的意义就在于保证基本功能是正常可用且稳定的。而对于接口、数据源等原因造成的不稳定因素,是外在原因,不在单元测试考虑范围之内。

(2)使用 main 方法进行测试:

@PostMapping("/add") public void addStudent(@RequestBody Student student){     studentService.save(student); } 

假如要对上面的 Controller 进行测试,可以编写如下的代码示例,使用 main 方法进行测试的时候,先启动整个工程应用,然后编写 main 方法如下进行访问,在单步调试代码。

public static void main(String[] args) {     RestTemplate restTemplate = new RestTemplate();     HttpHeaders httpHeaders = new HttpHeaders();     httpHeaders.setContentType(MediaType.APPLICATION_JSON);     String json = "{\"id\":4,\"name\":\"阿狸\",\"classname\":\"初三一班\",\"age\":16,\"sex\":\"女\"}";     HttpEntity httpEntity = new HttpEntity<>(json, httpHeaders);     String url = "http://localhost:8080/student/add";     ResponseEntity responseEntity = restTemplate.postForEntity(url, httpEntity, Map.class);     System.out.println(responseEntity.getBody()); } 

(3)使用 main 方法进行测试的缺点:

  1. 通过编写大量的 main 方法针对每个内容做打印输出到控制台枯燥繁琐,不具备优雅性。

  2. 测试方法不能一起运行,结果需要程序员自己判断正确性。

  3. 统一且重复性工作应该交给工具去完成。

3. 单元测试框架 - JUnit

3.1 JUnit 简介

Unit 官网:https://junit.org/。JUnit 是一个用于编写可重复测试的简单框架。它是用于单元测试框架的 xUnit 体系结构的一个实例。

JUnit 的特点:

(1) 针对于 Java 语言特定设计的单元测试框架,使用非常广泛。

(2) 特定领域的标准测试框架。

(3) 能够在多种 IDE 开发平台使用,包含 Idea、Eclipse 中进行集成。

(4) 能够方便由 Maven 引入使用。

(5) 可以方便的编写单元测试代码,查看测试结果等。

JUnit 的重要概念:

名称功能作用
Assert断言方法集合
TestCase表示一个测试案例
TestSuite包含一组 TestCase,构成一组测试
TestResult收集测试结果

JUnit 的一些注意事项及规范:

(1) 测试方法必须使用 @Test 修饰

(2) 测试方法必须使用 public void 进行修饰,不能带参数

(3) 测试代码的包应该和被测试代码包结构保持一致

(4) 测试单元中的每个方法必须可以独立测试,方法间不能有任何依赖

(5) 测试类一般使用 Test 作为类名的后缀

(6) 测试方法使一般用 test 作为方法名的前缀

JUnit 失败结果说明:

(1) Failure:测试结果和预期结果不一致导致,表示测试不通过

(2) error:由异常代码引起,它可以产生于测试代码本身的错误,也可以是被测代码的 Bug

3.2 JUnit 内容

(1) 断言的 API

断言方法断言描述
assertNull(String message, Object object)检查对象是否为空,不为空报错
assertNotNull(String message, Object object)检查对象是否不为空,为空报错
assertEquals(String message, Object expected, Object actual)检查对象值是否相等,不相等报错
assertTrue(String message, boolean condition)检查条件是否为真,不为真报错
assertFalse(String message, boolean condition)检查条件是否为假,为真报错
assertSame(String message, Object expected, Object actual)检查对象引用是否相等,不相等报错
assertNotSame(String message, Object unexpected, Object actual)检查对象引用是否不等,相等报错
assertArrayEquals(String message, Object[] expecteds, Object[] actuals)检查数组值是否相等,遍历比较,不相等报错
assertArrayEquals(String message, Object[] expecteds, Object[] actuals)检查数组值是否相等,遍历比较,不相等报错
assertThat(String reason, T actual, Matcher matcher)检查对象是否满足给定规则,不满足报错

(2) JUnit 常用注解:

1) @Test: 定义一个测试方法 @Test (excepted=xx.class): xx.class 表示异常类,表示测试的方法抛出此异常时,认为是正常的测试通过的 @Test (timeout = 毫秒数) : 测试方法执行时间是否符合预期。

2) @BeforeClass在所有的方法执行前被执行,static 方法全局只会执行一次,而且第一个运行

3) @AfterClass在所有的方法执行之后进行执行,static 方法全局只会执行一次,最后一个运行

4) @Before在每一个测试方法被运行前执行一次

5) @After在每一个测试方法运行后被执行一次

6) @Ignore:所修饰的测试方法会被测试运行器忽略。

7) @RunWith:可以更改测试执行器使用 junit 测试执行器。

3.3 JUnit 使用

3.3.1 Controller 层单元测试

(1) Springboot 中使用 maven 引入 Junit 非常简单,使用如下依赖即可引入:

     org.springframework.boot     spring-boot-starter-test     test        junit     junit     4.13.1     test  

(2) 上面使用 main 方法案例可以使用如下的 Junit 代码完成:

@RunWith(SpringRunner.class) @SpringBootTest(classes = UnittestDemoApplication.class) class StudentControllerTest {      @Autowired     private WebApplicationContext webApplicationContext;      private MockMvc mockMvc;      @BeforeEach     void setUp() {         mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();     }      @Test     void testAddStudent() throws Exception {         // 创建一个Student对象作为请求的JSON体         Student student = new Student();         student.setId(6);         student.setName("小乔");         student.setClassname("初二三班");         student.setAge(14);         student.setSex("女");          // 将Student对象转换为JSON字符串         ObjectMapper objectMapper = new ObjectMapper();         String json = objectMapper.writeValueAsString(student);          // 发送POST请求         mockMvc.perform(MockMvcRequestBuilders.post("/student/add")                         .contentType(MediaType.APPLICATION_JSON_UTF8)                         .accept(MediaType.APPLICATION_JSON_UTF8)                         .content(json)                 )                 // 断言返回的状态码为200                 .andExpect(MockMvcResultMatchers.status().isOk())                 // 断言返回的JSON中包含指定的code和message                 .andExpect(MockMvcResultMatchers.jsonPath("$.code").value(200))                 .andExpect(MockMvcResultMatchers.jsonPath("$.message").value("保存成功"))                 .andDo(MockMvcResultHandlers.print());     } } 

只需要在类或者指定方法上右键执行即可,可以直接充当 postman 工作访问指定 url,且不需要写请求代码,这些都由工具自动完成。

在这里插入图片描述

(3)案例中相关组件介绍

本案例中构造 mockMVC 对象时,也可以使用如下方式:

@Autowired private StudentController studentController; @Before public void setupMockMvc(){    // 初始化MockMvc对象    mockMvc = MockMvcBuilders.standaloneSetup(studentController).build(); } 

其中 MockMVC 是 Spring 测试框架提供的用于 REST 请求的工具,是对 Http 请求的模拟,无需启动整个模块就可以对 Controller 层进行调用,速度快且不依赖网络环境。

使用 MockMVC 的基本步骤如下:

  1. mockMvc.perform 执行请求
  2. MockMvcRequestBuilders.post 或 get 构造请求
  3. MockHttpServletRequestBuilder.param 或 content 添加请求参数
  4. MockMvcRequestBuilders.contentType 添加请求类型
  5. MockMvcRequestBuilders.accept 添加响应类型
  6. ResultActions.andExpect 添加结果断言
  7. ResultActions.andDo 添加返回结果后置处理
  8. ResultActions.andReturn 执行完成后返回相应结果

3.3.2 Service 层单元测试

可以编写如下代码对 Service 层查询方法进行单测:

正例:

@RunWith(SpringRunner.class) @SpringBootTest public class StudentServiceTest {   	@Autowired     private StudentService studentService;       @Test     public void getOne(){         Student stu = studentService.getById(1);         Assert.assertThat(stu.getName(), CoreMatchers.is("张三"));     } } 

执行结果:

在这里插入图片描述

反例:

@RunWith(SpringRunner.class) @SpringBootTest public class StudentServiceTest {   	@Autowired     private StudentService studentService;       @Test     public void getOne(){         Student stu = studentService.getById(1);         Assert.assertThat(stu.getName(), CoreMatchers.is("李四"));     } } 

执行结果:

在这里插入图片描述

3.3.3 Dao 层单元测试

可以编写如下代码对 Dao 层保存方法进行单测:

@RunWith(SpringRunner.class) @SpringBootTest public class StudentDaoTest {   	@Autowired     private StudentMapper studentMapper;       @Test     @Rollback(value = true)     @Transactional     public void insertOne() throws Exception {    	    Student student = new Student();         student.setId(7);         student.setName("王五");         student.setClassname("大一");         student.setAge(20);         student.setSex("男");         int count = studentMapper.insert(student);         Assert.assertEquals(1, count);     } } 

在这里插入图片描述
其中 @Rollback (value = true) 可以执行单元测试之后回滚所新增的数据,保持数据库不产生脏数据。

3.3.4 异常测试

(1) 在 service 层定义一个异常情况:

public void computeScore() {    int a = 10, b = 0;    int c = a/b; } 

(2) 在 service 的测试类中定义单元测试方法:

反例

    @Test     public void computeScoreTest() {         studentService.computeScore();     } 

结果:

在这里插入图片描述

正例:

junit 5.0版本之前,在@Test上添加expected = ArithmeticException.class

@Test(expected = ArithmeticException.class)     public void computeScoreTest() {         studentService.computeScore();     } 

junit 5.0版本之后,使用Assert.assertThrows

@Test public void computeScoreTest() {     Assert.assertThrows(ArithmeticException.class, () -> {         studentService.computeScore(); // This line should throw ArithmeticException     }); } 

(3) 执行单元测试也会通过,原因是 @Test 注解中的定义了异常

在这里插入图片描述

3.3.5 测试套件测多个类

(1) 新建一个空的单元测试类

(2) 利用注解 @RunWith (Suite.class) @SuiteClasses 标明要一起单元测试的类

在这里插入图片描述

运行结果:

在这里插入图片描述

3.3.6 idea 中查看单元测试覆盖率

(1) 单测覆盖率

测试覆盖率是衡量测试过程工作本身的有效性,提升测试效率和减少程序 bug,提升产品可靠性与稳定性的指标。

统计单元测试覆盖率的意义:

1) 可以洞察整个代码中的基础组件功能的所有盲点,发现相关问题。

2) 提高代码质量,通常覆盖率低表示代码质量也不会太高,因为单测不通过本来就映射出考虑到各种情况不够充分。

3) 从覆盖率的达标上可以提高代码的设计能力。

(2) 在 idea 中查看单元测试覆盖率很简单,只需按照图中示例的图标运行,或者在单元测试方法或类上右键 Run ‘xxx’ with Coverage 即可。执行结果是一个表格,列出了类、方法、行数、分支覆盖情况。

在这里插入图片描述

(3) 在代码中会标识出覆盖情况,绿色的是已覆盖的,红色的是未覆盖的。

在这里插入图片描述
(4) 如果想要导出单元测试的覆盖率结果,可以使用如下图所示的方式,勾选 Open generated HTML in browser

在这里插入图片描述
导出结果:

在这里插入图片描述

3.3.7 JUnit 插件自动生成单测代码

(1) 安装插件

在这里插入图片描述
(2)选择要生成单元测试的类,按Alt+Insert出现如下界面,选择TestMe自动生成文件

在这里插入图片描述
(3)选择需要的生成模板,可以根据自己实际引入的依赖选择,此处选择Junit4+Mockito
在这里插入图片描述
(4)生成的代码如下,可以生成一些基本的方法和注解,然后根据实际情况修改,可以节省一部分工作量。
在这里插入图片描述

3.3.8 常用注解和配置

@Mock:创建一个模拟的对象,类似于@Autowired,但不是真实的对象,是Mock对象,这个注解使用在类属性上

@InjectMocks:创建一个实例,其余用@Mock注解创建的mock将被注入到用该实例中,这个注解使用在类属性上

@RunWith:表示一个运行器,@RunWith(PowerMockRunner.class)表示指定用PowerMockRunner运行,这个注解使用在类上

@PowerMockIgnore:这个注解表示将某些类延迟到系统类加载器加载,解决一些类加载异常。(具体类加载异常实际中还未遇见,后续补充),这个注解在类和方法上使用

@PrepareForTest:这个注解告诉PowerMock为测试准备某些类,通常是那些需要字节码操作的类。这包括带有final、private、static或native方法的类,new一个对象时,需要特殊处理(见下面的whenNew),这个注解在类和方法上使用

@Test:@Test修饰的public void方法可以作为测试用例运行。Junit会构造一个新的类实例,然后调用所有加了@Test的方法,方法执行过程中的任何异常,都会被判定为测试用例执行失败。

@Before:@Before注解的方法中的代码会在@Test注解的方法中首先被执行。可以做一些公共的前置操作:加入一些申请资源的代码:申请数据库资源,申请io资源,申请网络资源,new一些公共的对象等等。

@After:@After注解的方法中的代码会在@Test注解的方法中首先被执行。可以做一些公共的后置操作,如关闭资源的操作。

注:可以查看注解上的注释,了解其大致用法。

代码地址GitHub

单元测试之Mockito见文章《【单元测试】单元测试之Mockito的使用》
【单例测试】Mockito实战见文章《【单例测试】Mockito实战》

觉得有用的话还请来个三连!!!

相关内容

热门资讯

安卓系统如何停止禁用,安卓系统... 手机里的安卓系统突然间变得不听话了,各种权限被禁用,真是让人头疼啊!不过别急,今天就来教你怎么巧妙地...
安卓系统中控屏幕,尽享智能生活 你有没有发现,现在手机屏幕越来越大,看着眼睛都累了呢?别急,今天就来给你揭秘一下安卓系统中控屏幕的神...
鸿蒙系统国外加安卓,打造国际化... 你知道吗?最近科技圈可是炸开了锅,因为华为的鸿蒙系统在国外市场竟然和安卓系统来了个亲密接触。这可不是...
安卓系统能调试设备,安卓设备调... 你有没有想过,你的安卓手机或者平板电脑其实是个大宝藏呢?里面藏着无数的秘密,等着你去探索。没错,我说...
安卓系统故障码,快速诊断与解决... 手机突然卡顿,屏幕闪个不停,是不是又遇到什么大麻烦了?别慌,今天就来和你聊聊安卓系统故障码那些事儿,...
安卓系统版本查询截图,从初代到... 你有没有想过,你的安卓手机里隐藏着多少秘密呢?比如,它的系统版本是多少?这可是了解手机性能和升级情况...
安卓 系统空间怎么访问,轻松掌... 你有没有遇到过手机里空间不够用的情况?别急,今天就来教你怎么轻松访问安卓系统的隐藏空间,让你的手机瞬...
安卓系统自动读短信,轻松掌握信... 你有没有想过,手机短信这东西,有时候真是让人又爱又恨呢?信息一来,手机震动个不停,有时候手忙脚乱,有...
安卓系统怎么连接雪铁龙,一键实... 你有没有想过,你的安卓手机和雪铁龙汽车之间也能来个亲密接触呢?没错,就是连接起来,让你的车生活更加智...
螳螂营销安卓版系统,助力企业腾... 你知道吗?最近我在手机上发现了一个超级有趣的应用——螳螂营销安卓版系统!这款应用简直就像是一个小助手...
机器人 安卓系统,探索安卓系统... 你知道吗?在这个科技飞速发展的时代,机器人已经不再是科幻电影里的专属了。它们正悄悄地走进我们的生活,...
安卓开发的系统框架,架构与核心... 你有没有想过,为什么你的安卓手机那么聪明,能帮你做那么多事情?其实,这一切都离不开安卓开发的系统框架...
安卓系统版本rom大全,全面解... 你有没有发现,手机更新换代的速度简直就像坐上了火箭呢?从安卓1.0到现在的安卓12,每一代系统都给我...
所有安卓系统疯狂报错,揭秘背后... 最近是不是发现你的安卓手机突然变得有点儿“疯狂”?各种报错信息层出不穷,让人头疼不已。别急,今天就来...
安卓系统的悬浮球,便捷操作与个... 你有没有发现,手机上的安卓系统最近有个新玩意儿,叫悬浮球?这可不是什么小打小闹的更新,它可是让手机操...
比亚迪双击解锁安卓系统,畅享智... 哇,你知道吗?最近比亚迪的双击解锁安卓系统可是引起了不小的轰动呢!想象一辆汽车,不仅动力强劲,还能像...
安卓系统应用沙盒,构建安全、独... 你知道吗?在智能手机的世界里,安卓系统就像是一个热闹的市集,各种应用如过江之鲫,争奇斗艳。但你知道吗...
什么电视用安卓系统,探索智能电... 你有没有想过,家里的电视竟然也能用安卓系统?没错,就是那个我们手机上常用的安卓系统,现在竟然也能在电...
安卓免费烘焙收银系统,助力烘焙... 你有没有想过,在繁忙的烘焙店中,一款好用的收银系统能让你的生意如虎添翼?今天,就让我带你一探究竟,看...
王者苹果系统迁移安卓,体验跨平... 你有没有想过,从王者苹果系统跳转到安卓系统,这中间的迁移过程,是不是就像一场说走就走的旅行呢?想象你...