使用单元测试框架Junit的正确姿势~

695 阅读14分钟

大家好,我是G探险者。

不知道大家平时开发出来的功能,写不写单元测试?反正我是不咋写,写个单元测试有时候mock比开发实际功能都难搞,谁写谁懂得。

image.png

没办法,由于甲方爸爸的要求,我们必须得写单元测试,并且要配备相应的单元测试报告。惭愧啊,写个单元测试让我掉了不少头发。

image.png

今天我就把写单元测试的一些流程梳理一下,供大家做个参考,让大家少踩点坑。

1. 什么是单元测试?

单元测试是软件开发中的一种测试方法,旨在验证代码中的最小可测试单元(通常是函数、方法或类)是否按照预期进行了设计、实现和行为。在单元测试中,开发者编写测试用例来测试代码的各种情况和边界条件,然后运行这些测试用例以验证代码的正确性。

单元测试的主要特点包括:

  1. 独立性:每个单元测试应该是独立的,不依赖于其他测试用例的执行结果。

  2. 自动化:单元测试应该可以自动运行,开发者不需要手动进行测试。

  3. 快速:单元测试应该能够快速执行,以便在开发过程中频繁地运行。

  4. 精确性:单元测试应该能够准确地验证代码的行为,覆盖各种情况和边界条件。

通过编写和执行单元测试,开发者可以提高代码的质量和稳定性,及早发现并修复潜在的问题,同时也增强了代码的可维护性和可扩展性。

2. 常见的java语言单元测试框架有哪些?

  1. JUnit:Java语言最常用的单元测试框架之一。它提供了一组断言方法来验证代码的行为,并支持测试的组织和运行。

  2. Spring Boot Test:针对Spring Boot应用程序的测试支持。它提供了一组注解和工具,用于编写和运行与Spring Boot应用程序相关的单元测试、集成测试和端到端测试。

  3. TestNG:另一个流行的Java单元测试框架,与JUnit类似,但提供了更多的功能和灵活性,如参数化测试、测试分组、依赖测试等。

  4. Mockito:用于Java的模拟框架,用于模拟和验证对象的行为。Mockito允许你创建模拟对象,并定义它们的行为和交互。

  5. PowerMock:扩展了其他模拟框架(如Mockito和EasyMock)的功能,以提供更多的模拟能力,如静态方法和final类的模拟。

  6. Spock:基于Groovy语言的测试框架,结合了JUnit、Mockito和其他测试框架的功能,并提供了更简洁和易读的语法。

  7. AssertJ:一个流畅的断言库,提供了丰富的断言方法,可以编写清晰、易读的测试代码。

  8. EasyMock:另一个Java模拟框架,用于创建模拟对象和定义它们的行为,相比Mockito,EasyMock在一些特定场景下可能更适用。

  9. WireMock:用于模拟HTTP服务的框架,可以帮助开发者进行对外部HTTP服务的集成测试。

  10. Arquillian:用于Java EE应用程序的集成测试框架,可以在真实的容器环境中运行测试,支持各种Java EE组件的测试。

这些框架在Java开发中广泛应用于单元测试、集成测试和功能测试等不同类型的测试场景,开发者可以根据具体需求选择合适的框架进行测试。

这些单元测试框架都有其各自的特点和适用场景。 下面是关于几种常见的单元测试框架的简要对比:

框架语言主要特点适用场景依赖
JUnitJava- 提供了一组断言方法来验证代码行为
- 支持测试的组织和运行
- 单元测试junit:junit
Spring Boot TestJava- 针对Spring Boot应用程序的测试支持- Spring Boot应用程序的单元测试、集成测试、端到端测试spring-boot-starter-test
TestNGJava- 支持更多功能和灵活性,如参数化测试、测试分组、依赖测试等- 单元测试、集成测试org.testng:testng
MockitoJava- 用于模拟和验证对象的行为- 单元测试、模拟测试、验证测试org.mockito:mockito-core
PowerMockJava- 扩展了其他模拟框架的功能,支持更多模拟能力- 单元测试、模拟静态方法和final类等场景org.powermock:powermock-api-mockito2:2.0.0
SpockGroovy- 结合了JUnit、Mockito等框架的功能
- 提供了更简洁和易读的语法
- 单元测试、集成测试org.spockframework:spock-core
AssertJJava- 提供丰富的断言方法,编写清晰易读的测试代码- 单元测试org.assertj:assertj-core
EasyMockJava- 用于创建模拟对象和定义行为- 单元测试、模拟测试org.easymock:easymock
WireMockJava- 用于模拟HTTP服务- 集成测试、功能测试com.github.tomakehurst:wiremock
ArquillianJava EE- 用于Java EE应用程序的集成测试,支持真实容器环境- Java EE应用程序的集成测试org.jboss.arquillian:junit-container

