单元测试中 when 与 given 的深度解析

Java单元测试实战指南:从JUnit 5到Mockito的最佳实践 单元测试是软件工程中保障代码质量的核心实践,它通过隔离测试最小可执行单元,为开发者提供快速反馈。其核心原理在于通过Mock等技术模拟外部依赖,确保测试的独立性与执行效率,从而成为持续集成和敏捷开发流程的基石。在Java生态中,JUnit 5、Mockito和AssertJ构成了主流的测试工具链,它们共同支撑了测试驱动开发与重构安全网的技术价值。无论是Spring Boot服务、数据库交互层还是包含外部API调用的模块,遵循Given-When-Then模式与合理的测试分层策略,都能有效提升代码的健壮性与可维 阅读详情

单元测试中 when 与 given 的深度解析

1. when 与 given 的作用和区别

基本概念

// when 和 given 都是用于设置Mock对象行为的语法
// 它们来自不同的测试风格,但功能相似

作用对比表

特性when().thenReturn()given().willReturn()
来源Mockito传统语法BDD(行为驱动开发)风格
语法风格命令式声明式
可读性一般更好,更接近自然语言
使用场景传统单元测试BDD风格测试
功能完全相同完全相同

2. 详细用法和区别

2.1 when() 的用法

class WhenUsageTest {
    
    @Test
    @DisplayName("when() 方法的多种用法")
    void whenVariousUsages() {
        List<String> mockList = mock(List.class);
        
        // 1. 基本用法 - 设置返回值
        when(mockList.get(0)).thenReturn("first");
        when(mockList.get(1)).thenReturn("second");
        
        // 2. 设置异常抛出
        when(mockList.get(999)).thenThrow(new IndexOutOfBoundsException("索引越界"));
        
        // 3. 链式调用 - 多次调用返回不同值
        when(mockList.size())
            .thenReturn(1)
            .thenReturn(2)
            .thenReturn(3);
        
        // 4. 使用参数匹配器
        when(mockList.get(anyInt())).thenReturn("any_element");
        when(mockList.contains(anyString())).thenReturn(true);
        
        // 验证
        assertEquals("first", mockList.get(0));
        assertEquals("second", mockList.get(1));
        assertEquals("any_element", mockList.get(100));
        
        // 链式调用验证
        assertEquals(1, mockList.size());
        assertEquals(2, mockList.size());
        assertEquals(3, mockList.size());
        
        // 异常验证
        assertThrows(IndexOutOfBoundsException.class, () -> mockList.get(999));
    }
    
    @Test
    @DisplayName("when() 用于void方法")
    void whenForVoidMethods() {
        List<String> mockList = mock(List.class);
        
        // 对于void方法,使用doReturn().when()格式
        doThrow(new RuntimeException("清空失败")).when(mockList).clear();
        
        assertThrows(RuntimeException.class, () -> mockList.clear());
    }
}

2.2 given() 的用法

class GivenUsageTest {
    
    @Test
    @DisplayName("given() 方法的多种用法")
    void givenVariousUsages() {
        List<String> mockList = mock(List.class);
        
        // 1. 基本用法 - BDD风格
        given(mockList.get(0)).willReturn("first");
        given(mockList.get(1)).willReturn("second");
        
        // 2. 设置异常抛出
        given(mockList.get(999)).willThrow(new IndexOutOfBoundsException("索引越界"));
        
        // 3. 链式调用
        given(mockList.size())
            .willReturn(1)
            .willReturn(2)
            .willReturn(3);
        
        // 4. 使用参数匹配器
        given(mockList.get(anyInt())).willReturn("any_element");
        given(mockList.contains(anyString())).willReturn(true);
        
        // 验证 - BDD风格使用then()
        assertThat(mockList.get(0)).isEqualTo("first");
        assertThat(mockList.get(1)).isEqualTo("second");
        assertThat(mockList.get(100)).isEqualTo("any_element");
        
        assertThat(mockList.size()).isEqualTo(1);
        assertThat(mockList.size()).isEqualTo(2);
        assertThat(mockList.size()).isEqualTo(3);
        
        assertThatThrownBy(() -> mockList.get(999))
            .isInstanceOf(IndexOutOfBoundsException.class);
    }
    
    @Test
    @DisplayName("given() 用于void方法")
    void givenForVoidMethods() {
        List<String> mockList = mock(List.class);
        
        // BDD风格对于void方法使用willThrow()
        willThrow(new RuntimeException("清空失败")).given(mockList).clear();
        
        assertThatThrownBy(() -> mockList.clear())
            .isInstanceOf(RuntimeException.class)
            .hasMessage("清空失败");
    }
}

3. 推荐使用哪个?

推荐使用 given().willReturn() 的原因:

class RecommendationExample {
    
