什么是单元测试的 WWW 与 AAA 原则?读完本篇文章可瞬间升职公司 CTO

点赞支持这款 全新设计的Java 脚手架 ,让 Java 再次伟大!

在这里插入图片描述

单元测试的设计哲学

程序首先是写给人读的,然后顺便让机器能够运行。

Programs must be written for people to read, and only incidentally for machines to execute.

而编写编程的第一步就是命名,单元测试也一样。

WWW

如何让单元测试的命名被人读懂呢?你需要在命名中体现三个要素,简称 WWW 原则。

  • 你要测什么?(what)
  • 在什么条件下测?(when)
  • 你期待什么表现?(want)

下面来看一个 MJGA 脚手架中的单元测试示例

  @Test
  void createVerifyGetSubjectJwt_givenUserIdentify_shouldReturnTrueAndGetExpectIdentify() {
    String jwt = cookieJwt.createJwt("1");
    assertThat(cookieJwt.verifyToken(jwt)).isTrue();
    assertThat(cookieJwt.getSubject(jwt)).isEqualTo("1");
  }

分三个部分拆分这个命名如下:

  • createVerifyGetSubjectJwt 体现了要测试的对象。
  • givenUserIdentify 表示了测试的条件。
  • shouldReturnTrueAndGetExpectIdentify 阐述了期待的行为。

这就是一个好的命名——一个业务人员可以读懂的命名。

AAA

第一关结束了,现在来实现测试逻辑。其实有了 WWW 的命名设计,逻辑的实现已经有据可循。和命名一样,测试逻辑也有原则可以参考,通常被称为 AAA 原则。

  • 准备(Arrange)
  • 执行(Act)
  • 断言(Assert)

继续来看一个 MJGA 脚手架的测试案例

@Test
  @WithMockUser
  void signIn_givenValidHttpRequest_shouldSucceedWith200() throws Exception {
    String stubUsername = "test_04cb017e1fe6";
    String stubPassword = "test_567472858b8c";
    SignInDto signInDto = new SignInDto();
    signInDto.setUsername(stubUsername);
    signInDto.setPassword(stubPassword);
    when(signService.signIn(signInDto)).thenReturn(1L);

    mockMvc
        .perform(
            post("/auth/sign-in")
                .contentType(MediaType.APPLICATION_JSON)
                .content(
                    """
                {
                  "username": "test_04cb017e1fe6",
                  "password": "test_567472858b8c"
                }
                 """)
                .with(csrf()))
        .andExpect(status().isOk());
  }

Arrange
这是测试的准备阶段。在这个阶段你需要构造一些提供上下文使用的代码,比如插入数据,构建对象,乃至更复杂的 mock 与 stub 都在此列。在案例中我们构建了 SignInDto 对象供后续使用,并假设了 signIn 方法的返回值——这些都是在为后续的测试做准备。

Act
这是执行阶段,在案例中对应 mockMvc.perfom 这里只是 Api 比较复杂,你就理解成一行代码就行。

Assert
你需要清晰的表明你期待的结果是什么。在案例中对应 .andExpect(status().isOk());

很多时候可能需要写一些测试方法用来验证某个逻辑是否能够正常运行不报错,因为方法本身没有返回值。这很容易,使用 assertDoesNotThrow 来解决这个问题。

 // pause & resume job
    JobKey firstDataBackupJobKey = dataBackupJobKeys.iterator().next();
    assertDoesNotThrow(
        () -> {
          dataBackupScheduler.pauseJob(firstDataBackupJobKey);
          dataBackupScheduler.resumeJob(firstDataBackupJobKey);
        });

结语

这些编码哲学和你用什么语言和框架无关,任何单元测试都是靠这种思维写出来的。更多的单元测试内容待我后续整理好后发出。请大家支持 MJGA 脚手架,鼓励我创作更多的内容,谢谢各位佬们。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值