【问题标题】:Best practices of unittesting methods that contain conditional conditions包含条件条件的单元测试方法的最佳实践
【发布时间】:2010-09-27 17:08:34
【问题描述】:

我是单元测试的新手,目前在寻找一种体面的方法来测试包含分支的方法时遇到了问题。

我创建了一个小演示方法,希望可以用来解释问题。

public void ExportAccounts()
{
     int emptyAccounts = 0;
     int nonEmptyAccounts = 0;
     int errorous = 0;
     string outputPath = this.GetOutputPath();
     Account[] accounts = this.MyWebserviceAdapter.GetAccounts();
     foreach(Account account in accounts)
     {
        try 
        {
          if(account.Amount > 0)
          {
               this.ExportNonEmpty(outputPath, account);
               nonEmptyAccounts++;
          } else {
               this.ExportEmptyAccount(outputPath, account);
               emptyAccounts++;
          }
        } catch(Exception e) {
          logger.error(e);
          errorous++;
        }
     }
     logger.debug(string.Format("{0} empty / {1} non empty / {2} errorous", emptyAccounts, nonEmptyAccounts, errorous));
}

我可以模拟 MyWebserviceAdapter 以返回预定义的帐户列表。我应该在同一个测试中输入一个空的和非空的帐户列表,还是应该有单独的测试?

此外,我的 ExportNonEmpty() 和 ExportEmpty() 方法是私有的,但确实会将文件写入文件系统。我应该提供一个模拟 FileProvider 以便不触及文件系统吗?

我应该公开 ExportNonEmpty() 和 ExportEmpty() 以便能够分别测试它们吗?这些方法还包含一些 if-then-else 语句,并且可以抛出异常等等。

我发现如果我为每个代码路径创建一个测试,我将代码从一个测试复制到另一个测试 - 生成模拟等。这不是有点奇怪吗?

我应该将计数器变量公开为输出变量,以便在调用方法后能够验证它们吗?

this.GetOUTputPath() 通过静态的 ConfigurationManager 从配置文件中获取值。我应该 a) 通过为 testt 下的类创建部分模拟并覆盖 GetOutputPath 方法来模拟它,还是 b) 创建我自己的可以模拟的 ConfigurationAdapter?

我正在使用 nunit 和 Rhino Mocks。

【问题讨论】:

    标签: unit-testing conditional


    【解决方案1】:

    对每个条件进行单独的测试。我假设您相信 foreach 会遍历包含两者的循环。我会。大概您正在测试您在其他地方模拟的方法,以确保它在存在时正确返回两种类型的帐户。

    您可以通过将通用代码提取到测试类中的方法甚至是 TestHelper 类中来重构,而不是复制代码。如果需要,可以对它们进行参数化以使其普遍可用。

    您应该能够通过为被测类添加访问器来测试您的私有方法。如果您在私有方法中使用“创建单元测试”右键单击菜单项,或者在为整个类添加时简单地将私有方法添加为一个以创建单元测试,则会自动添加一个。对于测试 ExportAccounts,只需使用您知道会引发和不会引发异常的数据,以便您可以测试直线逻辑和异常处理。

    我不会公开方法变量。在方法之外不需要它们。但是,您应该模拟记录器以确保使用预期的参数调用它。

    创建 ConfigurationAdapter(或 Wrapper)并将其注入您的类以消除对静态类的依赖。模拟适配器或提供虚假的实现,由您选择。无论如何,删除依赖项是一个很好的模式。我不喜欢模拟或存根测试类中的任何内容。

    编辑: 对于单元测试的基本阅读,我推荐Pragmatic Unit Testing(C# 版本)和Code Complete 中的单元测试章节。您可能还想接听Test Driven Development in .Net,但其他更一般。

    【讨论】:

    • 您提到提取公共代码,我同意这是一个好主意 - 但问题是无法提取您注入的模拟,因为我在每个测试方法中都设置了对它的预期调用,因此如果在不同的测试方法中测试不同的条件,所有的复制。
    • 是的,但是您的期望应该非常相似,许多不同之处仅在于期望的值。将这些值作为参数传递给提取的方法。我知道这行得通——我经常这样做。返回模拟/存根对象并根据需要设置其他存根/期望。
    • 你能给我举个例子来说明如何做到这一点,因为我对如何做到这一点有点困惑吗?
    【解决方案2】:

    我会用几个测试向量对此进行测试:全空、全非空,以及以空和非空开始和结束的混合。

    至于验证结果:暴露和测试计数器将给出一个“白盒”测试,该测试知道对象的内部状态,这提供了更彻底的测试,但更难改变稍后实施。 (如果您更改实现,即使效果相同,测试也可能会失败。

    我的偏好通常是测试«黑匣子»,并且只测试操作的外部可观察结果。然后,如果公开的功能仍然相同,您可以更改内部结构和回归测试。但是,这可能需要更多的模拟编码。

    对于 java,有很多库可以帮助您构建模拟对象,我不了解 .net,但我认为情况也是如此。

    【讨论】:

    • 你会为每个测试向量设置一个单独的测试方法吗?这样做会导致必须将大量模拟复制到我假设的每种测试方法中。你能评论一下你会怎么做吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-20
    • 1970-01-01
    • 2011-02-02
    • 1970-01-01
    • 1970-01-01
    • 2011-02-13
    相关资源
    最近更新 更多