    /**
     * 推荐使用 BDD 风格 (given-when-then) 的原因:
     * 1. 更好的可读性,接近自然语言
     * 2. 与测试结构 (Given-When-Then) 一致
     * 3. 更清晰的测试意图表达
     */
    @Test
    @DisplayName("BDD风格测试示例 - 推荐")
    void bddStyleRecommendedExample() {
        // Given - 准备阶段(设置Mock行为)
        UserRepository userRepository = mock(UserRepository.class);
        EmailService emailService = mock(EmailService.class);
        UserService userService = new UserService(userRepository, emailService);
        
        User testUser = new User(1L, "john_doe", "john@example.com");
        given(userRepository.findById(1L)).willReturn(Optional.of(testUser));
        given(emailService.sendWelcomeEmail(anyString())).willReturn(true);
        
        // When - 执行阶段
        boolean result = userService.sendWelcomeEmail(1L);
        
        // Then - 验证阶段
        assertThat(result).isTrue();
        then(emailService).should().sendWelcomeEmail("john@example.com");
    }
    
    @Test
    @DisplayName("传统风格测试示例 - 不推荐")
    void traditionalStyleNotRecommended() {
        // 设置阶段
        UserRepository userRepository = mock(UserRepository.class);
        EmailService emailService = mock(EmailService.class);
        UserService userService = new UserService(userRepository, emailService);
        
        User testUser = new User(1L, "john_doe", "john@example.com");
        when(userRepository.findById(1L)).thenReturn(Optional.of(testUser));
        when(emailService.sendWelcomeEmail(anyString())).thenReturn(true);
        
        // 执行阶段
        boolean result = userService.sendWelcomeEmail(1L);
        
        // 验证阶段
        assertTrue(result);
        verify(emailService).sendWelcomeEmail("john@example.com");
    }
}

4. 综合性单元测试示例

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.BDDMockito.*;

/**
 * 银行账户服务综合性单元测试示例
 * 展示 when 和 given 在实际项目中的使用
 */
@ExtendWith(MockitoExtension.class)
class BankAccountServiceTest {

    @Mock
    private AccountRepository accountRepository;

    @Mock
    private TransactionRepository transactionRepository;

    @Mock
    private AuditService auditService;

    @Mock
    private NotificationService notificationService;

    @InjectMocks
    private BankAccountService bankAccountService;

    @Captor
    private ArgumentCaptor<Account> accountCaptor;

    @Captor
    private ArgumentCaptor<Transaction> transactionCaptor;

    private Account testAccount;
    private Account targetAccount;

    @BeforeEach
    void setUp() {
        testAccount = Account.builder()
                .id(1L)
                .accountNumber("1234567890")
                .accountHolder("张三")
                .balance(new BigDecimal("1000.00"))
                .status(AccountStatus.ACTIVE)
                .createdAt(LocalDateTime.now())
                .build();

        targetAccount = Account.builder()
                .id(2L)
                .accountNumber("0987654321")
                .accountHolder("李四")
                .balance(new BigDecimal("500.00"))
                .status(AccountStatus.ACTIVE)
                .createdAt(LocalDateTime.now())
                .build();
    }

    /**
     * 存款操作测试 - 使用 BDD 风格 (推荐)
     */
    @Test
    @DisplayName("存款 - 有效金额时应成功并记录交易")
    void deposit_WithValidAmount_ShouldSucceedAndRecordTransaction() {
        // Given - 准备阶段:设置Mock对象的行为
        BigDecimal depositAmount = new BigDecimal("500.00");
        BigDecimal expectedBalance = new BigDecimal("1500.00");
        
        given(accountRepository.findByAccountNumber("1234567890"))
                .willReturn(Optional.of(testAccount));
        given(accountRepository.save(any(Account.class)))
                .willAnswer(invocation -> invocation.getArgument(0));
        given(transactionRepository.save(any(Transaction.class)))
                .willAnswer(invocation -> invocation.getArgument(0));
        willDoNothing().given(auditService).logTransaction(anyString(), anyString(), any(BigDecimal.class));

        // When - 执行阶段:调用被测试的方法
        TransactionResult result = bankAccountService.deposit("1234567890", depositAmount);

        // Then - 验证阶段:验证结果和行为
        assertThat(result.isSuccess()).isTrue();
        assertThat(result.getMessage()).contains("存款成功");
        assertThat(result.getNewBalance()).isEqualByComparingTo(expectedBalance);

        // 验证账户保存时余额正确更新
        then(accountRepository).should().save(accountCaptor.capture());
        Account savedAccount = accountCaptor.getValue();
        assertThat(savedAccount.getBalance()).isEqualByComparingTo(expectedBalance);

        // 验证交易记录被保存
        then(transactionRepository).should().save(transactionCaptor.capture());
        Transaction savedTransaction = transactionCaptor.getValue();
        assertThat(savedTransaction.getType()).isEqualTo(TransactionType.DEPOSIT);
        assertThat(savedTransaction.getAmount()).isEqualByComparingTo(depositAmount);

        // 验证审计日志被调用
        then(auditService).should().logTransaction(eq("1234567890"), eq("DEPOSIT"), eq(depositAmount));
    }

