## 软件测试的四个阶段 ### 单元测试 单元测试是对软件组成单元进行测试。其目的是检验软件基本组成单位的正确性。测试的对象是软件设计的最小单位:模块。又称为模块测试 测试阶段:编码后 测试对象:最小模块 测试人员:白盒测试工程师或开发工程师 测试依据:代码和注释+详细设计文档 测试方法:白盒测试 测试内容:模块接口测试、局部数据结构测试、路径测试、错误处理测试、边界测试 ### 集成测试 集成测试也称联合测试(联调)、组装测试,将程序模块采用适当的集成策略组装起来,对系统的接口及集成后的功能进行正确性检测的测试工作。集成主要目的是检查软件单位之间的接口是否正确。 测试阶段:一般单元测试之后进行 测试对象:模块间的接口 测试人员:白盒测试工程师或开发工程师 测试依据:单元测试的模块+概要设计文档 测试方法:黑盒测试与白盒测试相结合 测试内容:模块之间数据传输、模块之间功能冲突、模块组装功能正确性、全局数据结构、单模块缺陷对系统的影响 ### 系统测试 将软件系统看成是一个系统的测试。包括对功能、性能以及软件所运行的软硬件环境进行测试。 测试阶段:集成测试通过之后 测试对象:整个系统(软、硬件) 测试人员:黑盒测试工程师 测试依据:需求规格说明文档 测试方法:黑盒测试 测试内容:功能、界面、可靠性、易用性、性能、兼容性、安全性等 ### 验收测试 验收测试是部署软件之前的最后一个测试操作。它是技术测试的最后一个阶段,也称为交付测试。验收测试的目的是确保软件准备就绪,按照项目合同、任务书、双方约定的验收依据文档,向软件购买者展示该软件系统满足原始需求。 测试阶段:系统测试通过之后 测试对象:整个系统(包括软硬件)。 测试人员:主要是最终用户或者需求方。 测试依据:用户需求、验收标准 测试方法:黑盒测试 测试内容:同系统测试(功能...各类文档等) ## 单元测试 软件测试有助于确保代码质量,是软件开发过程中不可或缺的一部分。 单元测试是测试代码中最小的功能单元的过程。软件开发的最佳实践是将软件编写为小型功能单元,然后为每个代码单元编写单元测试。 您可以先将单元测试编写为代码。然后,在每次更改软件代码时自动运行该测试代码。这样,如果测试失败,您就能快速隔离出存在漏洞或错误的代码区域。 单元测试可以强化模块化思维范式,并提高测试的覆盖率和质量。自动单元测试有助于确保您或您的开发人员有更多时间专注于编码。 单元测试是一个代码块,用于验证较小、孤立的应用程序代码块(通常是函数或方法)的准确性。单元测试旨在根据代码块背后的开发人员理论逻辑检查其是否按预期运行。 单个代码块也可以有一组单元测试,称为测试用例。一整套测试用例涵盖了代码块的全部预期行为,但并不总是需要定义完整的测试用例集。 ### 为什么要执行单元测试? - **比执行其他测试节省时间** 其他通常涉及打开应用程序并执行你(或其他人)必须遵循的一系列步骤,以验证预期的行为。 测试人员可能并非总是了解这些步骤。 为了执行测试,他们需要联系更熟悉该领域的人。 对于细微更改,测试本身可能需要几秒钟,对于较大更改,可能需要几分钟。 最后,在系统中所做的每项更改都必须重复此过程。 而单元测试只需按一下按钮即可运行,只需要几毫秒时间,且无需测试人员了解整个系统。 测试通过与否取决于测试运行程序,而非测试人员。 - **防止回归** 回归缺陷是在对应用程序进行更改时引入的缺陷。 通常,测试人员不仅要测试新功能,还要测试预先存在的功能,以验证先前实现的功能是否仍按预期运行。 使用单元测试,可在每次生成后,甚至在更改一行代码后重新运行整套测试。 让你确信新代码不会破坏现有功能。 - **减少耦合代码** 当代码紧密耦合时,可能难以进行单元测试。 如果不为正在编写的代码创建单元测试,耦合度可能不太明显。 为代码编写测试会自然地解耦代码,因为采用其他方法测试会更困难。 ### 术语 - Fake:捏造、伪造、假货,通常和real相对应,也即真和伪的区别 > Fake:可以理解为假冒伪劣品……也即伪装成跟真的一样,但其实并不一样 - Mock:嘲笑、假装、模仿 > 主要体现在模仿和操纵之上,也即伪造成真身(好比盗用他人身份证件或大学录取通知书),可以根据操纵者的心愿表现出和被模仿者不一样的行为。有驾驭的意思。 - Stub:桩 > 主要是体现在被测功能或代码需要检查或调用其他功能或代码之时,提供一个虚假存在的“其他功能或代码”,让被测功能或代码有一种“恩,它在”的感觉即可。桩也可以接收被测功能给它发出来的信息,但并不会做出任何的回应。这个词汇通常用在自上而下方式集成测试时,用来指代被测功能下层被调用功能或代码的虚假品。可以认为它就跟稻草人、木头人差不多德性,或者把它看做是貔貅,只进不出。 在软件测试中,Stub和Mock是两种非常重要的测试辅助工具。它们可以有效地模拟或替代一些依赖项,以便更好地进行单元测试 、功能测试或集成测试等各种测试。 > Stub(桩对象):Stub通常用于替代测试 对象的某些部分,以便进行单元测试等测试。例如,当被测代码需要访问外部数据源或者调用其他函数时,我们可以使用Stub来模拟这些依赖项的行为,从而使得测试过程更加独立和可控。 > Mock(模拟对象):Mock通常用于模拟函数或对象的行为,以便更好地进行单元测试或功能测试。例如,当被测代码需要与某个对象进行交互时,我们可以使用Mock来模拟该对象的行为和响应,并判断被测代码的行为是否正确。 总之,Stub和Mock是测试中非常重要的工具,它们可以帮助我们更好地进行测试,并提高测试的覆盖率和质量。但是需要注意,在使用Stub和Mock时,需要确保其模拟的行为和实际依赖项或对象的行为一致,以免影响测试结果的准确性。 ### 常见模式 “Arrange、Act、Assert”是单元测试时的常见模式。 顾名思义,它包含三个主要操作: 安排对象,根据需要对其进行创建和设置。 作用于对象。 断言某些项按预期进行。 ``` [Fact] public void Add_EmptyString_ReturnsZero() { // Arrange var stringCalculator = new StringCalculator(); // Act var actual = stringCalculator.Add(""); // Assert Assert.Equal(0, actual); } ``` ### spring boot中的单元测试 #### Mockito 在软件开发和测试中,Mockito 是一个开源的、常用的 Java 框架,专门用于创建和验证测试中的模拟对象(mocks)。Mockito 允许开发者模拟方法调用,以便于在编写单元测试时,可以隔离测试目标对象,忽略外部依赖项。 ```java import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) public class UserServiceImplTest { @Mock private UserRepository userRepository; @InjectMocks private UserServiceImpl userService; private User testUser; @BeforeEach public void setUp() { // 初始化测试数据 testUser = new User(1L, "John Doe", "john@example.com"); } @Test public void testGetUserById() { // 配置Mock行为 when(userRepository.findById(testUser.getId())).thenReturn(Optional.of(testUser)); // 执行测试方法 User result = userService.getUserById(testUser.getId()); // 验证结果 assertEquals(testUser, result); } } ``` @InjectMocks 和 @Mock 是 Mockito 框架中的两个注解,它们在单元测试中用于创建对象和模拟行为。以下是它们的主要区别: 目的: - @Mock:用于创建一个模拟对象(mock object)。这个模拟对象不会执行实际的方法,而是返回预定义的值或者抛出预期的异常。它允许测试者控制方法的行为和验证方法调用。 - @InjectMocks:用于创建一个被测试类的实例(通常称为“被测对象”或“系统组件”)。这个注解告诉 Mockito 自动注入在同一个测试类中声明的 @Mock 或 @Spy 对象。 对象创建: - @Mock:创建一个全新的模拟对象,不依赖于任何其他对象。 - @InjectMocks:创建一个被测试类的实例,如果该类有依赖,那么这些依赖可以通过 @Mock 或 @Spy 注解的实例来注入。 依赖注入: -@Mock 不会自动注入到任何地方,除非手动设置。 - @InjectMocks 会查找并注入与其类型匹配的 @Mock 或 @Spy 对象作为依赖。 使用场景: - @Mock 通常用于测试中不需要实际行为的依赖对象。 - @InjectMocks 用于测试主要的业务逻辑类,它的依赖可以是模拟的,也可以是真实的(如果没有指定模拟,则使用默认构造函数创建)。 初始化: - 使用 @RunWith(MockitoJUnitRunner.class) 或在测试方法前调用 MockitoAnnotations.initMocks(this) 是为了初始化这些注解标记的模拟对象。 在编写单元测试时,通常会结合使用 @Mock 和 @InjectMocks,@Mock 用来创建模拟依赖,而 @InjectMocks 用来创建包含这些模拟依赖的被测对象实例。这样可以确保在测试中控制依赖的行为,同时隔离被测代码。 ## 参考 [微软:.NET Core 和 .NET Standard 单元测试最佳做法](https://learn.microsoft.com/zh-cn/dotnet/core/testing/unit-testing-best-practices) [AWS: 什么是单元测试?](https://aws.amazon.com/cn/what-is/unit-testing/) [单元测试实战(四种覆盖详解、测试实例)](https://www.cnblogs.com/csonezp/p/11757967.html)