• 单元测试的心法分享


    大家好,我是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. 输出结果:方法返回什么?是否有副作用(如修改了类的状态、写入日志等)?

    分析示例

    假设 MyClass 的 myMethod 如下:

    1. public class MyClass {
    2.     private MyDependency dependency;
    3.     public MyClass(MyDependency dependency) {
    4.         this.dependency = dependency;
    5.     }
    6.     public String myMethod(String input) {
    7.         String transformedInput = input.toUpperCase();
    8.         boolean isValid = dependency.validate(transformedInput);
    9.         if (isValid) {
    10.             return "Valid: " + transformedInput;
    11.         } else {
    12.             return "Invalid: " + transformedInput;
    13.         }
    14.     }
    15. }

    分析步骤

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

    单元测试模板

    1. import static org.mockito.Mockito.*;
    2. import static org.junit.Assert.*;
    3. import org.junit.Before;
    4. import org.junit.Test;
    5. import org.junit.runner.RunWith;
    6. import org.mockito.InjectMocks;
    7. import org.mockito.Mock;
    8. import org.mockito.MockitoAnnotations;
    9. import org.powermock.modules.junit4.PowerMockRunner;
    10. import org.powermock.modules.junit4.PowerMockRunnerDelegate;
    11. import org.mockito.runners.MockitoJUnitRunner;
    12. @RunWith(PowerMockRunner.class)
    13. @PowerMockRunnerDelegate(MockitoJUnitRunner.class)
    14. public class MyClassTest {
    15.     @Mock
    16.     private MyDependency mockDependency;
    17.     @InjectMocks
    18.     private MyClass myClass;
    19.     @Before
    20.     public void setUp() {
    21.         MockitoAnnotations.initMocks(this);
    22.     }
    23.     @Test
    24.     public void testMyMethod_ValidInput() {
    25.         // Arrange
    26.         String input = "test";
    27.         String transformedInput = "TEST";
    28.         when(mockDependency.validate(transformedInput)).thenReturn(true);
    29.         // Act
    30.         String result = myClass.myMethod(input);
    31.         // Assert
    32.         assertEquals("Valid: TEST", result);
    33.         verify(mockDependency).validate(transformedInput);
    34.     }
    35.     @Test
    36.     public void testMyMethod_InvalidInput() {
    37.         // Arrange
    38.         String input = "test";
    39.         String transformedInput = "TEST";
    40.         when(mockDependency.validate(transformedInput)).thenReturn(false);
    41.         // Act
    42.         String result = myClass.myMethod(input);
    43.         // Assert
    44.         assertEquals("Invalid: TEST", result);
    45.         verify(mockDependency).validate(transformedInput);
    46.     }
    47. }

    模板解析

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

    注意事项

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

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

    题外话

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

  • 相关阅读:
    基于Java在线教学质量评价系统设计实现(源码+lw+部署文档+讲解等)
    【线性代数基础进阶】特征值和特征向量-补充+练习
    eclipse和idea如何打开和关闭debug功能
    Mediapipe学习记录
    没那么简单的单例模式
    积雪草酸肌白蛋白纳米粒|野黄芩苷豆清白蛋白纳米粒|黄芩苷蓖麻蛋白纳米粒(齐岳)
    requests 实践
    python环境搭建
    开发语言漫谈-React
    Mybatis面试题
  • 原文地址:https://blog.csdn.net/qq_34050399/article/details/139395482