代码之家  ›  专栏  ›  技术社区  ›  Ohad Schneider

单元测试简单集合类

  •  5
  • Ohad Schneider  · 技术社区  · 15 年前

    考虑以下类别:

    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()现在作为列表contains()的一个瘦包装器,不应该对其本身进行测试,但是如果它在将来变为更复杂的东西呢?也许为了测试的目的,应该始终保留一个简单的“薄包装”方法?

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

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

    事先谢谢, JC

    3 回复  |  直到 15 年前
        1
  •  9
  •   Michael Myers KitsuneYMG    15 年前

    我觉得你的测试很好。您可能误解了单元测试的原则。

    一个测试(理想情况下)应该只测试一件事,这是正确的,但这并不意味着它应该只测试一个方法;相反,它应该只测试一个方法。 行为 (不变的,对某一商业规则的坚持等)。

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

    要解决其他问题:

    • 理论上,两者都有可能存在一个缺陷,这只在没有一个接一个调用它们的场景中表现出来。
      是的,但那只是意味着你需要更多的测试。例如,添加两个数字,然后调用包含,或者调用包含而不添加。

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

        2
  •  1
  •   womp    15 年前

    有两种可能性。

    1. 你的设计出了一个缺陷。您应该仔细考虑添加方法正在执行的操作是否对使用者明确。如果不想让人们向列表中添加重复项,为什么还要使用contains()方法?如果不将用户添加到列表中,并且不引发任何错误,则会使用户感到困惑。更糟糕的是,他们可能会在对列表集合调用.add()之前编写完全相同的代码,从而复制该功能。也许应该删除它,并用索引器替换它?从您的列表类中不清楚它不打算保存重复项。

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

    例如,假设您有一个函数,它只是调用到其他层中,这些层可能已经过单元测试了。这并不意味着您不为函数编写单元测试,即使它只是一个包装器。

        3
  •  1
  •   Community CDub    8 年前

    在实践中,您当前的测试是好的。对于这样简单的事情,add()和contains()中的bug不太可能互相串通隐藏。在您真正关心测试add()和add()的情况下,一个解决方案是让您的_list变量对单元测试代码可用。

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

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