    /**
     * 取款操作测试 - 使用传统 when 风格 (对比)
     */
    @Test
    @DisplayName("取款 - 余额充足时应成功")
    void withdraw_WithSufficientBalance_ShouldSucceed() {
        // 使用 when 风格的传统写法
        BigDecimal withdrawAmount = new BigDecimal("200.00");
        BigDecimal expectedBalance = new BigDecimal("800.00");
        
        when(accountRepository.findByAccountNumber("1234567890"))
                .thenReturn(Optional.of(testAccount));
        when(accountRepository.save(any(Account.class)))
                .thenAnswer(invocation -> invocation.getArgument(0));
        when(transactionRepository.save(any(Transaction.class)))
                .thenAnswer(invocation -> invocation.getArgument(0));
        doNothing().when(auditService).logTransaction(anyString(), anyString(), any(BigDecimal.class));

        // 执行
        TransactionResult result = bankAccountService.withdraw("1234567890", withdrawAmount);

        // 验证
        assertThat(result.isSuccess()).isTrue();
        assertThat(result.getNewBalance()).isEqualByComparingTo(expectedBalance);

        verify(accountRepository).save(accountCaptor.capture());
        assertThat(accountCaptor.getValue().getBalance()).isEqualByComparingTo(expectedBalance);
    }

    /**
     * 取款失败测试 - 余额不足
     */
    @Test
    @DisplayName("取款 - 余额不足时应失败")
    void withdraw_WithInsufficientBalance_ShouldFail() {
        // Given - BDD风格
        BigDecimal withdrawAmount = new BigDecimal("1500.00"); // 超过余额
        
        given(accountRepository.findByAccountNumber("1234567890"))
                .willReturn(Optional.of(testAccount));

        // When & Then
        assertThatThrownBy(() -> bankAccountService.withdraw("1234567890", withdrawAmount))
                .isInstanceOf(InsufficientBalanceException.class)
                .hasMessage("余额不足");

        // 验证账户没有被保存
        then(accountRepository).should(never()).save(any(Account.class));
        // 验证没有记录交易
        then(transactionRepository).should(never()).save(any(Transaction.class));
    }

    /**
     * 转账测试 - 复杂业务逻辑
     */
    @Test
    @DisplayName("转账 - 正常情况应成功更新两个账户")
    void transfer_WithValidData_ShouldUpdateBothAccounts() {
        // Given
        BigDecimal transferAmount = new BigDecimal("300.00");
        BigDecimal sourceExpectedBalance = new BigDecimal("700.00");
        BigDecimal targetExpectedBalance = new BigDecimal("800.00");
        
        given(accountRepository.findByAccountNumber("1234567890"))
                .willReturn(Optional.of(testAccount));
        given(accountRepository.findByAccountNumber("0987654321"))
                .willReturn(Optional.of(targetAccount));
        given(accountRepository.save(any(Account.class)))
                .willAnswer(invocation -> invocation.getArgument(0));
        given(transactionRepository.save(any(Transaction.class)))
                .willAnswer(invocation -> {
                    Transaction transaction = invocation.getArgument(0);
                    transaction.setId(100L); // 设置模拟ID
                    return transaction;
                });
        willDoNothing().given(notificationService).sendTransferNotification(anyString(), anyString(), any(BigDecimal.class));

        // When
        TransferResult result = bankAccountService.transfer(
                "1234567890", 
                "0987654321", 
                transferAmount, 
                "房租"
        );

        // Then
        assertThat(result.isSuccess()).isTrue();
        assertThat(result.getTransactionId()).isNotNull();

        // 验证两个账户都被保存
        then(accountRepository).should(times(2)).save(accountCaptor.capture());
        List<Account> savedAccounts = accountCaptor.getAllValues();
        
        assertThat(savedAccounts).hasSize(2);
        assertThat(savedAccounts.get(0).getBalance()).isEqualByComparingTo(sourceExpectedBalance);
        assertThat(savedAccounts.get(1).getBalance()).isEqualByComparingTo(targetExpectedBalance);

        // 验证通知被发送
        then(notificationService).should().sendTransferNotification(
                eq("1234567890"), 
                eq("0987654321"), 
                eq(transferAmount)
        );
    }

    /**
     * 参数化测试 - 测试多种金额情况
     */
    @ParameterizedTest
    @DisplayName("存款金额边界测试")
    @CsvSource({
            "0.01, 1000.01",    // 最小金额
            "100.00, 1100.00",  // 正常金额
            "999999.99, 1000999.99" // 大金额
    })
    void deposit_WithVariousAmounts_ShouldCalculateCorrectBalance(
            String amount, String expectedBalance) {
        // Given
        BigDecimal depositAmount = new BigDecimal(amount);
        
        given(accountRepository.findByAccountNumber("1234567890"))
                .willReturn(Optional.of(testAccount));
        given(accountRepository.save(any(Account.class)))
                .willAnswer(invocation -> invocation.getArgument(0));

        // When
        TransactionResult result = bankAccountService.deposit("1234567890", depositAmount);

        // Then
        assertThat(result.getNewBalance()).isEqualByComparingTo(new BigDecimal(expectedBalance));
    }