以上扯的有点多,很多我们用不上

image.png

重点关注JUint、 Mockito、PowerMock、 Spring Boot Test就行。

3. JUint、 Mockito、PowerMock、 Spring Boot Test 的区别,如何选?如何搭配使用?

Spring Boot Test 和 JUnit 是两种不同的测试框架,它们之间有一些区别,主要体现在以下几个方面:

  1. 功能范围

    • JUnit:是一个通用的Java单元测试框架,用于编写和运行单元测试。它提供了一组断言方法和注解,用于验证代码的行为和组织测试。
    • Spring Boot Test:是Spring Boot框架提供的专门用于测试Spring Boot应用程序的测试框架。它建立在JUnit之上,提供了额外的功能和注解,用于编写和运行与Spring Boot应用程序相关的单元测试、集成测试和端到端测试。
  2. 集成环境

    • JUnit:可以与任何Java项目一起使用,不限于Spring Boot。
    • Spring Boot Test:专门针对Spring Boot应用程序设计,提供了特定于Spring Boot的测试支持,包括自动配置、内存数据库、Mock对象等。
  3. 注解

    • JUnit:提供了一些基本的注解,如 @Test 用于标记测试方法,@Before@After 用于在测试方法执行前后执行一些准备和清理操作等。
    • Spring Boot Test:除了继承了JUnit的注解外,还提供了一些Spring Boot特有的注解,如 @SpringBootTest 用于加载Spring Boot应用程序上下文,@MockBean 用于模拟Bean对象等。
  4. 功能扩展

    • JUnit:提供了基本的单元测试功能,可以通过集成其他库来扩展功能,如Mockito用于模拟对象,AssertJ用于更丰富的断言等。
    • Spring Boot Test:除了提供基本的单元测试功能外,还集成了许多Spring Boot特有的功能,如自动配置、内存数据库(如H2)、Spring Boot测试切片等。

下面是关于Spring Boot Test 和 JUnit 的简要对比:

特点Spring Boot TestJUnit
用途用于测试Spring Boot应用程序的单元测试、集成测试和端到端测试通用的Java单元测试框架
扩展集成了Spring Boot特有的功能,如自动配置、内存数据库、测试切片等提供基本的单元测试功能,可以通过集成其他库来扩展
注解提供了Spring Boot特有的注解,如 @SpringBootTest、@MockBean 等提供了基本的注解,如 @Test、@Before、@After 等
集成环境专门针对Spring Boot应用程序设计可以与任何Java项目一起使用
功能范围提供了额外的功能,如测试Spring Boot应用程序的自动配置、集成等提供基本的单元测试功能
选择建议适用于Spring Boot项目,测试Spring Boot应用程序的功能适用于普通Java项目,进行基本的单元测试

这些是 Spring Boot Test 和 JUnit 的一些主要区别和特点。在选择使用哪个框架时,应该根据项目的需求和特点来决定。

至于Mockito和PowerMock如何选择和搭配请参照如下:

框架主要特点如何选?如何搭配使用?
Mockito- 用于模拟和验证对象的行为- 适用于需要模拟对象行为的场景通常与JUnit或Spring Boot Test一起使用进行单元测试
PowerMock- 扩展了其他模拟框架的功能,支持更多模拟能力- 适用于需要模拟静态方法、final类等的场景通常与JUnit和Mockito一起使用来扩展模拟功能

小结

  • 如果你正在开发一个普通的Java项目,并且只需要进行基本的单元测试,那么使用JUnit是一个不错的选择。
  • 如果你正在开发一个Spring Boot应用程序,并且需要测试Spring Boot的自动配置、集成以及与Spring框架相关的功能,那么Spring Boot Test是更合适的选择。
  • 在Spring Boot项目中,通常可以使用JUnit和Spring Boot Test结合使用,使用JUnit编写常规的单元测试,使用Spring Boot Test进行Spring Boot相关的集成测试。
  • 如果你需要模拟对象的行为以及验证对象的交互,可以选择搭配使用Mockito。
  • 如果你的项目中有需要模拟静态方法、final类等特殊情况,可以选择搭配使用PowerMock。

4. 常用的注解及作用

下面是关于JUnit、Mockito、PowerMock和Spring Boot Test注解的对比表格:

