TDD:您为单元测试公开了哪些方法?

TDD: Which methods do you expose for unit testing?

TDD的一个方面我从未完全理解。

假设有人要求您实现一个简单的Stack对象。如果您已经正确完成了设计,那么您将获得一个非常简洁的API。假设:push()pop()isEmpty()。除此之外的任何事情都会过度消耗需求,并让用户有太多空间来捣乱您的代码。

所以现在让我们假设您要对代码进行单元测试。如果您所有的公共方法都只是上面显示的三个方法,那么您如何去做?到目前为止,这些方法只会进行测试。

因此,要么添加私有方法,它们根本不会帮助您,因为它们对您的单元测试用例不可见。或者你将这些方法公之于众,那就是你努力工作的简约API。现在用户将要乱用你的堆栈,肯定会出现错误。

您如何处理打开公共测试方法与干净简单API的这种困境?

编辑:只是为了指向正确的方向,获得技术指针(例如"使用此hack来暴露私有方法"等等)会很高兴但是我对更通用的答案更感兴趣这两个概念更重要,以及您如何处理这个主题。


  • 测试功能;这通常意味着测试公共接口 - 因为不应该通过公共接口访问所有功能吗?如果他们不是,那么他们不是功能!可能有例外,但我想不出任何。

  • 测试公共接口;任何不直接或间接从公共接口调用的方法都是不必要的。它们不仅不需要进行测试,而且根本不需要存在。


  • 你应该看一下这个问题:你测试私有方法吗?

    To not break the encapsulation, I find that the private method is huge or complex or important enough to require its own tests, I just put it in another class and make it public there (Method Object). Then I can easily test the previously-private-but-now-public method that now lives on it's own class.


    使用您的Stack示例,您实际上不需要公开任何内部工作来进行单元测试。重申其他人所说的内容,您应该随意拥有所需数量的私有方法,但只能通过您的公共API进行测试。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    [Test]
    public void new_instance_should_be_empty()
    {
      var stack = new Stack();

      Assert.That(stack.IsEmpty(), Is.True);
    }

    [Test]
    public void single_push_makes_IsEmpty_false()
    {
      var stack = new Stack();

      stack.Push("first");

      Assert.That(stack.IsEmpty(), Is.False);
    }

    使用推送和弹出的组合,您可以模拟Stack类的所有行为,因为这是用户与其进行交互的唯一方式。你不需要任何技巧,因为你应该只测试别人可以使用的东西。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    [Test]
    public void three_pushes_and_three_pops_results_in_empty_stack()
    {
      var stack = new Stack();

      stack.Push("one");
      stack.Push("two");
      stack.Push("three");
      stack.Pop();
      stack.Pop();
      stack.Pop();

      Assert.That(stack.IsEmpty(), Is.True);
    }


    使用TDD,您的所有代码都应该可以从公共接口访问:

    • 首先,您要为您的功能编写测试。
    • 然后,您将为测试通过最少量的代码。这表明您的功能已实现。

    如果未涵盖某些代码,则表示此代码无用(删除它并再次运行测试)或者您的测试不完整(某些功能已经实现但未通过测试明确指出)。


    权利TDD是关于可以通过公共接口测试的测试行为...如果有任何私有方法,那么这些方法应该由任何公共接口间接测试...


    有时候我会创建一些方法,这些方法本来是私有的包级别(Java)或内部(.NET)方法,通常使用注释或注释/属性来解释原因。

    但是,大部分时间我都避免这样做。公共API不允许您在堆栈案例中测试什么?如果用户可以看到错误并且他们只使用公共API,那么是什么阻止您拨打相同的电话?

    我公开其他私有方法的时间是,它使得更容易单独测试复杂的一组步骤的一部分 - 如果单个方法调用非常"粗糙",那么单独测试每个步骤是非常有帮助的,甚至是虽然普通用户不应该看到这些步骤。


    如果您的私有方法没有通过公共API测试方法间接测试并且需要进行测试,那么我会将您的主类委托给另一个辅助类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public Stack {
        public ... push(...) {...}
        public ... pop(...) {...}
        public ... isEmpty(...) {...}

        // secondary class
        private StackSupport stackSupport;
        public StackSupport getStackSupport() {...}
        public void setStackSupport(StackSupport stackSupport) {...}
    }

    public StackSupport {
        public ...yourOldPrivateMethodToTest(...) {...}
    }

    那么你的私有方法是另一个类中的公共方法。您可以在另一个类测试中测试该公共方法。 :-)


    您可以使用Reflection测试私有,但稍后会很痛苦。我认为您需要将测试放在同一个程序集(或程序包)中并尝试使用Internal。这样你就可以获得一些保护,你可以测试你想要测试的东西。


    使用TDD编码时,我为对象创建了公共接口API。这意味着在您的示例中,类实现的接口只有push()pop()isEmpty()

    然而,通过调用它们来测试这些方法本身并不是单元测试(在本文结尾处更多内容),因为它们很可能测试多个内部方法的合作,这些方法共同产生了期望的结果,这就是你的问题是:这些方法应该是私有的吗?

    我的答案是否定的,使用protected(或者用您选择的语言等效)代替private,这意味着如果您的项目和测试POM的结构类似,那么测试套件类可以看到实际课程,因为他们实际上在同一个文件夹中。这些可以被认为是单元测试:您正在测试类本身的功能块,而不是它们的合作。

    至于测试各个接口/ API方法,当然也很重要,我也不反对,但这些都介于单元测试和验收测试之间的朦胧线之间。

    在我看来,这里要记住的最重要的事情是,单元测试会告诉你方法是否行为不当,验收测试会判断多个方法/类的合作是否行为不当以及集成测试是否表明多个系统的合作是否行为不当。