单元测试一个简单的集合类

时间:2009-07-28 15:45:46

标签: unit-testing language-agnostic

考虑以下课程:

public class MyIntSet
{
    private List<int> _list = new List<int>();

    public void Add(int num)
    {
        if (!_list.Contains(num))
            _list.Add(num);
    }

    public bool Contains(int num)
    {
        return _list.Contains(num);
    }
}

遵循“仅测试一件事”原则,假设我想测试“添加”功能。 考虑以下这种测试的可能性:

[TestClass]
public class MyIntSetTests
{
    [TestMethod]
    public void Add_AddOneNumber_SetContainsAddedNumber()
    {
        MyIntSet set = new MyIntSet();
        int num = 0;

        set.Add(num);
        Assert.IsTrue(set.Contains(num));
    }
}

我对这个解决方案的问题是它实际上测试了 2个方法:Add()和Contains()。 从理论上讲,两者都可能存在错误,只会出现在一个接一个不被调用的情况下。当然,Contains()现在服务器作为List的Contains()的瘦包装器,它本身不应该进行测试,但如果它将来会变得更复杂呢?也许应该始终保留一个简单的“薄包裹”方法用于测试目的?

另一种方法可能会建议模拟或公开(可能使用InternalsVisibleTo或PrivateObject)私有_list成员并让测试直接检查它,但如果有一天内部列表被其他一些集合替换,则可能会产生测试可维护性问题(也许是C5)。

有更好的方法吗? 我反对上述实现的任何论据都有缺陷吗?

提前致谢, JC

3 个答案:

答案 0 :(得分:9)

你的测试对我来说似乎完全没问题。您可能误解了单元测试的原则。

单个测试应该(理想情况下)只测试一件事,这是事实,但这并不意味着它应该只测试一种方法;相反,它应该只测试一个行为(一个不变量,遵守某个业务规则等)。

您的测试测试行为“如果您添加到新集合,它不再为空”,这是一种行为: - )。

解决您的其他问题:

  • 从理论上讲,两者都可能存在错误,只会出现在一个接一个调用它们的情况下。
    没错,但这只是意味着你需要更多测试:-)。例如,添加两个数字,然后调用Contains,或调用Contains而不添加。

  • 另一种方法可能会建议模拟或公开(可能使用InternalsVisibleTo)私有_list成员并让测试直接检查它,但这可能会产生测试可维护性问题[...]
    非常,所以不要这样做。单元测试应始终针对被测设备的公共接口。这就是为什么它被称为单元测试,而不是“在一个单元内乱搞”-test; - )。

答案 1 :(得分:1)

有两种可能性。

  1. 你暴露了设计中的一个缺陷。您应该仔细考虑您的Add方法正在执行的操作对于使用者是否清楚。如果您不希望人们向列表中添加重复项,为什么还要使用Contains()方法呢?当用户没有添加到列表中并且没有抛出错误时,将会感到困惑。更糟糕的是,他们可能会在他们的列表集合上调用.Add()之前编写完全相同的代码来复制功能。也许它应该删除,并用索引器代替?从列表类中不清楚它并不意味着重复。

  2. 设计很好,您的公共方法应该相互依赖。这是正常的,没有理由你不能测试这两种方法。你拥有的测试案例越多,理论上就越好。

  3. 作为一个例子,假设你有一个只调用其他层的函数,这些层可能已经过单元测试。这并不意味着你不会为函数编写单元测试,即使它只是一个包装器。

答案 2 :(得分:1)

在实践中,您当前的测试很好。对于这个简单的事情,add()和contains()中的错误不太可能相互串谋隐藏对方。如果您真的担心单独测试add()和add(),一种解决方案是让您的_list变量可用于您的单元测试代码。

[TestClass]
public void Add_AddOneNumber_SetContainsAddedNumber() {
    MyIntSet set = new MyIntSet();
    set.add(0);
    Assert.IsTrue(set._list.Contains(0));
}

这样做有两个缺点。一:它需要访问私有_list变量,a little complex in C#(我推荐the reflection technique)。二:它使您的测试代码依赖于Set实现的实际实现,这意味着如果您更改了实现,则必须修改测试。我从来没有像集合类那样简单,但在某些情况下它可能很有用。