框架注解主要作用
JUnit@Test标记测试方法
@Before在每个测试方法执行之前执行,用于初始化测试环境
@After在每个测试方法执行之后执行,用于清理测试环境
@BeforeClass在测试类加载时执行,只执行一次,用于初始化静态资源
@AfterClass在测试类执行完成后执行,只执行一次,用于清理静态资源
@Ignore标记测试方法为忽略测试,暂时跳过执行
Mockito@Mock创建模拟对象
@Spy创建部分模拟对象,部分方法保留原始行为
@InjectMocks注入模拟对象或部分模拟对象到被测试对象中
PowerMock@PrepareForTest准备被测试类,以支持对静态方法、final类、构造函数等的模拟
Spring Boot Test@SpringBootTest加载Spring应用程序上下文,以便在测试中使用Spring组件
@MockBean创建Spring应用程序上下文中的模拟Bean
@Autowired自动注入Spring组件
@RunWith(SpringRunner.class)指定使用SpringRunner来运行测试类

使用示例:

JUnit注解:

  1. @Test:标记测试方法。
@Test
public void testAddition() {
    Calculator calculator = new Calculator();
    int result = calculator.add(3, 4);
    assertEquals(7, result);
}
  1. @Before:在每个测试方法执行之前执行,用于初始化测试环境。
@Before
public void setUp() {
    // 初始化测试环境
}
  1. @After:在每个测试方法执行之后执行,用于清理测试环境。
@After
public void tearDown() {
    // 清理测试环境
}
  1. @BeforeClass:在测试类加载时执行,只执行一次,用于初始化静态资源。
@BeforeClass
public static void setUpClass() {
    // 初始化静态资源
}
  1. @AfterClass:在测试类执行完成后执行,只执行一次,用于清理静态资源。
@AfterClass
public static void tearDownClass() {
    // 清理静态资源
}
  1. @Ignore:标记测试方法为忽略测试,暂时跳过执行。
@Ignore
public void testThisMethodWillBeIgnored() {
    // 这个测试方法将被忽略
}

Mockito注解:

  1. @Mock:创建模拟对象。
@Mock
private CalculatorService calculatorService;
  1. @Spy:创建部分模拟对象,部分方法保留原始行为。
@Spy
private List<String> list = new ArrayList<>();
  1. @InjectMocks:注入模拟对象或部分模拟对象到被测试对象中。
@InjectMocks
private CalculatorController calculatorController;

PowerMock注解:

  1. @PrepareForTest:准备被测试类,以支持对静态方法、final类、构造函数等的模拟。
@PrepareForTest(Calculator.class)
public class CalculatorTest {
    // 测试代码
}

Spring Boot Test注解:

  1. @SpringBootTest:加载Spring应用程序上下文,以便在测试中使用Spring组件。
@SpringBootTest
public class MySpringBootTest {
    // 测试代码
}
  1. @MockBean:创建Spring应用程序上下文中的模拟Bean。
@MockBean
private CalculatorService calculatorService;
  1. @Autowired:自动注入Spring组件。
@Autowired
private CalculatorController calculatorController;
  1. @RunWith(SpringRunner.class):指定使用SpringRunner来运行测试类。
@RunWith(SpringRunner.class)
public class MySpringBootTest {
    // 测试代码
}

以上示例展示了每个注解在不同测试框架中的用法及其功能。

啰啰嗦嗦说了一堆,重点来了,你就直接给我说再写吧

image.png

客官别急,我懂

image.png

5. 几个完整的单元测试示例

controller的单元测试:

import com.fasterxml.jackson.databind.ObjectMapper;
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.mockito.junit.MockitoJUnitRunner;
import org.springframework.http.MediaType;
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.setup.MockMvcBuilders;

import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(MockitoJUnitRunner.class)
public class ControllerUnitTestTemplate {

    private MockMvc mockMvc;

    @Mock
    private ServiceClassTemplate service; // 如果需要,模拟您的服务

