如何避免添加吸气剂以方便单元测试?

geo*_*off 6 c# unit-testing

在阅读了一篇博客文章,提到为了便于测试而揭露公众获取者似乎是错误的,我找不到任何更好的实践的具体例子.

假设我们有这个简单的例子:

public class ProductNameList
{
    private IList<string> _products;
    public void AddProductName(string productName)
    {
        _products.Add(productName);
    }
}
Run Code Online (Sandbox Code Playgroud)

让我们说出于面向对象设计的原因,我没有必要公开公开产品列表.那么我怎样才能测试AddProductName()是否完成了它的工作?(也许它添加了两次产品,或者根本没有.)

一种解决方案是公开_products的只读版本,我可以测试它是否只有一个产品名称 - 我传递给AddProductName()的产品名称.

我之前提到的博客文章说它更多的是关于交互(即产品名称是否被添加)而不是状态.然而,状态正是我正在检查的.我已经知道AddProductName()已被调用 - 我想测试该方法完成其工作后对象的状态是否正确.

免责声明:虽然这个问题类似于 平衡设计原则:单元测试,(1)语言不同(C#而不是Java),(2)这个问题有示例代码,(3)我不觉得问题是充分回答(即代码可以帮助证明这个概念).

Ste*_*ary 7

单元测试应该测试公共API.

如果您"不需要公开公开产品列表",那么您为什么要关心它是否能够AddProductName完成它的工作?如果列表完全是私有的,从来没有受到任何其他影响,它会有什么不同?

找出可以使用API​​检测到的状态的影响 ,并测试它.AddProductName

这里非常相似的问题:域模型(公开属性)


Bro*_*ski 1

您可以保护您的访问器,以便您可以模拟,或者您可以使用内部,以便您可以在测试中访问该属性,但正如您所建议的那样,IMO 将是错误的。

我认为有时我们非常想要确保代码中的每一个小东西都经过测试。有时我们需要退一步问一下为什么我们要存储这个值,它的目的是什么?然后,我们可以开始测试组件的行为是否正确,而不是测试值是否已设置。

编辑

在您的场景中您可以做的一件事是使用一个混蛋构造函数,在其中注入 IList,然后测试您是否已添加产品:

public class ProductNameList
{
    private IList<string> _products;

    internal ProductNameList(IList<string> products)
    {
        _products = products;
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

然后你会像这样测试它:

[Test]
public void FooTest()
{
    var productList = new List<string>();

    var productNameList = new ProductNameList(productList);

    productNameList.AddProductName("Foo");

    Assert.IsTrue(productList[0] == "Foo");
}
Run Code Online (Sandbox Code Playgroud)

您需要记住使内部结构对您的测试组件可见。