《Java单元测试实战》——编写方法:Java编程技巧之单元测试用例编写流程(2)

简介: 《Java单元测试实战》——编写方法:Java编程技巧之单元测试用例编写流程(2)

《Java单元测试实战》——编写方法:Java编程技巧之单元测试用例编写流程(1) https://developer.aliyun.com/article/1232420?groupCode=java



3) 一个有依赖的单元测试

 

一个有依赖的单元测试,需要四大步骤:

 

定义对象:定义测试对象、模拟依赖对象、注入依赖对象;

模拟方法:模拟参数或返回值、模拟依赖方法;

调用方法:传入参数对象、调用测试方法、验证返回值或异常;

验证方法:验证依赖方法、验证方法参数、验证依赖对象。

 

案例代码


/**
 * 用户服务类
 */
@Service
public class UserService {
    /** 定义依赖对象 */
    /** 用户DAO */
    @Autowired
    private UserDAO userDAO;
    /** 标识生成器 */
    @Autowired
    private IdGenerator idGenerator;
    /** 定义依赖参数 */
    /** 可以修改 */
    @Value("${userService.canModify}")
    private Boolean canModify;
    /**
     * 保存用户
     * 
     * @param userSave 用户保存
     * @return 用户标识
     */
    public Long saveUser(UserVO userSave) {
        // 获取用户标识
        Long userId = userDAO.getIdByName(userSave.getName());
        // 根据存在处理
        // 根据存在处理: 不存在则创建
        if (Objects.isNull(userId)) {
            userId = idGenerator.next();
            UserDO userCreate = new UserDO();
            userCreate.setId(userId);
            userCreate.setName(userSave.getName());
            userCreate.setDescription(userSave.getDescription());
            userDAO.create(userCreate);
        }
        // 根据存在处理: 已存在可修改
        else if (Boolean.TRUE.equals(canModify)) {
            UserDO userModify = new UserDO();
            userModify.setId(userId);
            userModify.setName(userSave.getName());
            userModify.setDescription(userSave.getDescription());
            userDAO.modify(userModify);
        }
        // 根据存在处理: 已存在禁修改
        else {
            throw new UnsupportedOperationException("不支持修改");
        }
        // 返回用户标识
        return userId;
    }
}

测试用例

 

/**
 * 用户服务测试类
 */
