Day 07 「Tell. Don't Ask.」 测试与依赖:测行为

2021 IT 铁人 Day 07 测试与依赖:测行为

今天来聊「不回传值的命令」的使用场景与测试。

Query 与 Command

程序行为,大多不外乎 Query 或 Command。所谓的 Query,就是向某个物件问东西,譬如向 Student 问 FullName、向 Transcript 问 AverageScore、向 Course 问 Description 等等。而 Command 则是叫物件去做某件事。譬如叫 Student 去 Register、叫 Semester 去 Start、叫 EmailService 去 Send 等等。

私以为,物件导向,就是「你现实生活会怎麽做,你程序就这样写。」在现实生活中,我们叫别人做事,有时候会期待对方简单回覆执行状况如何,有时候不会,让对方有事再回报就好。在物件导向的程序设计中也是一样,大部分的 Command,如果凡事正常,对方要嘛不回传任何讯息,要嘛只是简单回个 boolean 代表正确与否、或回个 int 代表影响资料笔数。最多,就是在发生错误时,丢个 Exception 出来。

无论如何,原则应该是要「简单就好」,因为我就是不想管那麽多,才把事情委托另一个物件去办嘛!都委托给别人了,我当然只要知道有没有成功就好啦!我管那麽多细节做什麽?

Tell. Don't Ask

物件导向程序设计有这麽一句名言:"Tell. Don't Ask." 就是在讲这样的设计概念。

你说这跟单元测试有什麽关系?有关系。遵照上述原则设计时,函式的呼叫方几乎不会预期得到什麽回信。如果我要测试的对象,会至少回传某个值,在对方执行完毕後,我可以去验那个值是否正确,但万一对方什麽都没回(void)呢?我要怎麽验证他有做到他答应我的事?

这就是我们今天要聊的主题:测行为。

不回传的 Command

在前两篇中,我们检查过了学生奖学金的申请日期。如果这是唯一的条件(范例而已嘛,意思到了就好),那检查完後就能将这份申请书存起来(假设就存在资料库),这时申请便通过了。

我们来看看程序长怎样:

public class ApplyScholarshipService {

    private final ApplicationChecker checker;
    private final ApplicationRepository applicationRepository;

    public ApplyScholarshipService(ApplicationChecker checker, ApplicationRepository applicationRepository) {
        this.checker = checker;
        this.applicationRepository = applicationRepository;
    }

    public void apply(Application application) {

        if (this.checker.checkTime(application)) {
            this.applicationRepository.create(application);
        }
    }
}

看起来蛮短的,逻辑也很单纯,就是:「如果申请表通过了 checker 的检查,就叫 repository 把申请表存起来。」写完程序,该来考虑测试怎麽写了。

Checker 的行为因为有时间的因素,不太好控制,所以可以用我们上一篇练习过的 Mock 工具,让它按我们意愿,回传 true 或 false。回传 true 时,因为 repository 没有回传值,所以我们要来验证他「有没有被呼叫 create 一次」。在 Java 使用 Mockito 的话,就可以搭配 verify 的 API,写起来就像这样:

@Test
void check_ok_then_create() {

    // 准备申请表
    Application application = new Application(777L);

    // 准备假 checker
    ApplicationChecker checker = mock(ApplicationChecker.class);
    when(checker.checkTime(application)).thenReturn(true);

    // 准备假 repository
    ApplicationRepository repository = mock(ApplicationRepository.class);

    // 执行
    new ApplyScholarshipService(checker, repository).apply(application);

    // 验证:真的有 create 一次
    verify(repository, times(1)).create(application);
}

这样就测完了。此时我们证明了,一旦 check 的检查通过了,我们的 service 真的会叫 repository 去 create 一次。接着,下一个测项应该就是 checker 检查不通过而回传 false 的情形了吧?在此情况下,测试应该要大同小异,只差在最後要 verify 的是「create 从未被呼叫」。

碍於篇幅,我就不贴上来了。 github 上有完整测项,读者可以下载来看看。

Reference

  1. Tell. Don't ask:https://martinfowler.com/bliki/TellDontAsk.html
  2. GitHub Repository:https://github.com/bearhsu2/ithelp2021.git
tags: ithelp2021

<<:  DAY07 资料视觉化

>>:  2021-Day2. Kotlin 读书会 Line 群组小帮手的新功能:个人服务

Flutter体验 Day 9-Button组件

Button组件 按钮也是在基础组件中常见的项目,它提供了点击事件可以用来定义互动的功能。 Butt...

Day05:资料结构 - 堆叠(Stack)

聊聊堆叠(Stack) 堆叠是一种後进先出(Last In First Out)(LIFO)的资料结...

[Day27] Flutter with GetX connectivity

connectivity侦测网路状态 判断当前是Wifi或是一般手机网路 在connectivity...

Day25 React useReducer - 另种管理state的方法

useReducer和useState都是用於资料状态管理的Hook, 那我该怎麽区分使用他们的时机...

[Day09] JavaScript - 流程判断

if...else 当条件成立的时候执行 if 内的陈述式,不成立时则执行else的陈述式。 语法 ...