一、测试金字塔原则
1. 测试分层策略
java
复制
下载
// 理想的测试比例:70%单元测试 + 20%集成测试 + 10%端到端测试
public class TestPyramid {
// Level 1: 单元测试(最底层,数量最多)
class UnitTests {
// 测试单个类或方法
// 快速、隔离、可重复
@Test
void shouldCalculateTotalPrice() {
Order order = new Order();
order.addItem(new Item("Book", 29.99, 2));
double total = order.calculateTotal();
assertEquals(59.98, total, 0.01);
}
}
// Level 2: 集成测试(中间层)
class IntegrationTests {
// 测试多个组件协作
// 涉及数据库、外部服务等
@Test
void shouldSaveAndRetrieveOrder() {
Order order = createOrder();
orderRepository.save(order);
Order retrieved = orderRepository.findById(order.getId());
assertNotNull(retrieved);
}
}
// Level 3: 端到端测试(最顶层)
class E2ETests {
// 测试完整业务流程
// 模拟真实用户操作
@Test
void shouldCompletePurchaseFlow() {
// 模拟浏览器操作
// 涉及UI、API、数据库全链路
}
}
}
二、测试代码质量标准
1. 测试命名规范
java
复制
下载
// 使用 Given-When-Then 或 Should-When 模式命名
class NamingConventions {
// 模式1:Given-When-Then(BDD风格)
@Test
void givenValidOrder_whenCalculateTotal_thenReturnCorrectAmount() {
// Given
Order order = createValidOrder();
// When
double total = order.calculateTotal();
// Then
assertEquals(100.0, total, 0.01);
}
// 模式2:Should-When(简洁风格)
@Test
void shouldReturnCorrectTotal_whenOrderHasItems() {
// 同上
}
// 模式3:MethodName_Scenario_ExpectedResult
@Test
void calculateTotal_WithMultipleItems_ReturnsSum() {
// 同上
}
// 要避免的命名
@Test
void test1() { /* ❌ 无意义 */ }
@Test
void testCalculate() { /* ❌ 不够具体 */ }
@Test
void calculateTest() { /* ❌ 同上 */ }
}
2. 测试结构:AAA模式
java
复制
下载
class AAAPattern {
@Test
void shouldApplyDiscountForVIPCustomer() {
// Arrange: 准备测试数据
Customer vipCustomer = new Customer();
vipCustomer.setVip(true);
Order order = new Order(vipCustomer);
order.addItem(new Item("Product", 100.0, 1));
// Act: 执行被测试的方法
double finalPrice = order.applyDiscount();
// Assert: 验证结果
assertEquals(90.0, finalPrice, 0.01); // VIP 9折
}
// 复杂的Arrange可以使用Builder模式
@Test
void shouldCalculateShippingCost() {
// Arrange
Order order = OrderBuilder.anOrder()
.withCustomer(CustomerBuilder.aCustomer()
.withAddress("Shanghai")
.build())
.withItem(ItemBuilder.anItem()
.withWeight(2.5)
.build())
.build();
ShippingCalculator calculator = new ShippingCalculator();
// Act
double shippingCost = calculator.calculate(order);
// Assert
assertTrue(shippingCost > 0);
assertEquals(25.0, shippingCost, 0.01);
}
}
三、Mock与Stub的最佳实践
1. 什么时候Mock?
java
复制
下载
class MockingGuidelines {
// ✅ 适合Mock的场景
@Test
void shouldSendEmail_whenOrderIsConfirmed() {
// Arrange
EmailService emailService = mock(EmailService.class);
OrderService orderService = new OrderService(emailService);
Order order = createOrder();
// Act
orderService.confirmOrder(order);
// Assert
verify(emailService).sendConfirmation(order); // 验证交互
}
// ✅ 外部依赖
@Test
void shouldHandleApiFailure_gracefully() {
// Arrange
PaymentGateway gateway = mock(PaymentGateway.class);
when(gateway.process(any())).thenThrow(new ApiException("Timeout"));
PaymentService service = new PaymentService(gateway);
// Act & Assert
assertThrows(PaymentException.class, () -> {
service.processPayment(createPayment());
});
}
// ❌ 不应该Mock的场景
@Test
void shouldNotMockValueObjects() {
// Value Object(值对象)不应该Mock
Money money = mock(Money.class); // ❌ 错误做法
when(money.add(any())).thenReturn(new Money(100));
// 正确做法:使用真实对象
Money money1 = new Money(50);
Money money2 = new Money(50);
Money result = money1.add(money2); // ✅ 测试真实行为
}
// ❌ 不要Mock被测类
@Test
void shouldNotMockClassUnderTest() {
OrderService service = mock(OrderService.class); // ❌ 错误
when(service.processOrder(any())).thenReturn(true);
// 这样测试没有意义,只是在测试Mock框架
}
}
2. Mock vs Stub vs Spy
java
复制
下载
class TestDoublesComparison {
// Mock: 验证交互
@Test
void mockExample() {
EmailService mockService = mock(EmailService.class);
OrderService service = new OrderService(mockService);
service.processOrder(createOrder());
verify(mockService).sendConfirmation(any()); // 验证是否被调用
}
// Stub: 提供预设响应
@Test
void stubExample() {
InventoryService stubService = mock(InventoryService.class);
// 预设行为
when(stubService.isAvailable(anyString())).thenReturn(true);
when(stubService.getStock("item1")).thenReturn(10);
OrderService service = new OrderService(stubService);
// 测试使用stub提供的数据
boolean canOrder = service.canPlaceOrder("item1", 5);
assertTrue(canOrder);
}
// Spy: 部分Mock,监控真实对象
@Test
void spyExample() {
List<String> realList = new ArrayList<>();
List<String> spyList = spy(realList); // 基于真实对象
// 可以修改部分行为
doReturn(false).when(spyList).isEmpty();
// 其他方法保持真实行为
spyList.add("item");
assertEquals(1, spyList.size());
// 验证交互
verify(spyList).add("item");
}
}
四、测试数据管理
1. 测试数据准备模式
java
复制
下载
class TestDataManagement {
// 1. 内联数据(简单场景)
@Test
void shouldCalculateSimpleTotal() {
// Arrange
Order order = new Order();
order.addItem(new Item("Book", 29.99, 1));
// Act & Assert
assertEquals(29.99, order.calculateTotal(), 0.01);
}
// 2. 提取到方法(中等复杂度)
@Test
void shouldApplyVIPDiscount() {
// Arrange
Order order = createVIPOrderWithTwoItems();
// Act & Assert
assertEquals(180.0, order.applyDiscount(), 0.01);
}
private Order createVIPOrderWithTwoItems() {
Customer vip = new Customer();
vip.setVip(true);
Order order = new Order(vip);
order.addItem(new Item("Item1", 100.0, 1));
order.addItem(new Item("Item2", 100.0, 1));
return order;
}
// 3. Builder模式(复杂对象)
@Test
void shouldCalculateShippingForInternationalOrder() {
// Arrange
Order order = OrderBuilder.anOrder()
.withCustomer(customer -> customer
.withAddress(AddressBuilder.anAddress()
.withCountry("US")
.build()))
.withItems(item -> item
.withName("Product")
.withWeight(5.0)
.withQuantity(2))
.build();
// Act & Assert
double shipping = shippingCalculator.calculate(order);
assertTrue(shipping > 50);
}
// 4. Object Mother模式(共享测试数据)
class OrderMother {
public static Order createStandardOrder() {
return OrderBuilder.anOrder()
.withCustomer(CustomerMother.createStandardCustomer())
.withItem(ItemMother.createBook())
.build();
}
public static Order createInternationalOrder() {
return OrderBuilder.anOrder()
.withCustomer(CustomerMother.createInternationalCustomer())
.withItem(ItemMother.createHeavyProduct())
.build();
}
}
@Test
void shouldCalculateTaxForInternationalOrder() {
// Arrange
Order order = OrderMother.createInternationalOrder();
// Act & Assert
double tax = taxCalculator.calculate(order);
assertEquals(15.0, tax, 0.01);
}
// 5. 测试数据工厂
class TestDataFactory {
private static int nextId = 1;
public static Order createOrder(String customerName, double amount) {
Order order = new Order();
order.setId(nextId++);
order.setCustomer(createCustomer(customerName));
order.addItem(createItem(amount));
return order;
}
private static Customer createCustomer(String name) {
Customer customer = new Customer();
customer.setName(name);
customer.setEmail(name.toLowerCase() + "@example.com");
return customer;
}
}
}
五、断言最佳实践
1. 有意义的断言消息
java
复制
下载
class AssertionBestPractices {
@Test
void shouldReturnValidUser_whenUserExists() {
// Arrange
UserRepository repository = new UserRepository();
User expectedUser = new User("john", "john@example.com");
repository.save(expectedUser);
// Act
User actualUser = repository.findByUsername("john");
// Assert - 提供有意义的错误消息
assertNotNull(actualUser, "User should not be null when username exists");
assertEquals("john", actualUser.getUsername(),
"Username should match the requested username");
assertEquals("john@example.com", actualUser.getEmail(),
"Email should be correctly retrieved from database");
}
// 使用Hamcrest或AssertJ提供更丰富的断言
@Test
void shouldReturnUserWithCorrectProperties() {
User user = userService.getUser(1);
// JUnit 5 原生断言
assertAll("User properties",
() -> assertEquals("John", user.getFirstName()),
() -> assertEquals("Doe", user.getLastName()),
() -> assertTrue(user.isActive(), "User should be active"),
() -> assertNotNull(user.getCreatedAt())
);
// AssertJ(更可读)
assertThat(user)
.isNotNull()
.hasFieldOrPropertyWithValue("firstName", "John")
.hasFieldOrPropertyWithValue("lastName", "Doe")
.extracting(User::getEmail)
.isEqualTo("john@example.com");
// Hamcrest
assertThat(user, allOf(
hasProperty("firstName", equalTo("John")),
hasProperty("lastName", equalTo("Doe")),
hasProperty("active", is(true))
));
}
// 测试异常
@Test
void shouldThrowException_whenUserNotFound() {
UserRepository repository = new UserRepository();
// 方法1:JUnit 5
assertThrows(UserNotFoundException.class, () -> {
repository.findByUsername("nonexistent");
});
// 方法2:AssertJ
assertThatThrownBy(() -> repository.findByUsername("nonexistent"))
.isInstanceOf(UserNotFoundException.class)
.hasMessage("User not found: nonexistent")
.hasNoCause();
// 方法3:捕获异常并验证
UserNotFoundException exception = assertThrows(
UserNotFoundException.class,
() -> repository.findByUsername("nonexistent")
);
assertEquals("User not found: nonexistent", exception.getMessage());
}
}
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案
【点击此处即可/免费获取】
2. 避免过度断言
java
复制
下载
class AvoidOverAssertion {
// ❌ 过度断言:测试太多不相关的东西
@Test
void badExample_overAssertion() {
Order order = orderService.processOrder(createOrder());
assertNotNull(order.getId()); // 相关
assertEquals("pending", order.getStatus()); // 相关
assertNotNull(order.getCreatedAt()); // 相关
assertEquals("USD", order.getCurrency()); // 可能相关
assertEquals(1, order.getVersion()); // 实现细节
assertNotNull(order.getUpdatedAt()); // 实现细节
assertEquals("system", order.getCreatedBy()); // 实现细节
// 测试了太多实现细节,而不是业务逻辑
}
// ✅ 好的做法:只断言业务相关的
@Test
void goodExample_focusedAssertion() {
Order order = orderService.processOrder(createOrder());
// 只断言核心业务规则
assertThat(order.getStatus()).isEqualTo("pending");
assertThat(order.getTotal()).isEqualTo(100.0);
assertThat(order.getItems()).hasSize(2);
// 如果其他属性重要,单独测试
}
// 为不同的关注点写不同的测试
@Test
void shouldSetCorrectStatus_whenProcessingOrder() {
Order order = orderService.processOrder(createOrder());
assertEquals("pending", order.getStatus());
}
@Test
void shouldCalculateCorrectTotal_whenOrderHasItems() {
Order order = orderService.processOrder(createOrder());
assertEquals(100.0, order.getTotal(), 0.01);
}
@Test
void shouldSetCreationTimestamp_whenOrderIsProcessed() {
Order order = orderService.processOrder(createOrder());
assertNotNull(order.getCreatedAt());
}
}
六、测试隔离与可重复性
1. 保证测试独立性
java
复制
下载
class TestIsolation {
// ❌ 坏例子:测试之间有状态共享
class BadOrderServiceTest {
private static List<Order> orders = new ArrayList<>(); // 静态变量!
@Test
void test1() {
orders.add(new Order()); // 影响其他测试
}
@Test
void test2() {
assertEquals(1, orders.size()); // 依赖test1的执行
}
}
// ✅ 好例子:每个测试独立
class GoodOrderServiceTest {
@Test
void shouldCreateNewOrder() {
// 每个测试创建自己的数据
OrderService service = new OrderService();
Order order = service.createOrder();
assertNotNull(order);
assertNotNull(order.getId());
}
@Test
void shouldCalculateOrderTotal() {
// 独立的数据
Order order = new Order();
order.addItem(new Item("Product", 50.0, 2));
double total = order.calculateTotal();
assertEquals(100.0, total, 0.01);
}
}
// 使用@BeforeEach而不是@BeforeAll
class SetupExample {
private OrderService orderService;
private Order testOrder;
@BeforeEach // 每个测试前执行
void setUp() {
orderService = new OrderService();
testOrder = createTestOrder(); // 新鲜的数据
}
@Test
void test1() {
// 使用自己的orderService和testOrder
}
@Test
void test2() {
// 不会受到test1的影响
}
// @AfterEach 用于清理资源
@AfterEach
void tearDown() {
// 清理数据库、文件等资源
}
}
}
2. 处理外部依赖
java
复制
下载
class ExternalDependencies {
// 使用Test Containers进行数据库测试
@Testcontainers
class DatabaseIntegrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:13")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
@Test
void shouldSaveAndRetrieveUser() {
// 使用真实的PostgreSQL,但在容器中
String jdbcUrl = postgres.getJdbcUrl();
UserRepository repo = new UserRepository(jdbcUrl);
User user = new User("test", "test@example.com");
repo.save(user);
User found = repo.findByUsername("test");
assertNotNull(found);
}
}
// 使用WireMock模拟HTTP服务
@Test
void shouldCallExternalApi() {
// 启动WireMock服务器
WireMockServer wireMock = new WireMockServer(8089);
wireMock.start();
try {
// 配置模拟响应
wireMock.stubFor(get(urlEqualTo("/api/users/1"))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody("{\"id\":1,\"name\":\"John\"}")));
// 测试调用外部API的代码
UserService service = new UserService("http://localhost:8089");
User user = service.getUser(1);
assertEquals("John", user.getName());
} finally {
wireMock.stop();
}
}
// 使用内存数据库进行测试
@DataJpaTest
class RepositoryTest {
@Autowired
private TestEntityManager entityManager;
@Autowired
private UserRepository userRepository;
@Test
void shouldFindByEmail() {
// 使用H2内存数据库
User user = new User("john", "john@example.com");
entityManager.persist(user);
Optional<User> found = userRepository.findByEmail("john@example.com");
assertTrue(found.isPresent());
}
}
}
七、测试性能与维护
1. 快速测试原则
java
复制
下载
class FastTests {
// 好的单元测试应该快速执行
@Test
void fastTestExample() {
// 执行时间 < 100ms
Order order = new Order();
order.addItem(new Item("Book", 29.99, 1));
long start = System.currentTimeMillis();
double total = order.calculateTotal();
long duration = System.currentTimeMillis() - start;
assertEquals(29.99, total, 0.01);
assertTrue(duration < 100, "Test should complete in < 100ms");
}
// 避免在单元测试中做这些:
class SlowTestsToAvoid {
@Test
void avoidFileIO() {
// ❌ 文件操作很慢
String content = Files.readString(Path.of("large_file.txt"));
}
@Test
void avoidNetworkCalls() {
// ❌ 网络调用不稳定且慢
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://external-api.com"))
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
}
@Test
void avoidSleep() {
// ❌ Thread.sleep 让测试变慢
Thread.sleep(1000); // 1秒!
}
@Test
void avoidComplexDatabaseOperations() {
// ❌ 复杂查询或大数据量操作
List<User> users = userRepository.findAllUsersWithComplexJoin();
}
}
}
2. 测试可维护性
java
复制
下载
class MaintainableTests {
// 1. 避免魔数(Magic Numbers)
@Test
void badExample_magicNumbers() {
Order order = createOrder();
double total = order.calculateTotal();
assertEquals(118.0, total, 0.01); // 118是什么?
}
@Test
void goodExample_namedConstants() {
// 使用有意义的常量
double EXPECTED_TOTAL = 100.0 * 1.18; // 100 + 18%税
Order order = createOrderWithTotal(100.0);
double total = order.calculateTotalWithTax();
assertEquals(EXPECTED_TOTAL, total, 0.01);
}
// 2. 测试代码也要DRY(Don't Repeat Yourself)
class DRYTests {
private OrderService orderService;
private Customer testCustomer;
@BeforeEach
void setUp() {
orderService = new OrderService();
testCustomer = createTestCustomer();
}
// 提取公共的验证逻辑
private void assertOrderIsValid(Order order) {
assertNotNull(order.getId());
assertNotNull(order.getCreatedAt());
assertEquals("pending", order.getStatus());
}
@Test
void shouldCreateValidOrder() {
Order order = orderService.createOrder(testCustomer);
assertOrderIsValid(order);
}
@Test
void shouldCreateValidOrderWithItems() {
Order order = orderService.createOrderWithItems(testCustomer,
Arrays.asList("item1", "item2"));
assertOrderIsValid(order);
assertEquals(2, order.getItems().size());
}
}
// 3. 使用自定义断言
class CustomAssertions {
// 自定义断言类
class OrderAssert {
private final Order actual;
private OrderAssert(Order actual) {
this.actual = actual;
}
public static OrderAssert assertThat(Order actual) {
return new OrderAssert(actual);
}
public OrderAssert hasStatus(String expectedStatus) {
assertEquals(expectedStatus, actual.getStatus(),
"Expected order status to be " + expectedStatus);
return this;
}
public OrderAssert hasTotal(double expectedTotal) {
assertEquals(expectedTotal, actual.getTotal(), 0.01,
"Expected order total to be " + expectedTotal);
return this;
}
public OrderAssert hasItemCount(int expectedCount) {
assertEquals(expectedCount, actual.getItems().size(),
"Expected " + expectedCount + " items in order");
return this;
}
}
@Test
void shouldCreateValidOrder() {
Order order = orderService.processOrder(createOrder());
OrderAssert.assertThat(order)
.hasStatus("pending")
.hasTotal(100.0)
.hasItemCount(2);
}
}
}
八、测试边界条件
1. 常见边界条件测试
java
复制
下载
class BoundaryConditions {
@Test
void shouldHandleEmptyOrder() {
Order order = new Order();
// 空订单
assertEquals(0.0, order.calculateTotal(), 0.01);
assertEquals(0, order.getItems().size());
}
@Test
void shouldHandleSingleItemOrder() {
Order order = new Order();
order.addItem(new Item("Book", 29.99, 1));
// 单个商品
assertEquals(29.99, order.calculateTotal(), 0.01);
}
@Test
void shouldHandleMaximumQuantity() {
Order order = new Order();
// 边界值:最大数量
order.addItem(new Item("Product", 10.0, Integer.MAX_VALUE));
// 确保不会溢出
assertTrue(order.calculateTotal() > 0);
}
@Test
void shouldHandleZeroPriceItem() {
Order order = new Order();
// 价格为0的商品
order.addItem(new Item("Free Item", 0.0, 1));
assertEquals(0.0, order.calculateTotal(), 0.01);
}
@Test
void shouldHandleNegativeQuantity() {
Order order = new Order();
// 负数数量应该抛出异常
assertThrows(IllegalArgumentException.class, () -> {
order.addItem(new Item("Product", 10.0, -1));
});
}
@Test
void shouldHandleNullInputs() {
OrderService service = new OrderService();
// null参数应该被正确处理
assertThrows(IllegalArgumentException.class, () -> {
service.processOrder(null);
});
}
@Test
void shouldHandleExtremeValues() {
Calculator calculator = new Calculator();
// 极大值
double result1 = calculator.multiply(Double.MAX_VALUE, 2);
assertTrue(Double.isInfinite(result1));
// 极小值
double result2 = calculator.divide(0.000001, 1000000);
assertEquals(0.000000000001, result2, 0.0000000000001);
// NaN处理
double result3 = calculator.sqrt(-1);
assertTrue(Double.isNaN(result3));
}
// 日期边界条件
@Test
void shouldHandleLeapYear() {
DateTimeUtils utils = new DateTimeUtils();
// 闰年2月29日
LocalDate leapDay = LocalDate.of(2024, 2, 29);
assertTrue(utils.isValidDate(leapDay));
// 非闰年2月29日
LocalDate nonLeapDay = LocalDate.of(2023, 2, 29);
assertFalse(utils.isValidDate(nonLeapDay));
}
@Test
void shouldHandleTimeZoneBoundaries() {
TimeZoneService service = new TimeZoneService();
// 夏令时转换
ZonedDateTime beforeDST = ZonedDateTime.of(
2024, 3, 10, 1, 59, 59, 0,
ZoneId.of("America/New_York"));
ZonedDateTime afterDST = beforeDST.plusSeconds(1);
// 测试时间跳跃
assertEquals(3, afterDST.getHour()); // 1:59:59 → 3:00:00
}
}
2. 参数化测试
java
复制
下载
class ParameterizedTesting {
// JUnit 5 参数化测试
@ParameterizedTest
@ValueSource(ints = {0, 1, 5, 10, 100, 1000})
void shouldCalculateCorrectDiscount(int quantity) {
DiscountCalculator calculator = new DiscountCalculator();
double discount = calculator.calculate(quantity);
// 根据数量验证折扣
if (quantity >= 100) {
assertEquals(0.2, discount, 0.01); // 20% discount
} else if (quantity >= 10) {
assertEquals(0.1, discount, 0.01); // 10% discount
} else {
assertEquals(0.0, discount, 0.01); // No discount
}
}
@ParameterizedTest
@CsvSource({
"100.0, 1, 100.0", // 正常情况
"100.0, 2, 200.0", // 多个数量
"0.0, 5, 0.0", // 免费商品
"50.0, 0, 0.0" // 零数量
})
void shouldCalculateTotal(double price, int quantity, double expectedTotal) {
Item item = new Item("Test", price, quantity);
assertEquals(expectedTotal, item.getTotalPrice(), 0.01);
}
@ParameterizedTest
@MethodSource("provideInvalidInputs")
void shouldRejectInvalidInputs(String input, Class<? extends Exception> expectedException) {
Validator validator = new Validator();
assertThrows(expectedException, () -> validator.validate(input));
}
private static Stream<Arguments> provideInvalidInputs() {
return Stream.of(
Arguments.of(null, IllegalArgumentException.class),
Arguments.of("", IllegalArgumentException.class),
Arguments.of(" ", IllegalArgumentException.class),
Arguments.of("a".repeat(101), ValidationException.class),
Arguments.of("<script>alert('xss')</script>", SecurityException.class)
);
}
// 使用自定义注解
@ParameterizedTest
@InvalidEmailSource
void shouldRejectInvalidEmails(String email) {
EmailValidator validator = new EmailValidator();
assertFalse(validator.isValid(email));
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@ArgumentsSource(InvalidEmailProvider.class)
@interface InvalidEmailSource {}
static class InvalidEmailProvider implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
return Stream.of(
Arguments.of(""),
Arguments.of("plainaddress"),
Arguments.of("@missinglocal.com"),
Arguments.of("local@.com"),
Arguments.of("local@domain."),
Arguments.of("local@domain..com"),
Arguments.of("local@domain.com."),
Arguments.of("local@-domain.com"),
Arguments.of("local@domain-.com")
);
}
}
}
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案
【点击此处即可/免费获取】
九、测试覆盖率策略
1. 有意义的覆盖率目标
java
复制
下载
class TestCoverageStrategy {
// ❌ 错误的追求:100%覆盖率
class UselessCoverage {
// 为了达到100%覆盖率而写的无用测试
@Test
void testGetterAndSetter() {
User user = new User();
user.setName("John");
assertEquals("John", user.getName());
user.setEmail("john@example.com");
assertEquals("john@example.com", user.getEmail());
// 这只是测试Java语法,不是业务逻辑
}
}
// ✅ 正确的追求:有意义的覆盖率
class MeaningfulCoverage {
// 测试业务逻辑
@Test
void shouldApplyDiscountForVIP() {
Order order = createVIPOrder();
order.applyDiscount();
assertTrue(order.getTotal() < order.getSubtotal());
}
@Test
void shouldNotApplyDiscountForRegularCustomer() {
Order order = createRegularOrder();
order.applyDiscount();
assertEquals(order.getTotal(), order.getSubtotal(), 0.01);
}
@Test
void shouldThrowExceptionForInvalidOrder() {
assertThrows(InvalidOrderException.class, () -> {
orderService.processOrder(null);
});
}
}
// 覆盖率工具配置示例
class CoverageConfiguration {
// JaCoCo配置(pom.xml)
/*
<configuration>
<excludes>
<!-- 排除不应该测试的 -->
<exclude>**/model/*.class</exclude> <!-- 实体类 -->
<exclude>**/dto/*.class</exclude> <!-- DTO -->
<exclude>**/config/*.class</exclude> <!-- 配置类 -->
<exclude>**/Application.class</exclude><!-- 启动类 -->
</excludes>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.80</minimum> <!-- 80%行覆盖 -->
</limit>
<limit>
<counter>BRANCH</counter>
<value>COVEREDRATIO</value>
<minimum>0.70</minimum> <!-- 70%分支覆盖 -->
</limit>
</limits>
</rule>
</rules>
</configuration>
*/
}
}
十、测试驱动开发(TDD)
1. 红-绿-重构循环
java
复制
下载
class TDDExample {
// 需求:计算订单折扣
// 步骤1:红 - 写一个失败的测试
@Test
void shouldApply10PercentDiscountForOrdersOver100() {
DiscountCalculator calculator = new DiscountCalculator();
// 测试还不存在的方法
double discount = calculator.calculate(150.0);
assertEquals(15.0, discount, 0.01); // 150 * 10% = 15
// 编译失败:calculate方法不存在
}
// 步骤2:绿 - 写最简单的实现让测试通过
class DiscountCalculator {
public double calculate(double amount) {
return 15.0; // 硬编码返回值,让测试通过
}
}
// 步骤3:添加更多测试
@Test
void shouldApply20PercentDiscountForOrdersOver500() {
DiscountCalculator calculator = new DiscountCalculator();
double discount = calculator.calculate(600.0);
assertEquals(120.0, discount, 0.01); // 600 * 20% = 120
// 测试失败:仍然返回15.0
}
@Test
void shouldApplyNoDiscountForOrdersUnder100() {
DiscountCalculator calculator = new DiscountCalculator();
double discount = calculator.calculate(50.0);
assertEquals(0.0, discount, 0.01); // 50 < 100, 无折扣
// 测试失败:仍然返回15.0
}
// 步骤4:重构 - 实现正确的逻辑
class DiscountCalculator {
public double calculate(double amount) {
if (amount > 500) {
return amount * 0.20; // 20%折扣
} else if (amount > 100) {
return amount * 0.10; // 10%折扣
} else {
return 0.0; // 无折扣
}
}
}
// 步骤5:重构 - 改进代码
class RefactoredDiscountCalculator {
private static final double PREMIUM_THRESHOLD = 500.0;
private static final double STANDARD_THRESHOLD = 100.0;
private static final double PREMIUM_RATE = 0.20;
private static final double STANDARD_RATE = 0.10;
public double calculate(double amount) {
if (amount > PREMIUM_THRESHOLD) {
return amount * PREMIUM_RATE;
}
if (amount > STANDARD_THRESHOLD) {
return amount * STANDARD_RATE;
}
return 0.0;
}
}
// 步骤6:边缘情况测试
@Test
void shouldHandleExactThresholdAmounts() {
DiscountCalculator calculator = new DiscountCalculator();
assertEquals(0.0, calculator.calculate(100.0), 0.01);
assertEquals(10.01, calculator.calculate(100.01), 0.01);
assertEquals(100.0, calculator.calculate(500.0), 0.01);
assertEquals(100.02, calculator.calculate(500.01), 0.01);
}
}
十一、总结与检查清单
1. 单元测试检查清单
java
复制
下载
class UnitTestChecklist {
// ✅ 每个测试应该:
void checklist() {
// 1. 测试单一功能
// 2. 有描述性的名称
// 3. 遵循AAA模式(Arrange-Act-Assert)
// 4. 独立于其他测试
// 5. 快速执行(< 100ms)
// 6. 不依赖外部资源
// 7. 测试行为,而不是实现
// 8. 包含有意义的断言消息
// 9. 处理边界条件和异常
// 10. 可以被任何人理解和运行
}
// 🚨 测试中应该避免:
void antiPatterns() {
// 1. 测试私有方法(测试公共API)
// 2. 过度使用Mock(Mock必要的依赖)
// 3. 测试实现细节(测试行为)
// 4. 共享测试状态(每个测试独立)
// 5. 忽略失败的测试(立即修复)
// 6. 写注释而不是好名字(命名即文档)
// 7. 过度断言(只断言相关部分)
// 8. 不测试边界条件(测试边缘情况)
// 9. 忽略异常路径(测试错误情况)
// 10. 依赖执行顺序(测试应该无序运行)
}
}
2. 测试金字塔指导原则
text
复制
下载
测试金字塔(理想比例)
───────────────────────────────
╱╲ UI/E2E Tests (10%)
╱ ╲ ─────────────
╱ ╲ Integration Tests (20%)
╱ ╲ ─────────────
╱________╲ Unit Tests (70%)
───────────────────────────────
黄金法则:
1. 多写单元测试(快速、可靠、维护成本低)
2. 适量集成测试(验证组件协作)
3. 少写端到端测试(慢、脆弱、维护成本高)
4. 所有测试应该是自动化的
5. 测试应该作为持续集成的一部分运行
记住:好的测试不是测试最多的代码,而是测试最重要的代码。 专注于测试业务逻辑、复杂算法、边界条件和异常处理,而不是简单的getter/setter。