Java单元测试之道

Java单元测试之道

Posted by ZBX on May 16, 2024

FIRST Properties of Good Tests

following problems with your tests:

• Tests that make little sense to someone following them

• Tests that fail sporadically

• “Tests” that don’t prove anything worthwhile

• Tests that require a long time to execute

• Tests that don’t sufficiently cover the code

• Tests that couple too tightly to implementation, meaning that small changes break lots of tests all at once

• Convoluted tests that jump through numerous setup hoops

Types of Testing

Spring Boot applications can be tested at various levels, including:

  1. Unit Testing: Testing individual components, such as classes or methods, in isolation.
  2. Integration Testing: Verifying that different components or services work correctly together.
  3. Functional Testing: Testing the application’s functionality from the user’s perspective.
  4. End-to-End Testing: Testing the entire application, including its external dependencies, in a production-like environment.

Unit Test

单元测试是测试驱动开发 (TDD) 的基础。在 TDD 中,首先编写失败的测试,然后编写代码以满足测试,然后通过重构代码和应用模式来提高代码质量。所以单元测试驱动设计。他们减少了工程,因为代码只编写以满足失败的测试。自动化测试为重构和新功能提供了快速的回归安全网络

Spring Test - Integration Testing

Leverage Spring Boot’s Testing Annotations

  1. Use @SpringBootTest to load the entire Spring context.
@SpringBootTest
@AutoConfigureMockMvc
class TestingWebApplicationTest {

	@Autowired
	private MockMvc mockMvc;

	@Test
	void shouldReturnDefaultMessage() throws Exception {
		this.mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk())
				.andExpect(content().string(containsString("Hello, World")));
	}
}
  1. Use MockMvc to simulate HTTP requests and test the response from your controller. Use @AutoConfigureMockMvc to automatically configure MockMvc

  2. Use @MockBean to replace a real bean with a mock implementation.

    @Mock vs @MockBean:

    @Mock is used for unit testing and mocking objects outside of the Spring context, while @MockBean is used for Spring Boot integration testing to replace real beans with mock or spy versions within the Spring application context.

  3. 当使用spy时,考虑使用doReturn|Answer|Throw(),spy默认所有方法均真实调用

    与使用@MockBean不同,上节中调用doReturn("").when(testService).doSomething()doSomething方法被打桩。而when(testService.doSomething()).thenReturn("")则达不到此效果。原因是:使用@SpyBean修饰的testService是一个真实对象,所以testService.doSomething()会被真实调用。

    spy修饰的变量,Mockito会重新创建一个实例的copy,并不直接作用于真实实例

    为测试主体类部分打桩考虑使用SpyBean, 为外部依赖打桩,考虑使用MockBean

  4. Use @DataJpaTest to test repositories with an embedded database.

  5. Use @AutoConfigureTestDatabase to configure the test database.

  6. Use @BeforeEach and @AfterEach to set up and tear down test fixtures. ``` @BeforeEach public void setUp() { // Initialize the EmployeeService or set up resources if needed employeeService = new EmployeeService();
    }

@AfterEach public void tearDown() { // Clean up resources or perform other cleanup tasks employeeService = null; }

## Test Configuration
1. Use @TestConfiguration to provide additional beans for testing.
	i. Create a static inner class in the same test class where we want to autowire the bean.
	ii. Create a separate test configuration class and import it using the @Import annotation.

2. Use @ConfigurationProperties to inject properties from a configuration file.
3. Use @ActiveProfiles to activate a specific profile for testing when you have multiple profiles.
4. Use @DynamicPropertySource to set dynamic configuration properties for tests.
5. Use @DirtiesContext to reset the Spring context after a test.
   1. @DirtiesContext通常用于测试方法对Spring应用程序上下文有副作用,不能通过常规事务或回滚机制撤消的情况。
   2. 就性能而言,重置应用程序上下文的代价相对较高,因此要谨慎使用。
6. Use RestTemplate to make HTTP requests.
7. Use @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) to start the server on a random port. Use TestRestTemplate to make HTTP requests and assert responses.
8. Use @Sql to execute SQL scripts before and after a test.
9. Use @Disabled to disable a test temporarily.
10. Use @RepeatedTest to repeat a test a specified number of times.


# Testing Frameworks
There are several testing tools commonly used in Spring Boot applications, including:

- JUnit: A widely-used testing framework for Java.
- Mockito: A mocking framework for creating mock objects in tests.
- Testcontainers: Provides lightweight, throwaway instances of common databases or services for testing.
- Spring Boot Test: Offers annotations and utilities for testing Spring Boot applications.
- RestAssured: A Java DSL for simplifying the testing of REST services.
- Selenium: Used for web application testing, especially end-to-end testing.
- Jacoco: A code coverage analysis tool that helps you identify areas of your codebase that need more testing.
- WireMock: A tool for mocking HTTP services, useful for testing external service interactions.



## 并发测试

#是否允许并行执行true/false junit.jupiter.execution.parallel.enabled=true #是否支持方法级别多线程same_thread/concurrent junit.jupiter.execution.parallel.mode.default=concurrent #是否支持类级别多线程same_thread/concurrent junit.jupiter.execution.parallel.mode.classes.default=concurrent

the maximum pool size can be configured using a ParallelExecutionConfigurationStrategy

junit.jupiter.execution.parallel.config.strategy=fixed junit.jupiter.execution.parallel.config.fixed.parallelism=5


## 参数化测试

```java
@ParameterizedTest(name = "{0} is expected to output {1}")
@MethodSource("fizzBuzzTestCases")
public void fizzBuzz(int input, string expected) {
    assertEquals(expected, underTest.fizzBuzz(input));
}

private static Stream<Arguments> fizzBuzzTestCases() {
    return Stream.of(
      Arguments.of(named.of(1, "1 - not divisible by 3 or 5"), "1"),
      Arguments.of(named.of(2, "2 - not divisible by 3 or 5"), "2"),
      Arguments.of(named.of(3, "3 - divisible by 3"), "Fizz"),
      Arguments.of(named.of(4, "4 - not divisible by 3 or 5"), "4"),
      Arguments.of(named.of(5, "5 - divisible by 5"), "Buzz"),
      Arguments.of(named.of(6, "6 - divisible by 3"), "Fizz"),
      Arguments.of(named.of(10, "10 - divisible by 5"), "Buzz"),
      Arguments.of(named.of(11, "11 - not divisible by 3 or 5"), "11"),
      Arguments.of(named.of(15, "15 - divisible by both 3 and 5"), "FizzBuzz")
    );
}