代码之家  ›  专栏  ›  技术社区  ›  NotDan

对调用另一个方法的方法进行单元测试

  •  77
  • NotDan  · 技术社区  · 17 年前

    对调用多个方法的方法进行单元测试的最佳方法是什么,例如:

    modify(string value)
    {
        if(value.Length > 5)  replaceit(value);
    
        else changeit(value);
    }
    

    此伪代码有一个modify方法(当前)调用以下任一方法 replaceit() changeit() 。我已经为编写了测试 replaceit changeit ,因此编写一个新的修改测试将是99%相同的代码集。我需要测试一下,因为它在未来可能会改变。

    那么,我应该复制粘贴现有的测试代码吗?将测试代码移动到公共函数?还有其他想法吗?我不确定这里的最佳做法。

    12 回复  |  直到 11 年前
        1
  •  33
  •   Eric Nicholson    17 年前

    这是一个经典的基于状态的测试与基于行为的测试场景。

    在这个简单得可笑的例子中,测试输出很好。不过,在某些时候,您会遇到执行后检查状态很复杂的测试。相反,您想检查行为(例如,验证changeit是否是用特定值调用的)。

    在这一点上,你可能应该研究一个像Rhino这样的模拟对象框架。Mocks(.Net)或Mockito(Java),并开始编写更多基于接口的代码。

        2
  •  14
  •   Ilja Preuß    17 年前

    你有很多选择。哪一个最好取决于你的问题中不清楚的细节。

    • 测试 modify 就好像这是一种无关的方法。优点:它可能在某个时候成为一个。
    • 测试一下if语句是否正确。也就是说,只需进行足够的测试,测试就会迫使您编写所需的实现(其中调用 replaceit changeit 只是 可能有效的最简单实现 如果你正在练习TDD,这对你来说应该是自然而然的。优点:高测试覆盖率,无需大量重复工作。
    • 子类和覆盖方法 (这是《有效使用遗留代码》一书中的一种破坏依赖性的技术):在您仅出于测试目的引入的子类上测试该方法,该子类覆盖 更换 改变它 使用预设答案或类似答案,他们可以设置感测变量(指示方法是否被正确值调用的变量)。优点:可能会简化你的测试(或不简化),有时甚至只是让测试成为可能。
    • 为提取一个新类 更换 改变它 方法,包括该类的接口。测试时卡住或模拟该接口 修改 .优点:两者都可能使你的设计更具可测试性 通常(或不)更好地解耦/可重用。
        3
  •  13
  •   Janis S.    9 年前

    如果你已经测试过 replaceit() changeit() 独立地,那么你唯一要测试的就是if条件。测试 modify() 使用一些值来确保它在正确的条件下调用正确的函数(这些条件是 null Strings 对于您给出的示例代码,长度为4、5和6)。

        4
  •  5
  •   Ian Boyd    17 年前

    只是测试 modify .

    Modify 当给定某些值时,应该返回某些值。

    这并不重要 怎样 modify完成了它的工作,只是它 它的工作。事实上,它恰好使用 replaceit changeit 是不允许任何人依赖的内部实现细节。

    如果将来你改变了 修改 使用不同的方法(或没有方法),不会、不应该也不会影响您的测试 修改 .

    也就是说,测试 更换 改变它 .

        5
  •  4
  •   Gishu    17 年前

    按优先次序排列

    1. modify(test)只有2个场景(if-stmt的每个分支),所以我会为表单的修改编写2个测试。
      如果替换的预期结果(值)很容易确定。。

    .

    public TestModifyIfValueLength..()
        {
          string expectedValue = .. ;// literal result of replaceit(value)
          Assert.Equals( expectedValue, modify("asd") );
        }
    
    1. 如果没有,请考虑使用存根(使用子类并覆盖changeit、replaceit)来验证是否调用了正确的方法。
    2. 如果存根工作量太大,那就做Mock。提取一个接口并设置对changeit的期望,替换它。

    假设

    • 您有对replaceit(value)和changeit(values)的测试,它们全面测试这两种方法(例如,的所有边界条件)。
    • replace()和changeit()是公共方法。如果没有,您应该考虑只针对公共方法编写测试。您应该可以在测试代码不知情的情况下自由调整/丢弃私有方法。
        6
  •  3
  •   Jon Skeet    17 年前

    在这种情况下,什么是“测试代码”?设置并检查结果?如果是这样,我会将其重构为不同的方法,并在每个测试中使用它。不过,只有当它的数量很大时,我才会这样做——仅仅通过阅读该方法的代码,就能看到测试所做的一切,这对可读性有好处。

    说实话,复杂的测试方法一开始经常让我感到困扰——通常它们实际上是无法避免的,但如果你 简化它们,这是值得的。

        7
  •  2
  •   TheSmurf    17 年前

    如果你已经为replace()和changeit()编写了测试,那么modify的测试只会检查是否根据“value”的值返回了不同的结果。然而,您将在测试中重新实现该方法的逻辑,这有点荒谬。

    在这种情况下,我不会测试修改,直到它具有更复杂的逻辑,或者更好的是被另一种更重要的测试方法使用。

        8
  •  2
  •   Eran Galperin    17 年前

    好吧,不,你的测试代码不会有99%的相同,因为你实际上是在测试不同的东西,除非替换、更改和修改所有代码都返回相同的值。

    不知道为什么会有困难。修改方法的测试应该大约有四行长。由于您已经在测试底层功能,并且只想确保此特定方法不会中断,因此编写一个测试此函数中两个可能的代码路径返回预期值的测试就足够了。

        9
  •  2
  •   Justin Standard    17 年前

    你基本上需要两次测试。

    1) 传入一个字符串,如“The Quick Brown Fox Jumps!”(长度大于五),确保该值受以下因素的影响 replaceit(...)

    2) 传入一个类似“Foo”(长度小于5)的字符串,并确保该值受以下因素的影响 changeit(...)

    你的测试(伪代码)可能看起来像这样:

    testLongValue() {
        string testValue = "A value longer than 5 chars";
        string expected = "Replaced!";
        string actual = modify(testValue);
        assertEqual(expected, actual);
    }
    
    testShortValue() {
        string testValue = "len4";
        string expected = "Changed!";
        string actual = modify(testValue);
        assertEqual(expected, actual);
    }
    

    显然,如果我知道replace()和changeit()应该做什么,我可以给你一个更现实的例子,但这应该能让你明白。如果它改变了原始值引用而不是返回它,那么在调用发生后,您可以将testValue用作实际值。

        10
  •  2
  •   Brian Matthews    17 年前

    当测试以下边界条件时 if (value.length > 5) 您应该确保您的测试数据包含以下值 value 有长度的 4 , 5 ,或 6 .

        11
  •  2
  •   Saravanan    13 年前

    您可以从这些方法中创建一个函数并模拟这些函数。或者,您可以创建一个虚拟方法,并使用Rhino mocks-partial mock来模拟这些虚拟方法。

        12
  •  0
  •   Olivier    17 年前

    与Justin Standard相同, 通过 null as value(对于您提供的代码片段,这显然会失败;) 单元测试的基本规则是“只测试特定于被测方法的内容”。而且相当不错。..不调用另一个方法的方法并不常见。