    /**
     * 异常情况测试 - 账户不存在
     */
    @Test
    @DisplayName("操作不存在的账户时应抛出异常")
    void operateOnNonExistentAccount_ShouldThrowException() {
        // Given
        given(accountRepository.findByAccountNumber("9999999999"))
                .willReturn(Optional.empty());

        // When & Then
        assertThatThrownBy(() -> bankAccountService.deposit("9999999999", new BigDecimal("100.00")))
                .isInstanceOf(AccountNotFoundException.class)
                .hasMessage("账户不存在: 9999999999");
    }

    /**
     * 测试方法链式调用 - 连续操作
     */
    @Test
    @DisplayName("连续操作账户 - 应正确处理状态变化")
    void consecutiveOperations_ShouldHandleStateCorrectly() {
        // Given - 设置连续调用的不同返回值
        given(accountRepository.findByAccountNumber("1234567890"))
                .willReturn(Optional.of(testAccount));
        
        given(accountRepository.save(any(Account.class)))
                .willAnswer(invocation -> invocation.getArgument(0));
        
        // 第一次存款后的账户状态
        Account afterFirstDeposit = Account.builder()
                .id(1L)
                .accountNumber("1234567890")
                .balance(new BigDecimal("1500.00"))
                .build();
                
        // 第二次取款后的账户状态  
        Account afterWithdrawal = Account.builder()
                .id(1L)
                .accountNumber("1234567890")
                .balance(new BigDecimal("1300.00"))
                .build();

        // 设置连续保存时返回不同的账户状态
        given(accountRepository.save(any(Account.class)))
                .willReturn(afterFirstDeposit, afterWithdrawal);

        // When - 执行连续操作
        TransactionResult depositResult = bankAccountService.deposit("1234567890", new BigDecimal("500.00"));
        TransactionResult withdrawResult = bankAccountService.withdraw("1234567890", new BigDecimal("200.00"));

        // Then
        assertThat(depositResult.getNewBalance()).isEqualByComparingTo("1500.00");
        assertThat(withdrawResult.getNewBalance()).isEqualByComparingTo("1300.00");

        // 验证保存被调用了两次
        then(accountRepository).should(times(2)).save(any(Account.class));
    }

    /**
     * 测试 void 方法的不同写法
     */
    @Test
    @DisplayName("审计日志 - 测试void方法的不同Mock方式")
    void auditLogging_TestVoidMethodMocking() {
        // 测试方法1: BDD风格 - willDoNothing()
        willDoNothing().given(auditService).logTransaction(anyString(), anyString(), any(BigDecimal.class));
        
        // 测试方法2: 传统风格 - doNothing().when()
        doNothing().when(notificationService).sendTransferNotification(anyString(), anyString(), any(BigDecimal.class));

        // 执行测试
        auditService.logTransaction("123", "TEST", new BigDecimal("100"));
        notificationService.sendTransferNotification("123", "456", new BigDecimal("100"));

        // 验证
        then(auditService).should().logTransaction("123", "TEST", new BigDecimal("100"));
        verify(notificationService).sendTransferNotification("123", "456", new BigDecimal("100"));
    }
}

// 支持类定义
class Account {
    private Long id;
    private String accountNumber;
    private String accountHolder;
    private BigDecimal balance;
    private AccountStatus status;
    private LocalDateTime createdAt;
    
    // builder, getters, setters
    public static AccountBuilder builder() { return new AccountBuilder(); }
    
    static class AccountBuilder {
        private Account account = new Account();
        
        public AccountBuilder id(Long id) { account.id = id; return this; }
        public AccountBuilder accountNumber(String accountNumber) { account.accountNumber = accountNumber; return this; }
        public AccountBuilder accountHolder(String accountHolder) { account.accountHolder = accountHolder; return this; }
        public AccountBuilder balance(BigDecimal balance) { account.balance = balance; return this; }
        public AccountBuilder status(AccountStatus status) { account.status = status; return this; }
        public AccountBuilder createdAt(LocalDateTime createdAt) { account.createdAt = createdAt; return this; }
        public Account build() { return account; }
    }
    
    // getters
    public Long getId() { return id; }
    public String getAccountNumber() { return accountNumber; }
    public String getAccountHolder() { return accountHolder; }
    public BigDecimal getBalance() { return balance; }
    public AccountStatus getStatus() { return status; }
    public LocalDateTime getCreatedAt() { return createdAt; }
}

enum AccountStatus {
    ACTIVE, INACTIVE, FROZEN, CLOSED
}

enum TransactionType {
    DEPOSIT, WITHDRAWAL, TRANSFER
}

class Transaction {
    private Long id;
    private String fromAccount;
    private String toAccount;
    private BigDecimal amount;
    private TransactionType type;
    private String description;
    private LocalDateTime timestamp;
    
    // constructor, getters, setters
}

class TransactionResult {
    private boolean success;
    private String message;
    private BigDecimal newBalance;
    
    public TransactionResult(boolean success, String message, BigDecimal newBalance) {
        this.success = success;
        this.message = message;
        this.newBalance = newBalance;
    }
    
