单元测试的心法分享

大家好,我是G探险者!

今天我们简单聊聊单元测试的哪些事儿~

两天时间我玩明白了单元测试的套路。

这里我分享一下思路。

在我眼里单元测试室什么?

请看这张草图:

单元测试主要关注单个代码单元(通常是类或方法)的逻辑正确性,而不是功能测试的全面性。具体来说:

单元测试的目的

  1. 验证逻辑路径:单元测试旨在验证代码逻辑中的不同路径是否按预期执行。这包括条件分支(如if-else语句)、循环、异常处理等。
  2. 隔离测试:通过mock或stub来隔离被测试的单元,使其不依赖外部资源(如数据库、网络服务)或其他复杂对象。
  3. 快速反馈:单元测试应该快速执行,提供即时的反馈,帮助开发者在早期发现并修复问题。
  4. 保证代码质量:通过覆盖不同的逻辑路径和边界情况,确保代码在各种情况下都能正确运行。

与功能测试的区别

  • 单元测试:专注于验证单个代码单元的内部逻辑,不要求模拟复杂的实际场景。变量值的准确性不是重点,重点是逻辑是否按预期执行。
  • 功能测试:验证整个系统或子系统的功能是否满足需求,通常在集成环境中进行,涉及实际的外部资源和复杂的交互。

单元测试中的Mock实践

  1. 分析需要mock的对象和行为

    • 确定哪些外部依赖(如数据库、网络服务)需要被mock。
    • 只mock那些必要的对象,保持测试的简洁性。
  2. 创建mock对象的方式

    • 使用@Mock注解或Mockito.mock()方法创建mock对象。
    • 可以使用@RunWith(MockitoJUnitRunner.class)MockitoAnnotations.initMocks(this)来初始化@Mock注解的字段。
  3. mock的目的

    • 隔离被测试单元,确保测试专注于目标代码逻辑。
    • 控制依赖对象的行为和状态,模拟各种异常和边界条件,验证代码的健壮性和正确性。
  4. mock对象和行为

    • 使用when().thenReturn()模拟方法返回特定值。
    • 使用when().thenThrow()模拟方法抛出异常。
    • 使用thenReturn()多次或thenAnswer()模拟连续调用时的不同结果。
    • 使用verify()方法验证方法的调用次数和顺序,确保逻辑执行的正确性。

这里我总结了一些些单元测试基本套路吧,整理一个模板。

分析方法

假设我们有一个类 MyClass 和其中的一个方法 myMethod。在分析该方法时,你需要考虑以下几个方面:

  1. 输入参数:方法接受哪些参数?
  2. 依赖对象:方法中使用了哪些类的实例?
  3. 行为动作:方法内部调用了哪些其他方法?是否有外部依赖,如数据库、网络调用等?
  4. 输出结果:方法返回什么?是否有副作用(如修改了类的状态、写入日志等)?

分析示例

假设 MyClassmyMethod 如下:

java 复制代码
public class MyClass {
    private MyDependency dependency;

    public MyClass(MyDependency dependency) {
        this.dependency = dependency;
    }

    public String myMethod(String input) {
        String transformedInput = input.toUpperCase();
        boolean isValid = dependency.validate(transformedInput);
        if (isValid) {
            return "Valid: " + transformedInput;
        } else {
            return "Invalid: " + transformedInput;
        }
    }
}

分析步骤

  1. 输入参数input(字符串)
  2. 依赖对象MyDependency(通过构造函数注入)
  3. 行为动作 :调用了 dependency.validate(transformedInput)
  4. 输出结果 :返回一个字符串,形式为 "Valid: " + transformedInput"Invalid: " + transformedInput

单元测试模板

java 复制代码
import static org.mockito.Mockito.*;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.modules.junit4.PowerMockRunnerDelegate;
import org.mockito.runners.MockitoJUnitRunner;

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(MockitoJUnitRunner.class)
public class MyClassTest {

    @Mock
    private MyDependency mockDependency;

    @InjectMocks
    private MyClass myClass;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testMyMethod_ValidInput() {
        // Arrange
        String input = "test";
        String transformedInput = "TEST";
        when(mockDependency.validate(transformedInput)).thenReturn(true);

        // Act
        String result = myClass.myMethod(input);

        // Assert
        assertEquals("Valid: TEST", result);
        verify(mockDependency).validate(transformedInput);
    }

    @Test
    public void testMyMethod_InvalidInput() {
        // Arrange
        String input = "test";
        String transformedInput = "TEST";
        when(mockDependency.validate(transformedInput)).thenReturn(false);

        // Act
        String result = myClass.myMethod(input);

        // Assert
        assertEquals("Invalid: TEST", result);
        verify(mockDependency).validate(transformedInput);
    }
}

模板解析

  1. 导入依赖:导入了Mockito和JUnit的静态方法。
  2. 测试类定义 :定义了 MyClassTest 测试类。
  3. Mock对象 :使用 @Mock 注解创建 MyDependency 的mock对象。
  4. 测试准备 :在 setUp 方法中初始化mock对象,并创建 MyClass 实例。
  5. 测试方法 :定义两个测试方法,分别测试myMethodvalidate 方法返回 truefalse 的情况下的行为。

注意事项

  • 测试覆盖:确保测试覆盖了所有可能的输入和边界情况。
  • 行为验证 :使用 verify 方法验证依赖对象的方法调用情况。
  • 异常处理:如果方法可能抛出异常,编写相应的测试用例。

通过这个模板,你可以系统地分析并编写单元测试,确保测试的全面性和准确性。

题外话

当然现在idea 里面有相关的些单元测试的插件,比如Squaretest,或者你直接通过ChatGPT来写。但是你得知道原理,不然糊里糊涂。

相关推荐
q***718519 分钟前
Spring Boot 集成 MyBatis 全面讲解
spring boot·后端·mybatis
大象席地抽烟26 分钟前
使用 Ollama 本地模型与 Spring AI Alibaba
后端
程序员小假29 分钟前
SQL 语句左连接右连接内连接如何使用,区别是什么?
java·后端
小坏讲微服务30 分钟前
Spring Cloud Alibaba Gateway 集成 Redis 限流的完整配置
数据库·redis·分布式·后端·spring cloud·架构·gateway
方圆想当图灵1 小时前
Nacos 源码深度畅游:Nacos 配置同步详解(下)
分布式·后端·github
方圆想当图灵1 小时前
Nacos 源码深度畅游:Nacos 配置同步详解(上)
分布式·后端·github
小羊失眠啦.2 小时前
用 Rust 实现高性能并发下载器:从原理到实战
开发语言·后端·rust
Filotimo_3 小时前
SpringBoot3入门
java·spring boot·后端
一 乐3 小时前
校园墙|校园社区|基于Java+vue的校园墙小程序系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·后端·小程序
golang学习记3 小时前
🍵 Go Queryx 入门指南:让数据库操作像喝奶茶一样丝滑!
后端