在TDD中,您如何为本身具有副作用的代码编写测试?

时间:2018-03-22 00:06:32

标签: tdd side-effects

如果函数的副作用是设计中固有的,我该如何开发这样的函数?

例如,如果我想实现像http.get(“url”)这样的函数,并且我将副作用存储为具有依赖注入的服务,它将如下所示:

var http = {
  "get": function( url, service ) {
    return promise(function( resolve ) {
      service( url ).then(function( Response ) {
        resolve( Response );
      });
    });
  }
}

...但我需要实现与原始http.get(url)相同的服务,因此会产生相同的副作用,因此将我置于开发循环中。我是否必须模拟服务器来测试这样的功能?如果是这样,TDD开发周期的哪一部分属于哪种?它是集成测试,还是单元测试?

另一个例子是数据库的模型。如果我正在开发适用于数据库的代码,我将设计一个接口,抽象实现该接口的模型,并使用依赖注入将其传递给我的代码。只要我的模型实现了接口,我就可以使用任何数据库并轻松地将其状态和响应存根,以便为与数据库交互的其他函数实现TDD。那个型号怎么样?它将与数据库进行交互 - 似乎副作用是设计中固有的,并且抽象它将我带入开发循环,当我去实现该抽象时。如何实现模型的方法而不能将它们抽象出去?

2 个答案:

答案 0 :(得分:3)

  

在TDD中,您如何为本身具有副作用的代码编写测试?

我认为我在任何地方都没有看到特别明确的答案;最接近的可能是GOOS - TDD的“伦敦”学校往往集中在外面。

但从广义上讲,你需要感觉副作用属于imperative shell。它们通常在基础架构组件中实现。因此,您通常需要更高级别的抽象,您可以将其传递给系统的功能部分。

例如,读取系统时钟是一个副作用,产生自纪元值以来的时间。你的大多数系统都不应该关心时间的来源,所以读取时钟的抽象应该是系统的输入

现在,它可以感觉像“乌龟一直向下” - 你如何测试你与基础设施的互动? Kent Beck describes停止状态

  

我得到的代码是有效的,而不是测试代码,所以我的理念是尽可能少地测试以达到给定的置信水平....

我倾向于依靠Hoare's observation

  

构建软件设计有两种方法:一种方法是使其变得如此简单以至于显然没有缺陷

一旦你开始实现明显正确的副作用,你就不用担心了。

当您盯着副作用,并且实施不明显时,您开始寻找将硬部分拉回功能核心的方法,进一步隔离副作用。

当您开始将所有组件连接在一起时,通常会发生副作用的实际测试。由于副作用,这些测试通常较慢;因为它们共享可变状态,所以通常需要确保它们按顺序运行。

答案 1 :(得分:1)

如果您正在对类似的模块编写单元测试,请关注该模块本身,而不是依赖于该模块。例如,它应该如何响应数据库服务正在关闭,或抛出异常/错误,返回空数据,返回良好数据等。这就是为什么你模拟它们并返回不同的值或设置不同的行为,如抛出异常。