TDD,如果两个 public 方法只使用一个私有方法,那么如何测试它们?

TDD, how to tests two public methods if they use only one private method on nothing else?

是否需要测试 2 个方法,如果它们只使用一个具有相似参数的私有方法?

例如我有一些接口(合同):

public interface IInterface
{
   void Method1(arg1, arg2, arg3);
   void Method2(arg1, arg2, arg3);
}

和此接口的实现:

public class MyClass : IInterface
{
    public void Method1(arg1, arg2, arg3)
    {
        Method3(arg1, arg2, arg3)
    }

    public void Method2(arg1, arg2, arg3)
    {
        Method3(arg1, arg2, arg3)
    }

    private void Method3(arg1, arg2, arg3)
    {
        // handle data
    }
}

例如,我有 3 个方法 1 测试,是否需要根据 TDD/RGB 规则

复制粘贴方法 2 的测试

Is it required to copy-paste this tests for Method2 based on TDD/RGB rules

Required有点太强了; TDD 警察不会来踢你的门。

但你肯定在画外线。

如果 MyClass.Method2 在所有有趣的情况下确实应该具有与 MyClass.Method1 相同的行为,那么您应该进行测试以记录此 属性.

该测试不需要是现有测试的复制粘贴;它可能类似于数据驱动测试。

[TestMethod()]
public void testMethodEquivalence () {
    val arg1 = TestContext.DataRow["arg1"]
    val arg2 = TestContext.DataRow["arg2"]
    val arg3 = TestContext.DataRow["arg3"]

    MyClass expected = new MyClass();
    MyClass sut = new MyClass();

    Assume.That(sut, Is.EqualTo(expected))

    expected.Method1(arg1, arg2, arg3)
    sut.Method2(arg1, arg2, arg3)

    Assert.That(sut, Is.EqualTo(expected))
}

重点是,在某个地方,应该对 MyClass.Method2 的实施有足够的限制,如果任何更改违反 class 合同,新编码员将毫不留情地提醒重构。

单元测试的核心要点是验证 public 被测代码的合同

从这个意义上说 - 第一个观察结果是:在您的示例中,两种方法都采用完全相同的参数并执行完全相同的操作。那么合理的答案是:您不需要 方法 1 和方法 2 - 您可以制作方法 3 public 并调用它。

假设您的示例过于简单化,您确实有两个个选择:

  • 集中测试两种 public 方法
  • 专注于 Method1,并对 Method2 进行一些 "ok, it works" 测试

这里没有黄金法则 - 您应该退后一步,决定您的实施方式有多大 可能 会发生变化。但在那种情况下,你会做 TDD,对吧。这意味着如果您更改 Method2,您将 首先 检查现有测试。当您 然后 发现它们还不够时 - 您可以添加更多内容。

从那里,我会选择选项 2。

所以听起来您并不想重构 class 的调用者以使用一个简化的界面。如果使用它的地方很普遍,或者在共享代码库中,你不能随意重构,那可能是合理的。

我要做的是将这个 class 变成一个外观,以获得更简洁的界面。换句话说,将您的私有方法拉入具有简洁界面的新 class。然后使用现有的 class 以几乎相同的方式使用,但增加了对真实实现的依赖性。

这有几个优点:

  1. 您可以开始将 class 依赖于您的原始接口的元素逐渐转移到新的实现中。 (特别是如果您弃用这些方法以提醒其他开发人员。)最终可以完全删除外观。
  2. 您可以对新 class.
  3. 进行广泛的单元测试
  4. 您可以编写两个非常简单的单元测试来验证您是否在现有 class 中调用此依赖项。 (无需再测试每个案例。)