阿里Java面试被问:单元测试的最佳实践

一、测试金字塔原则

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。

相关推荐
小码过河.3 分钟前
设计模式——解释器模式
java·设计模式·解释器模式
我不是8神3 分钟前
RPC与 Thread 知识点全面总结
java·开发语言·jvm
君爱学习4 分钟前
MySQL - 基础增删查改
java
我要敲一万行5 分钟前
前端面试erp项目常问问题
前端·面试
cyforkk8 分钟前
05、Java 基础硬核复习:数组的本质与面试考点
java·开发语言·面试
12 分钟前
ubuntu 通过ros-noetic获取RTK模块的nmea格式数据
java·前端·javascript
小马哥编程13 分钟前
单元测试中,开发模拟器(Simulator)、测试驱动器(Test driver)、桩(Stub),
单元测试·log4j
橘橙黄又青14 分钟前
List和Map篇
java·开发语言·面试
曹牧14 分钟前
Java:包含空字符字段的对象序列化为JSON字符串
java·开发语言
黎雁·泠崖16 分钟前
Java方法重写Override:规则+底层本质+与重载区别
java·开发语言