    // getters
    public boolean isSuccess() { return success; }
    public String getMessage() { return message; }
    public BigDecimal getNewBalance() { return newBalance; }
}

class TransferResult {
    private boolean success;
    private String message;
    private Long transactionId;
    
    public TransferResult(boolean success, String message, Long transactionId) {
        this.success = success;
        this.message = message;
        this.transactionId = transactionId;
    }
    
    // getters
    public boolean isSuccess() { return success; }
    public String getMessage() { return message; }
    public Long getTransactionId() { return transactionId; }
}

// 自定义异常
class InsufficientBalanceException extends RuntimeException {
    public InsufficientBalanceException(String message) {
        super(message);
    }
}

class AccountNotFoundException extends RuntimeException {
    public AccountNotFoundException(String message) {
        super(message);
    }
}

关键总结

1. when vs given 选择建议

场景推荐用法理由
新项目given().willReturn()BDD风格,可读性更好
遗留项目保持现有风格一致性更重要
团队偏好统一选择一种保持代码风格一致
复杂测试given().willReturn()更清晰的测试结构

2. 最佳实践

// 推荐:BDD风格,结构清晰
@Test
void shouldDoSomethingWhenCondition() {
    // Given
    given(someRepository.findById(anyLong())).willReturn(someEntity);
    
    // When  
    SomeResult result = service.doSomething();
    
    // Then
    assertThat(result).isNotNull();
    then(someRepository).should().findById(anyLong());
}

// 不推荐:混合风格
@Test
void testSomething() {
    // 混乱的风格混合
    when(repo.find()).thenReturn(entity);
    given(service.process()).willReturn(result);
    // ...
}

3. 核心要点

  • 功能相同:when().thenReturn() 和 given().willReturn() 功能完全一样
  • 风格差异:主要是语法风格和可读性差异
  • 统一性:在项目中保持风格统一最重要
  • BDD优势:given-when-then 结构更符合测试的自然逻辑流程

这个综合性示例展示了在实际企业级开发中如何选择和使用 when/given,以及它们在复杂业务场景下的应用。

Spring Boot单元测试实战:JUnit 5与Mockito高效集成指南 单元测试是保障软件质量的核心实践,其核心原理在于隔离被测代码与外部依赖,通过模拟(Mock)技术创建可控的测试环境。在Java生态中,JUnit 5作为新一代测试框架,提供了现代化的测试结构和生命周期管理,而Mockito则专注于依赖模拟,两者结合能显著提升测试的可靠性与执行速度。这种技术组合对于构建可维护、低耦合的代码至关重要,尤其在微服务和模块化架构中,能有效验证业务逻辑的正确性。本文以Spring Boot项目为背景,深入讲解如何配置JUnit 5与Mockito的集成环境,并通过订单服务等典型应用场 阅读详情

相关推荐

Mockito单元测试框架:核心API详解与实战避坑指南

在软件工程实践中,单元测试是保障代码质量、实现快速反馈的关键环节。其核心原理在于隔离被测代码与外部依赖,确保测试的独立性与稳定性。为实现这一目标,Mock测试技术应运而生,它通过创建模拟对象来替代真实依赖,从而让开发者能够专注于业务逻辑的验证。Mockito作为Java领域最流行的Mock框架,提供了强大的行为设定、参数匹配和交互验证能力,其技术价值在于显著提升测试代码的可维护性与执行效率。在微服务、企业级应用等复杂场景中,Mockito能有效模拟数据库访问、远程服务调用等不稳定依赖,是构建高质量测试套件的

weixin_30883311的博客 407

单元测试中常用方法详解

在上面的代码中,我们首先创建了一个名为calculator的模拟对象,然后调用了它的add方法两次。方法返回的是抛出的异常,如果代码块没有抛出异常或抛出的异常类型不是期望的类型,则会抛出AssertionError。如果需要对抛出的异常进行其他的断言判断,可以使用返回的异常对象进行断言。其中,mockedObject是要验证的模拟对象,times是方法被调用的次数,methodToCheck是要验证的方法,args是方法的参数。方法表示设置Mock对象在以上所述的方法调用时,返回一个指定的结果。

weixin_48921735的博客 2114

Java单元测试中Mock技术的核心原理与Mockito实战指南

在软件工程实践中,单元测试是确保代码质量的关键环节,其核心原则在于隔离性——即测试应聚焦于单一单元的逻辑,排除外部依赖的干扰。为实现这一目标,Mock(模拟)技术应运而生,它通过创建依赖对象的替身,预设其行为与返回值,从而将测试与数据库、网络服务等不稳定因素解耦。这一机制不仅大幅提升了测试执行速度与结果稳定性,更推动了测试驱动开发(TDD)的落地。作为Java生态中最主流的Mock框架,Mockito提供了强大的打桩(Stubbing)与行为验证(Verification)能力,遵循Given-When-T

weixin_34023982的博客 452

单元测试

