处理TDD接口更改
我已经开始使用TDD。如前面的问题所述,最大的困难是处理接口更改。随着需求的变化,如何减少对测试用例的影响?
解决方案
在编写新接口的代码之前,请先编写测试。
我认为这是接口使用过多的流行说法的原因之一。
但是,我不同意。
当需求发生变化时,测试也应如此。正确的?我的意思是,如果编写测试的标准不再有效,则应重写或者取消该测试。
希望对我们有所帮助,但我想我可能误解了问题。
如果我们遵循"测试优先"方法,则理论上接口更改对测试代码应该没有影响。毕竟,当我们需要更改接口时,首先要更改测试用例以符合需求,然后继续更改接口/实现,直到测试通过。
会有影响。我们只需要接受改变接口将需要时间来首先改变相关的测试用例。没有办法解决这个问题。
但是,我们可以考虑节省时间,因为以后不尝试在此界面中找到难以捉摸的错误,并且在发行周内未修复该错误是完全值得的。
更改界面需要更新使用该界面的代码。在这方面,测试代码与非测试代码没有什么不同。不可避免地需要对该接口的测试进行更改。
通常,当界面更改时,我们会发现"太多"的测试失败了,即,针对很大程度上不相关的功能的测试最终取决于该界面。这可能表明测试范围太广,需要重构。发生这种情况的方式有很多种,但是这里有一个例子,希望能显示出一般的想法和特定的情况。
例如,如果构造Account对象的方式发生了变化,并且这需要更新Order类的所有或者大部分测试,则可能是错误的。大多数Order单元测试可能都不在乎帐户的制作方式,因此重构测试如下:
def test_add_item_to_order(self): acct = Account('Joe', 'Bloggs') shipping_addr = Address('123 Elm St', 'etc' 'etc') order = Order(acct, shipping_addr) item = OrderItem('Purple Widget') order.addItem(item) self.assertEquals([item], order.items)
对此:
def make_order(self): acct = Account('Joe', 'Bloggs') shipping_addr = Address('123 Elm St', 'etc' 'etc') return Order(acct, shipping_addr) def make_order_item(self): return OrderItem('Purple Widget') def test_add_item_to_order(self): order = self.make_order() item = self.make_order_item() order.addItem(item) self.assertEquals([item], order.items)
这种特定的模式是一种创建方法。
这里的一个优点是订单测试方法与创建帐户和地址的方式是隔离的。如果这些接口发生更改,则我们只有一个更改的位置,而不是碰巧使用"帐户和地址"进行的每个测试。
简而言之:测试也是代码,就像所有代码一样,有时它们需要重构。
在TDD中,测试不是测试。它们是可执行规范。 IOW:它们是我们需求的可执行编码。始终牢记这一点。
现在,突然变得很明显:如果需求发生变化,则测试也必须发生变化!这就是TDD的重点!
如果我们正在做瀑布,则必须更改规格文档。在TDD中,我们必须执行相同的操作,只是规范不是用Word编写的,而是用xUnit编写的。
当接口更改时,我们应该期望测试会破裂。如果太多的测试失败,则意味着系统耦合太紧密,太多的事情取决于该接口。我们应该期望一些测试可以打破,但不是很多。
中断测试是一件好事,代码中的任何更改都应中断测试。
如果需求发生变化,那么测试应该是第一件事,而不是界面。
我将从在第一个适当的测试中修改接口设计开始,更新接口以通过新的测试。接口更新通过测试后,我们应该看到其他测试中断了(因为它们将使用过时的接口)。
应该使用新的界面设计来更新其余失败的测试,以使它们再次通过。
以测试驱动方式更新接口将确保更改实际上是必需的并且是可测试的。
"我们应该做些什么来防止我们的代码和测试不受需求的依赖?似乎没有什么。每次更改需求时,我们都必须更改我们的代码和测试。但是也许我们可以简化我们的工作?是的,我们可以。关键原则是:封装了可能会更改的代码。"
http://dmitry-nikolaev.blogspot.com/2009/05/atch-your-changes.html