TDD:您为单元测试公开了哪些方法?
Posted
技术标签:
【中文标题】TDD:您为单元测试公开了哪些方法?【英文标题】:TDD: Which methods do you expose for unit testing? 【发布时间】:2010-09-28 00:58:42 【问题描述】:我从未完全理解 TDD 的一个方面。
假设有人要求您实现一个简单的 Stack 对象。如果您正确地完成了设计,您将获得一个非常简单且干净的 API。假设:push()
、pop()
和 isEmpty()
。除此之外的任何事情都会过度扼杀需求,并让用户有太多空间来弄乱您的代码。
现在让我们假设您要对代码进行单元测试。如果你所有的公共方法都只是上面显示的三个,你怎么做呢?这些方法仅能完成您的测试。
因此,要么添加私有方法,这对您毫无帮助,因为它们对您的单元测试用例不可见。或者,您将这些方法公开,然后您的极简 API 就出现了,您为此努力工作。现在用户会弄乱你的 Stack 并且 bug 肯定会出现。
您如何处理开放公共测试方法与干净简单的 API 之间的困境?
编辑: 只是为了指出正确的方向,获得技术指针会很好(例如“使用此 hack 公开私有方法”等...)但我很喜欢更感兴趣的是关于这两个概念中哪一个更重要,以及你如何处理这个主题的更通用的答案。
【问题讨论】:
这是同一主题的另一个问题:How do you unit test private methods? 【参考方案1】:有时我会将原本私有的方法放入包级别 (Java) 或内部 (.NET) 方法中,通常带有注释或注释/属性来解释原因。
然而,大多数时候,我避免这样做。公共 API 不允许您在堆栈案例中进行哪些测试?如果用户可以看到错误并且他们只使用公共 API,那么是什么阻止您进行相同的调用?
我公开其他私有方法的时候是,它可以更容易地单独测试一组复杂步骤中的一部分 - 如果单个方法调用非常“笨重”,那么测试其中的每个步骤会非常有帮助隔离,即使普通用户看不到这些步骤。
【讨论】:
【参考方案2】:测试功能;这通常意味着测试公共接口 - 因为不应该通过公共接口访问所有功能吗?如果它们不是,那么它们就不是特征!这可能有例外,但我想不出。
测试公共接口;任何不是从公共接口直接或间接调用的方法都是不必要的。它们不仅不需要测试,而且根本不需要存在。
【讨论】:
我建议澄清一下:如果你开始觉得有必要公开一些私有的东西进行测试,不要这样做,但认为这表明你的实现太复杂了,你需要重新考虑您的实施及其测试的粒度。【参考方案3】:你应该看看那个问题:do you test private method?。
为了不破坏封装,我发现私有方法非常庞大或复杂或重要到需要它自己的测试,我只是将它放在另一个类中并在那里公开(方法对象)。然后我可以轻松地测试现在存在于它自己的类上的以前私有但现在公共的方法。
【讨论】:
【参考方案4】:您可以使用 Reflection 进行私有测试,但以后会很痛苦。我认为您需要将测试放在同一个程序集(或包)中并尝试使用 Internal。这样你就有了一些保护,你可以测试你想要测试的东西。
【讨论】:
【参考方案5】:正确的 TDD 是关于测试可以通过公共接口测试的行为...如果有任何私有方法,那么这些方法应该通过任何公共接口间接测试...
【讨论】:
【参考方案6】:使用 TDD,您的所有代码都应该可以从您的公共接口访问:
首先为您的功能编写测试。 然后,您编写的代码最少,才能通过测试。这表明您的功能已实现。如果某些代码未涵盖,这意味着该代码无用(删除它并再次运行您的测试)或您的测试不完整(某些功能已实现但测试未明确识别)。
【讨论】:
【参考方案7】:使用您的 Stack 示例,您确实不需要公开任何内部工作来对其进行单元测试。重申其他人所说的话,您应该可以根据需要随意使用尽可能多的私有方法,但只能通过您的公共 API 进行测试。
[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 类的所有行为,因为这是用户与之交互的唯一方式。您不需要任何技巧,因为您应该只测试其他人可以使用的东西。
[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);
【讨论】:
我更喜欢这个而不是接受的答案。这个代码示例很好地展示了您的测试应该如何关注 api 和结果而不是实现!【参考方案8】:使用 TDD 编码时,我为对象创建公共接口 API。这意味着在您的示例中,我的类实现的接口只有push()
、pop()
和isEmpty()
。
然而通过调用它们来测试这些方法本身并不是单元测试(更多内容在本文末尾),因为它们最有可能测试 co - 多个内部方法的操作共同产生所需的结果,这就是您的问题:这些方法是否应该是私有的?
我的回答是否定的,使用 protected
(或您选择的语言中的等效项)代替 private
,这意味着如果您的项目和测试 POM 的结构相似,则测试套件类可以查看实际课程内部,因为它们实际上在同一个文件夹中。这些可以被认为是单元测试:您正在测试类本身的功能块,而不是它们的合作。
至于测试单独的接口/API 方法,当然也很重要,我对此没有异议,但它们介于 unit testing 和 acceptance testing 的模糊线之间。
在我看来,这里要记住的最重要的事情是,单元测试会告诉您一个方法是否行为不端,验收测试会告诉您多个方法/类的合作是否行为不端,而集成测试会告诉您多个系统是否合作操作异常。
【讨论】:
您使用的“单位”定义是什么,表明私有方法符合条件?我看到的大多数定义都在谈论“公共方法”或“公共类上的一组离散方法”,或者更抽象地说“以您可以针对某些具体结果断言的方式完成实际任务的单个代码(状态变化、实际返回值、外部调用等)【参考方案9】:如果您的私有方法没有被公共 API 测试方法间接测试并且需要测试,那么我会将您的主类委托给另一个辅助类。
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(...) ...
那么你的私有方法是另一个类中的公共方法。您可以在另一个类测试中测试该公共方法。 :-)
【讨论】:
以上是关于TDD:您为单元测试公开了哪些方法?的主要内容,如果未能解决你的问题,请参考以下文章