@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {
    /** 定义静态常量 */
    /** 资源路径 */
    private static final String RESOURCE_PATH = "testUserService/";
    /** 模拟依赖对象 */
    /** 用户DAO */
    @Mock
    private UserDAO userDAO;
    /** 标识生成器 */
    @Mock
    private IdGenerator idGenerator;
    /** 定义测试对象 */
    /** 用户服务 */
    @InjectMocks
    private UserService userService;
    /**
     * 在测试之前
     */
    @Before
    public void beforeTest() {
        Whitebox.setInternalState(userService, "canModify", Boolean.TRUE);
    }
    /**
     * 测试: 保存用户-创建
     */
    @Test
    public void testSaveUserWithCreate() {
        // 模拟依赖方法
        // 模拟依赖方法: userDAO.getIdByName
        Mockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());
        // 模拟依赖方法: idGenerator.next
        Long userId = 123L;
        Mockito.doReturn(userId).when(idGenerator).next();
        // 调用测试方法
        String path = RESOURCE_PATH + "testSaveUserWithCreate/";
        String text = ResourceHelper.getResourceAsString(getClass(), path + "userSave.json");
        UserSaveVO userSave = JSON.parseObject(text, UserSaveVO.class);
        Assert.assertEquals("用户标识不一致", userId, userService.saveUser(userSave));
        // 验证依赖方法
        // 验证依赖方法: userDAO.getIdByName
        Mockito.verify(userDAO).getIdByName(userSave.getName());
        // 验证依赖方法: idGenerator.next
        Mockito.verify(idGenerator).next();
        // 验证依赖方法: userDAO.create
        ArgumentCaptor<UserDO> userCreateCaptor = ArgumentCaptor.forClass(UserDO.class);
        Mockito.verify(userDAO).create(userCreateCaptor.capture());
        text = ResourceHelper.getResourceAsString(getClass(), path + "userCreate.json");
        Assert.assertEquals("用户创建不一致", text, JSON.toJSONString(userCreateCaptor.getValue()));
        // 验证依赖对象
        Mockito.verifyNoMoreInteractions(userDAO, idGenerator);
    }
    /**
     * 测试: 保存用户-修改
     */
    @Test
    public void testSaveUserWithModify() {
        // 模拟依赖方法
        // 模拟依赖方法: userDAO.getIdByName
        Long userId = 123L;
        Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());
        // 调用测试方法
        String path = RESOURCE_PATH + "testSaveUserWithModify/";
        String text = ResourceHelper.getResourceAsString(getClass(), path + "userSave.json");
        UserSaveVO userSave = JSON.parseObject(text, UserSaveVO.class);
        Assert.assertEquals("用户标识不一致", userId, userService.saveUser(userSave));
        // 验证依赖方法
        // 验证依赖方法: userDAO.getIdByName
        Mockito.verify(userDAO).getIdByName(userSave.getName());
        // 验证依赖方法: userDAO.modify
        ArgumentCaptor<UserDO> userModifyCaptor = ArgumentCaptor.forClass(UserDO.class);
        Mockito.verify(userDAO).modify(userModifyCaptor.capture());
        text = ResourceHelper.getResourceAsString(getClass(), path + "userModify.json");
        Assert.assertEquals("用户修改不一致", text, JSON.toJSONString(userModifyCaptor.getValue()));
        // 验证依赖对象
        Mockito.verifyNoMoreInteractions(userDAO, idGenerator);
    }
    /**
     * 测试: 保存用户-异常
     */
    @Test
    public void testSaveUserWithException() {
        // 注入依赖对象
        Whitebox.setInternalState(userService, "canModify", Boolean.FALSE);
        // 模拟依赖方法
        // 模拟依赖方法: userDAO.getIdByName
        Mockito.doReturn(123L).when(userDAO).getIdByName(Mockito.anyString());
        // 调用测试方法
        String path = RESOURCE_PATH + "testSaveUserWithException/";
        String text = ResourceHelper.getResourceAsString(getClass(), path + "userSave.json");
        UserSaveVO userSave = JSON.parseObject(text, UserSaveVO.class);
        UnsupportedOperationException exception = Assert.assertThrows("异常类型不一致", UnsupportedOperationException.class,
            () -> userService.saveUser(userSave));
        Assert.assertEquals("异常消息不一致", "不支持修改", exception.getMessage());
        // 验证依赖方法
        // 验证依赖方法: userDAO.getIdByName
        Mockito.verify(userDAO).getIdByName(userSave.getName());
        // 验证依赖对象
        Mockito.verifyNoMoreInteractions(userDAO, idGenerator);
    }
}
其中,加载的JSON资源文件内容如下:
userSave.json:
userCreate.json:
userModify.json:

通过执行以上测试用例,可以看到对源代码进行了100%的行覆盖。




《Java单元测试实战》——编写方法:Java编程技巧之单元测试用例编写流程(3) https://developer.aliyun.com/article/1232418?groupCode=java

 