    @InjectMocks
    private ControllerClassTemplate controller;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
    }

    @Test
    public void testExampleMethod() throws Exception {
        // 准备请求数据
        RequestClassTemplate request = new RequestClassTemplate();
        // 根据需要设置请求对象的属性

        // 发送请求并验证响应状态
        mockMvc.perform(MockMvcRequestBuilders.post("/endpoint/path")
                        .content(asJsonString(request))
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk());
    }

    // 将对象转换为JSON字符串的实用方法
    private static String asJsonString(final Object obj) {
        try {
            return new ObjectMapper().writeValueAsString(obj);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

您只需将 ControllerUnitTestTemplateServiceClassTemplateControllerClassTemplatetestExampleMethod()RequestClassTemplate 替换为实际的类名和方法名,以及将 "/endpoint/path" 替换为要测试的实际接口路径。

service单元测试

@SpringBootTest(classes = {ExampleApplication.class})
@RunWith(PowerMockRunner.class)
public class ExampleServiceTest {
    @InjectMocks
    private ExampleService exampleService; // 被测试的服务对象

    @Mock
    private ExampleDao exampleDao; // 模拟的 DAO 对象

    @Test
    public void testSaveExample() throws Exception{
        // 准备测试数据
        DTOExample dtoExample = new DTOExample();
        ExampleEntity exampleEntity = new ExampleEntity();
        exampleEntity.setProperty("value");
        List<ExampleEntity> exampleEntityList = new ArrayList<>();
        exampleEntityList.add(exampleEntity);
        dtoExample.setExamples(exampleEntityList);

        // 模拟 DAO 对象的行为
        PowerMockito.doReturn(1).when(exampleDao).updateObject(dtoExample);

        // 执行被测试的方法
        exampleService.saveExample(dtoExample);
    }

    @Test
    public void testUpdateExample() throws Exception{
        // 准备测试数据
        DTOExample dtoExample = new DTOExample();
        ExampleEntity exampleEntity = new ExampleEntity();
        exampleEntity.setProperty("value");
        List<ExampleEntity> exampleEntityList = new ArrayList<>();
        exampleEntityList.add(exampleEntity);
        dtoExample.setExamples(exampleEntityList);

        // 模拟 DAO 对象的行为
        PowerMockito.doReturn(1).when(exampleDao).updateObject(dtoExample);

        // 执行被测试的方法
        exampleService.updateExample(dtoExample);
    }

    @Test
    public void testGetExampleData() throws Exception{
        // 准备测试数据
        DTOExample dtoExample = new DTOExample();
        ExampleEntity exampleEntity = new ExampleEntity();
        exampleEntity.setProperty("value");
        List<ExampleEntity> exampleEntityList = new ArrayList<>();
        exampleEntityList.add(exampleEntity);
        dtoExample.setExamples(exampleEntityList);

        // 模拟 DAO 对象的行为
        ExampleEntity exampleEntityResponse = new ExampleEntity();
        PowerMockito.doReturn(exampleEntityResponse).when(exampleDao).getObjectById("");

        // 执行被测试的方法
        exampleService.getExampleData("");
    }
}

6. 除了单元测试外,还有哪些类型的测试?

除了单元测试,还有许多其他类型的测试,其中一些主要类型包括:

  1. 集成测试(Integration Testing):测试不同组件之间的集成和交互,确保它们在一起工作时没有问题。集成测试可以测试模块、服务、子系统或整个应用程序的集成。

  2. 功能测试(Functional Testing):测试应用程序的功能是否按照规范和需求工作。功能测试通常从用户的角度出发,验证应用程序是否满足用户的功能要求。

  3. 验收测试(Acceptance Testing):通过验证应用程序是否符合业务需求和用户期望来确认项目交付的可接受性。验收测试通常由最终用户、产品所有者或业务代表执行。

  4. 回归测试(Regression Testing):在对代码进行更改之后,重新运行之前的测试,以确保新更改没有破坏现有功能。回归测试有助于捕获由于代码更改引入的新错误或导致的不良影响。

  5. 性能测试(Performance Testing):评估应用程序在各种条件下的性能和响应能力。性能测试可以包括负载测试、压力测试、并发测试等,以确定应用程序在正常和峰值负载下的性能表现。

  6. 安全测试(Security Testing):评估应用程序的安全性,检测和修复潜在的安全漏洞和弱点。安全测试可以包括漏洞扫描、渗透测试、认证和授权测试等。

  7. 可用性测试(Usability Testing):评估应用程序的用户界面和交互设计,以确保用户能够轻松、高效地使用应用程序。

  8. 兼容性测试(Compatibility Testing):评估应用程序在不同平台、设备、浏览器和操作系统下的兼容性和一致性。

  9. 非功能测试(Non-functional Testing):非功能测试包括各种类型,如性能测试、安全测试、可用性测试、兼容性测试等,以及特定于项目的测试类型,例如可扩展性测试、可靠性测试、容错性测试等。这些测试类型旨在评估软件系统在特定方面的表现和质量,并确保其符合用户的期望和需求。通过进行非功能测试,软件团队可以发现和解决潜在的性能问题、安全漏洞、用户体验问题等,从而提高软件系统的整体质量和可靠性。非功能测试通常与功能测试结合使用,以全面评估软件系统的质量和性能。 这些测试类型通常结合使用,以确保软件在各个方面的质量和稳定性。

~以上就介绍到这里吧,主要还是根据实际情况灵活运用哦。