单元测试类的最佳实践,主要负责调用依赖项的方法,但也包含逻辑

Ezi*_*nze 5 c# java testing integration-testing unit-testing

假设我有StartCommandHandler责任创建一些包含所需文件的文件。但为了做到这一点,我必须赋予他一系列子职责,例如:

  • 检查FTP中是否存在文件
  • 如果不从多个源下载文件到临时文件夹
  • 然后执行文件夹中的一些脚本
  • 然后在脚本执行后读取生成的文件
  • 然后从该文件夹创建 zip
  • 然后删除该文件夹
  • 然后更新数据库

由于该命令处理程序,我们正在创建包含所有必需文件的文件夹。现在该文件夹已准备好进行其他操作。

我刚刚读过"Art of the Unit testing"。并开始添加单元测试。SOLID我也遵循原则。特别是SRPDIP,我认为这是单元测试的先决条件。所以,我上面所说的大部分事情都是通过特定的接口完成的。因此,该命令处理程序 90% 的工作是调用依赖项的方法。10%是这样的逻辑:

if(!_dependency1.IsAnySomething())
{
     _dependency2.Download();

      var isScriptNeeded = _dependency2.IsScriptNeeded();

      if(isScriptNeeded)
      {
          var res = _dependency3.ExecuteScript();
         _dependency4.SetScriptResult(res.Info, res.Date, res.State);
      }

     _dependency3.Archive();

     _dependency5.DeleteTemp();
}
Run Code Online (Sandbox Code Playgroud)

我已经测试了该命令处理程序的所有依赖项。但是,帽子命令处理程序还包括一些小逻辑,例如是否需要下载文件,或者是否删除临时文件等等......

我脑子里有很多问题,比如:

  1. 单元测试对于这样的单元可能没有意义吗?集成测试可以拯救?因为,测试是否检查所有调用似乎是错误的,例如DeleteTemp下载后是否调用,或者脚本是否执行,或者脚本结果以正确的方式传递给方法SetScriptResult。这是好的单元测试吗?
  2. 有什么方法可以重构该类以使其可测试吗?

Bre*_*son 4

单元测试应该测试代码的行为,而不是代码的实现。

考虑单元测试如何增加价值是有帮助的:它们传达代码的预期行为,并验证预期行为是否是由实现生成的。它们在项目生命周期中两次增加价值:第一次是在代码最初实现时,第二次是在代码重构时。

但是,如果单元测试与特定实现紧密相关,那么它们在重构时就无法增加价值。

这从来都不是一门完美的科学,但了解您是否正在测试行为或实现的一种方法是问“如果我重构,这个单元测试会中断吗?” 如果重构会破坏测试,那么它就不是一个好的单元测试。

编写单元测试来简单地确保先调用方法 A,然后调用方法 B,然后调用方法 C(或其他)通常没有什么帮助。这只是为了测试您的实现是否是您的实现,并且它可能会阻碍而不是帮助下一个想要重构代码的开发人员。

相反,请考虑行为以及代码如何与其他对象交互。尝试将每个行为分成单独的对象,并单独测试这些对象。

例如,您可以将上述代码分解为三种不同的行为:

  1. 一个缓存对象,检查值是否不存在,然后调用工厂来创建它,
  2. 一个工厂对象,它创建一个空目录,调用构建器对象来填充它,然后压缩并删除它
  3. 一个构建器对象,它将文件下载到目录并运行它在其中找到的脚本。

每个对象都有单独可测试的行为:

class Cache {
    Cache(ValueStore store, ValueFactory factory) { ... }

    object GetValue(object key) {
        if (!store.HasValue(key))
            factory.CreateValue(key);
        return store.GetValue(key);
    }
}

class CacheTest {
   void GetValue_CallsFactory_WhenValueNotInStore() {
      // arrange
      var store = Mock.Of<VaueStore>(_ => _.HasValue() == false);
      var factory = Mock.Of<ValueFactory>();
      var cache = new Cache(store, factory);

      // act
      cache.getValue();

      // assert
      Mock.Get(factory).Verify(_ => _.CreateValue(), Times.Once());
   }
}
Run Code Online (Sandbox Code Playgroud)

您可以对工厂和构建器进行类似的细分,并单独测试它们的行为。