Stubbing: Stub对象用来提供测试时所需要的测试数据,对各种交互设置相应的回应。Mockito使用when(...).thenReturn(...)设置方法调用的返回值,使用when(...).thenThrow(...)设置方法调用时抛出的异常。 doReturn(Object toBeReturned)提前设置要返回的值 doThrow(Throwable… toBeThrown)提前设置要抛出的异常 doAnswer(Answer answer)提前对结果进行拦截 doCall..

jxndxrk的专栏 1073

Java单元测试实战:JUnit 5与Mockito核心机制详解与最佳实践

单元测试是软件工程中验证代码最小可测试单元正确性的基础实践,其核心原理在于通过自动化、隔离的测试用例,快速反馈代码逻辑的正确性。这项技术的核心价值在于构建可靠的安全网,提升代码质量、促进重构信心并实现快速反馈,是现代敏捷开发和持续集成的基石。在Java开发领域,单元测试广泛应用于验证业务逻辑、服务层方法、工具类等场景,是保障项目长期健康的关键。本文聚焦于Java单元测试的两大核心工具:JUnit 5与Mockito。JUnit 5作为新一代测试框架,提供了强大的**参数化测试**和灵活的断言机制,而Mock

jardownload的博客 494

JUnit 5核心方法深度解析与单元测试实战指南

单元测试是软件工程中验证代码最小可测试单元(如方法)正确性的基础实践,其核心价值在于为代码重构提供安全网、充当活文档并驱动良好设计。JUnit作为Java领域事实上的标准单元测试框架,通过定义清晰的生命周期注解(如@BeforeEach、@AfterEach)和丰富的断言机制,为开发者提供了标准化的测试编写范式。理解其从概念到原理的实现,能显著提升代码质量与可维护性。在技术价值上,JUnit 5的扩展模型、参数化测试及与Mockito等模拟框架的集成,使得测试能有效隔离依赖、覆盖多种场景,并融入持续集成流程

weixin_33742618的博客 290

Junit单元测试使用

