【问题标题】:Should you have duplicate unit tests for seperate methods that share the same private implementation?您是否应该对共享相同私有实现的单独方法进行重复的单元测试?
【发布时间】:2014-04-16 04:36:20
【问题描述】:

我正在为我们的大型企业应用程序构建一个业务层,而我们目前正在进行的单元测试不到 500 个。场景是我们有两个public 方法,public AddT(T)public UpdateT(T),它们都对private AddOrUpdateT(T) 进行内部调用,因为两者之间的许多核心逻辑是相同的,但不是全部;它们是不同的。

因为它们是单独的公共 API(不管私有实现),我为每个 API 编写了单元测试,即使它们是相同的。这可能看起来像

[TestMethod]
public void AddT_HasBehaviorA() 
{
}

[TestMethod]
public void UpdateT_HasBehaviorA() 
{ 
}

目前对于这个特定的业务对象,大约有 30 个用于添加的单元测试和 40 个用于更新的单元测试,其中 30 个更新测试与添加测试相同。

这是正常的,还是我应该将常见的行为抽象到一个单独进行单元测试的公共辅助类中,并且这两个 API 只使用该辅助类而不是具有实现的私有方法?

什么是这些情况的最佳做法?

【问题讨论】:

  • 另一种选择:您可以将其设为内部帮助程序类,并使用InternalsVisibleTo

标签: c# .net unit-testing tdd


【解决方案1】:

首先,重要的是要了解为什么要通过单元测试覆盖这些方法,因为这会影响答案。只有您和您的团队知道这一点,但如果我们假设单元测试的至少部分动机是为了获得值得信赖的回归测试套件,那么您应该测试可观察行为 被测系统 (SUT)。

换句话说,单元测试应该是黑盒测试。测试不应该知道 SUT 的实现细节。因此,您可以从中得出的天真结论是,如果您有重复的行为,那么您也应该有重复的测试代码。

但是,您的系统越复杂,它越依赖常见的行为和策略,实施该测试策略就越困难。这是因为您将通过系统获得组合爆炸的可能方式。 J.B. Rainsberger explains it better than I do.

更好的选择通常是听你的测试GOOS 推广的概念)。在这种情况下,将常见行为提取到公共方法中听起来很有价值。然而,这本身并不能解决组合爆炸的问题。虽然您现在可以单独测试常见行为,但您还需要证明两个原始方法(AddUpdate使用新的公共方法(而不是,例如,一些复制粘贴的代码)。

最好的方法是用新的策略组合方法:

public class Host<T>
{
    private readonly IHelper<T> helper;

    public Host(IHelper<T> helper)
    {
        this.helper = helper;
    }

    public void Add(T item)
    {
        // Do something
        this.helper.AddOrUpdate(item);
        // Do something else
    }

    public void Update(T item)
    {
        // Do something
        this.helper.AddOrUpdate(item);
        // Do something else
    }
}

(显然,您需要为类型和方法提供更好的名称。)

这使您能够使用Behaviour Verification证明AddUpdate 方法正确使用AddOrUpdate 方法:

[TestMethod]
public void AddT_HasBehaviorA() 
{
    var mock = new Mock<IHelper<object>>();
    var sut = new Host<object>(mock.Object);
    var item = new object();

    sut.Add(item);

    mock.Verify(h => h.AddOrUpdate(item));
}

[TestMethod]
public void UpdateT_HasBehaviorA() 
{ 
    var mock = new Mock<IHelper<object>>();
    var sut = new Host<object>(mock.Object);
    var item = new object();

    sut.Update(item);

    mock.Verify(h => h.AddOrUpdate(item));
}

【讨论】:

    【解决方案2】:

    最好尽量避免重复。这有助于代码的可读性和可维护性(可能还有其他 *ablilities ;-))。它还使测试更容易,因为您可以在一个地方开始测试通用功能,而不必进行如此多的重复测试。此外,重复测试也很糟糕。

    另一个最佳实践是只为具有独特逻辑的功能编写单元测试。如果在您的 Add 和 Update 方法中,您只是调用另一个方法,则无需在该级别编写单元测试,您应该专注于被调用的方法。

    这又回到了不重复代码的初始点,如果您有共享重复代码的私有方法,最好将其分解成其他可以运行测试的东西。

    【讨论】:

      【解决方案3】:

      “因为很多核心逻辑在两者之间是相同的,但不是全部;它们是不同的。”

      我认为您应该对这些进行单独的单元测试,因为正如您所说,它们并不完全相同。另外,如果您稍后更改实现以调用两种不同的方法怎么办?然后你的单元测试将只测试其中一个,因为它们与这两种方法的实现细节相耦合。测试将通过,但您可能引入了错误。

      我认为更好的方法是测试这两种方法,但添加辅助方法/类来完成共同工作。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-09-20
        • 2011-08-01
        • 2015-02-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-01-15
        相关资源
        最近更新 更多