相关文章
|
1天前
|
NoSQL Java Redis
如何在 Java 中操作这些 Redis 数据结构的基本方法
如何在 Java 中操作这些 Redis 数据结构的基本方法
8 2
|
2天前
|
Java 开发者 UED
【实战宝典】Java异常处理大师级教程:throws关键字,让异常声明成为你的专属标签!
【6月更文挑战第19天】在Java中,`throws`关键字是异常处理的关键,它提升了方法签名的透明度和代码质量。不使用`throws`时,未捕获的异常可能导致程序崩溃。例如,`readFileContent`方法若不声明`throws IOException`,则隐藏了可能的风险。而明确声明如`throws IOException`,提醒调用者需处理异常,增强代码稳定性。
|
2天前
|
安全 Java 程序员
【程序猿逆袭指南】Java高手的秘密武器:throws关键字,让你的方法签名霸气侧漏!
【6月更文挑战第19天】`throws`关键字是Java异常处理的关键,用于方法签名中声明可能抛出的异常,提示调用者处理。它增进代码可读性和安全性,避免运行时崩溃。通过`throws`声明多个异常,能精细规划错误处理。掌握其使用,能提升代码质量和程序员的专业形象,是Java编程中的必备技能。
|
2天前
|
Java 调度
【实战指南】Java多线程高手秘籍:线程生命周期管理,掌控程序命运的钥匙!
【6月更文挑战第19天】Java多线程涉及线程生命周期的五个阶段:新建、就绪、运行、阻塞和死亡。理解这些状态转换对性能优化至关重要。线程从新建到调用`start()`变为就绪,等待CPU执行。获得执行权后进入运行状态,执行`run()`。遇到阻塞如等待锁时,进入阻塞状态。完成后或被中断则死亡。管理线程包括合理使用锁、利用线程池、处理异常和优雅关闭线程。通过控制这些,能编写更高效稳定的多线程程序。
|
2天前
|
安全 Java 开发者
【技术咖必看】Java异常处理新境界:throws关键字,打造万无一失的方法签名!
【6月更文挑战第19天】在Java异常处理中,`throws`关键字用于方法签名,声明可能抛出的异常,提示调用者必须处理。它区分运行时异常和检查型异常,常用于声明需要调用者捕获的检查型异常。例如,`readFile`方法`throws IOException`,强制调用者通过try-catch或同样`throws`。多异常声明允许一次声明多个可能的异常类型,增强代码健壮性。理解并善用`throws`能构建更稳定、可读的代码。
|
1天前
|
Java 测试技术
Java多线程同步实战:从synchronized到Lock的进化之路!
【6月更文挑战第20天】Java多线程同步始于`synchronized`关键字,保证单线程访问共享资源,但为应对复杂场景,`Lock`接口(如`ReentrantLock`)提供了更细粒度控制,包括可重入、公平性及中断等待。通过实战比较两者在高并发下的性能,了解其应用场景。不断学习如`Semaphore`等工具并实践,能提升多线程编程能力。从同步起点到专家之路,每次实战都是进步的阶梯。
|
2天前
|
Java 开发者
线程的诞生之路:Java多线程创建方法的抉择与智慧
【6月更文挑战第19天】Java多线程编程中,开发者可选择继承Thread类或实现Runnable接口。继承Thread直接但受限于单继承,适合简单场景;实现Runnable更灵活,支持代码复用,适用于如银行转账这类需多线程处理的复杂任务。在资源管理和任务执行控制上,Runnable接口通常更优。
JAVA基础——三种流程控制语句
JAVA基础——三种流程控制语句
273 0
JAVA基础——三种流程控制语句
|
1天前
|
Java
JAVA多线程的“心灵感应”:wait()与notify()的秘密
【6月更文挑战第20天】Java多线程中,`wait()`和`notify()`是线程间协作的关键。它们充当线程间的通信桥梁,使得线程能感知对方状态。例如,生产者线程在资源满时`wait()`,消费者线程消费后`notify()`或`notifyAll()`,确保资源有效利用且避免冲突。简化的代码示例展示了这种同步机制,线程通过等待和唤醒操作实现“心灵感应”般的协同工作。
|
1天前
|
Java 开发者 C++
Java多线程同步大揭秘:synchronized与Lock的终极对决!
【6月更文挑战第20天】在Java多线程编程中,`synchronized`和`Lock`是两种关键的同步机制。`synchronized`作为内置关键字提供基础同步,简单但可能不够灵活;而`Lock`接口自Java 5引入,提供更复杂的控制和优化性能的选项。在低竞争场景下,`synchronized`性能可能更好,但在高并发或需要精细控制时,`Lock`(如`ReentrantLock`)更具优势。选择哪种取决于具体需求和场景,理解两者机制至关重要。