Junit单元测试使用1.简单测试2.DAO未完成时的测试3.测试方法4.覆盖率查看 1.简单测试 先创建springBoot项目,选择javaWeb进行创建 创建实体类、DAO、Service 实体类 public class Student { private int id; private int age; private String name; public Student(int id, int age, String name) { this.i

weixin_42919166的博客 905

JUnit 6实战:系统化提升单元测试覆盖率至90%+的完整指南

单元测试是保障软件质量的核心实践,通过隔离被测代码并验证其行为,为系统构建可靠的安全网。其原理在于通过自动化测试用例,覆盖代码的各个执行路径,包括正常流程与异常分支。高覆盖率的测试不仅能快速发现回归缺陷,更能驱动代码设计向高内聚、低耦合演进,提升可维护性。在实践中,结合JUnit 6的现代特性(如参数化测试、动态测试)与Mockito、AssertJ等工具,可以高效构建健壮的测试套件。本文聚焦于如何利用JUnit 6和JaCoCo等工具链,系统性地将单元测试覆盖率提升至90%以上,涵盖从工具选型、测试策略到

weixin_34411563的博客 606

springboot 之 单元测试

springboot 之 单元测试一、JUnit5 的变化二、JUnit5常用注解@DisplayName@Timeout@BeforeEach 、@AfterEach 、@BeforeAll 、@AfterAll@Disabled@RepeatedTest三、断言(assertions)3.1、简单断言3.2、数组断言3.3、组合断言3.4、异常断言3.5、超时断言3.6、快速失败四、前置条件(assumptions)五、嵌套测试六、参数化测试 一、JUnit5 的变化 Spring Boot 2.2.0

肿肿的博客 836

java知识笔试题-JGiven:纯Java中的行为驱动开发

java知识笔试题 JGiven 是一个对开发人员友好且实用的 Java BDD 工具。 开发人员使用流畅的、特定于领域的 API 以纯 Java 编写场景,JGiven 生成领域专家可读的报告。 为什么是另一个 BDD 工具? 行为驱动开发 (BDD) 是一种开发方法,其中业务分析师、开发人员和测试人员以通用语言和符号描述软件产品的行为。 行为通常是根据场景来描述的,场景是用 Given-When-Then 表示法编写的。 通用语言和符号是 BDD 的基石之一。 另一个基石是定义的场景是可执行的,形成一个全面的测试套件,以及软件产品的动态文档。 在经典的 BDD 工具中,Java 之类的或场景是用纯文本文件编写的。 这允许非开发人员编写场景,因为不需要编程知识。 为了使场景可执行,开发人员编写了所谓的分步实现。 使用正则表达式将纯文本绑定到步骤实现。 对于维护这些可执行场景的开发人员来说,如果直接用编程语言编写测试,则不需要高开销。 除了经典的 BDD 工具之外,还有许多 Java 工具可以用 Groovy () 或 Scala () 等编程语言编写 BDD 测试。 然而,据我们所知

PCIe扫盲(三)

需要特别注意的是,PCIe的Spec中明确规定只有Root有权限发起配置请求(Originate Configuration Requests),也就是说PCIe系统里面的其他设备是不允许去配置其他设备的配置空间的,即peer-to-peer的配置请求是不允许的。当配置请求进行BDF路由的时候,正是依靠这些信息来确定要找的设备的。前面的文章还介绍过,Root和Switch的每一个端口中都包含一个P2P桥,并且知道桥的配置空间头(Configuration Space Header)是Type1型的。

Liuqz2009的专栏 2398

JUnit+Mockito单元测试之打桩when().thenReturn();

什么是Mock 测试 Mock 测试就是在测试过程中,对于某些不容易构造(如 HttpServletRequest 必须在Servlet 容器中才能构造出来)或者不容易获取的对象(如 JDBC 中的ResultSet 对象,JPA的CRUDRepository,需要执行数据库操作的),用一个虚拟的对象(Mock 对象)来创建(覆盖方法返回)以便测试的测试方法。 JUnit 是一个单元测试框架。 ...

★【World Of Moshow 郑锴】★ 4万+

深入解析UE5内存分析:从Memreport到Unreal Insights

本文深入解析了UE5内存分析的两大核心工具:Memreport与Unreal Insights。Memreport提供详细的内存快照,帮助定位UObject等资源的内存占用;而Unreal Insights则能进行实时监控与历史回溯,精准追踪内存泄漏。掌握这两款工具是优化UE5项目性能、确保大型项目稳定运行的必备技能。

lg888的博客 1005

Spock-example项目深度解析:如何用Groovy编写优雅的单元测试

Spock-example是一个帮助开发者快速上手Spock框架的示例项目,提供了多种Groovy编写的单元测试规范以及Gradle和Maven构建脚本。通过这个项目,新手可以轻松学习如何用Groovy编写清晰、高效的单元测试,提升测试代码的可读性和可维护性。 ## 📋 项目核心价值:为什么选择Spock-example? Spock-example项目的主要目标是降低Spock框架的学习门

gitblog_01008的博客 373

资产监测设备中130dBm的GPS信号注入天线端口,C/N0一般是多少?

笔者的团队一直从事于资产监测设备的开发,在开发过程中,涉及到很多细节的问题,针对新入职的同事来说较为陌生。同时,在团队外,还有需要对智能硬件终端开发感兴趣的用户。基于此背景下,笔者将在下面的内容主要介绍资产监测设备以及对设备中的GPS相关的问题:130dBm的GPS信号注入天线端口,C/N0一般是多少?作出详细的解答。 资产监测设备概述: 资产监测设备是基于物联网通信技术基础上的智能硬件终端,其主要作用是帮助用户监测资产在仓库、运输途中、交付整个流通环节中,提供资产的实时位置与状态参数。用户只需要登录资产

瑞奇聊通信模组 1229

基于51单片机的电压表【0-20V,ADC0832,4051,自动量程】(仿真)

自动换挡式数字电压表就是一个基于单片机的能自动切换量程的数字电压表,其作用是在使用低位的AD转换实现高精度电压测量,可以测量0~20V的电压,并且具有0~0.2V(待测电玉低于0.2V时),0~2V(待测电压低于2V时)和0~20V三个量程自动匹配测量,当待测电压值发生变化时,电压表可以根据输入电压情况自动选择合适的量程进行测量,并且把测量结果显示出来。(2)实现电压表精度测试,最高精度要求0.01V;(1)实现0~20V分3档式电压表;

qq_63964231的博客 1497

Modular Monolith DDD单元测试模式:Given-When-Then详解

在模块化单体(Modular Monolith)架构中,领域驱动设计(DDD)的单元测试面临双重挑战:既要验证复杂业务规则,又要确保测试代码与领域模型同步演进。传统的Arrange-Act-Assert(AAA)模式在表达领域行为时往往显得冗长,而Given-When-Then(GWT)模式通过**场景化语言**和**领域事件验证**,为DDD单元测试提供了更精准的表达范式。本文将深入剖析GWT模...

gitblog_00800的博客 971

STM32G0定时器中断实战:HAL库配置TIM2实现精准1秒LED闪烁(附完整代码)

本文深入解析了STM32G0系列微控制器中定时器中断的配置与应用,重点介绍了如何使用HAL库配置TIM2实现精准的1秒LED闪烁。通过详细讲解时钟树分析、预分频系数与自动重装载寄存器的计算原理,并结合CubeMX图形化配置与完整代码示例,帮助开发者从底层理解定时器工作机制,掌握可复用的工程化配置方法。

weixin_29194817的博客 252

rspec-given快速入门:5分钟完成安装配置,写出你的第一个Given/When/Then测试

rspec-given 是一个为 RSpec 测试框架提供 **Given/When/Then 关键字**的 Ruby 扩展库,让你能用"给定条件、执行动作、验证结果"的叙事结构写出更易读的单元测试。本文将带你完成 rspec-given 安装配置,并在 5 分钟内写出你的第一个 Given/When/Then 测试,即使你是完全没接触过 BDD 测试风格的新手也能轻松跟上。 ## 为什么用 G

gitblog_00284的博客 859

手把手教你学GPU的KMD专栏--3.1 设备初始化流程深度解析

本文深入解析了KMD(内核模式驱动)设备初始化流程,这是GPU驱动开发最基础且关键的环节。初始化流程包括6个核心阶段:KMD模块加载、PCIe设备探测匹配、GPU硬件初始化、显存分配映射、中断系统配置和设备注册。每个阶段都有详细步骤解析,涉及关键数据结构和错误处理机制。文章还分析了常见问题(驱动加载失败、显存分配失败、中断配置失败)的解决方案,并指出未来趋势将向动态初始化、安全增强和AI优化方向发展。正确高效的初始化流程是确保GPU被操作系统安全使用的前提,也是后续内存管理、任务调度等功能的基础。

MHD0815的博客 261

Android 12.0 系统systemui状态栏下拉左滑显示通知栏右滑显示控制中心模块的流程分析

在android12.0的系统rom定制化开发中,在系统原生systemui进行自定义下拉状态栏布局的定制的时候,需要在systemui下拉状态栏下滑的时候,根据下滑坐标来 判断当前是滑出通知栏还是滑出控制中心模块,所以就需要根据屏幕宽度,来区分x坐标值为多少是左滑出通知栏或者右滑出控制中心模块,接下来分析下 状态栏下滑坐标等相关功能代码分析

安卓兼职framework和app工程师的博客 2768

初识rspec-given:为RSpec与Minitest注入Given/When/Then的终极测试框架指南

如果你正在寻找一种让单元测试像故事一样清晰可读的测试框架,rspec-given 就是那个值得你停下脚步的项目。rspec-given 是 RSpec 生态中最优雅的扩展之一,它为 RSpec 与 Minitest 引入了 Cucumber 风格的 **Given/When/Then** 关键词,让测试代码从"技术实现"升华为"行为描述"。本文是一份面向新手的 rspec-given 入门指南,你

gitblog_00898的博客 306

【YOLO部署Android安卓手机APP】YOLOv8部署到安卓实时目标检测识别——官方/自训练模型YOLOv8人脸/车辆等目标检测(可自定义更换其他目标)

本文首先讲解如何直接使用官方训练好的模型部署到手机APP进行人脸检测,然后讲解如何修改其他目标进行检测,以车辆检测为例进行讲解如何训练自己的模型部署到手机APP。本文为详细设计/配置文档,包含完整所需的环境配置搭建,项目工程配置步骤等,手把手记录使用pycharm和Android studio的工程开发过程,实现YOLO部署到安卓。实战开发,亲测无误。本项目实现了人脸检测和车辆检测,当然,以此你可以按照本项目开发步骤扩展更换为其他目标进行检测,例如更换为车牌、手势、人脸面部活动、人脸表情、火焰烟雾、行人等

嵌入式阿齐Archie 1万+

Spring Boot单元测试优化:Mockito深度整合与测试策略实战

单元测试是软件工程中验证代码逻辑正确性的基础实践,其核心在于快速、独立地测试单个代码单元。在Java生态中,JUnit和Mockito是构建单元测试的基石框架,通过模拟(Mock)外部依赖来隔离测试环境。Spring Boot作为主流的Java应用框架,其自动配置和依赖注入机制为开发带来便利,但也为单元测试引入了复杂性——如何平衡框架的“重”与测试的“轻快”成为关键。通过理解Spring Boot的测试切片(Test Slices)原理,如@WebMvcTest、@DataJpaTest,开发者可以按需加载

weixin_34221073的博客 283

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

RSpec深度解析

RSpec是一个Ruby的行为驱动开发(BDD)测试框架,它通过描述性语言将测试转化为可读的文档。本文介绍了RSpec的核心功能:描述系统行为、自动化验证、驱动开发流程和隔离测试环境。通过购物车示例展示了RSpec的基本语法结构(describe/it/expect)和Given-When-Then测试模式。文章还对比了RSpec与Minitest、Cucumber等工具的差异,强调RSpec将测试提升为"行为描述"而非简单验证,使测试代码成为系统文档的一部分,便于团队协作和理解系统功能

leohu0723的博客 167

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

qCumber:kdb+/q语言的BDD单元测试框架解析

单元测试是软件开发中验证代码逻辑正确性的基础手段,而行为驱动开发(BDD)框架通过自然语言语法将测试用例转化为可执行的需求文档。在金融科技领域,kdb+/q作为高性能时序数据库语言,其生态中的qCumber测试框架创新性地融合了BDD模式与kdb+特性,支持Gherkin语法编写可读性极强的测试用例,并能深度测试表、函数等q对象。该框架特别适合量化交易系统开发,通过"测试即文档"理念,使测试代码本身成为系统规格说明,同时提供性能分析、历史测试结果存储等专业功能,大幅提升金融级系统的开发效率与质量保障能力。

weixin_30647065的博客 445

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

上一篇: 单元测试高级技巧与实践
下一篇: 在单元测试中, Mockito 框架中 when() 和 given() 两者的作用和区别
龙茶清欢
博客等级 码龄7年 3720粉丝 375原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙茶清欢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值