在另一个问题中,TDD的一个问题是在重构期间和之后使测试套件与代码基保持同步。
现在,我非常喜欢重构。我不会放弃去做TDD的。但是,我也经历过测试编写的问题,以至于轻微的重构会导致大量的测试失败。
如何在重构时避免中断测试?
编辑:我写了一个新的问题问我想问什么 (但保留了这个有趣的变体)。
发布于 2010-09-21 13:28:04
你想要做的并不是真正的重构。通过重构,根据定义,您不会改变您的软件所做的事情,而是会改变它的工作方式。
从所有绿色测试(全部通过)开始,然后“在引擎盖下”进行修改(例如,将方法从派生类移到基类,提取方法,或用Builder封装组合,等等)。你的测试应该还能通过。
您所描述的似乎不是重构,而是重新设计,这也增强了被测试软件的功能。TDD和重构(正如我在这里试图定义的那样)没有冲突。您仍然可以重构(绿色-绿色)并应用TDD (红色-绿色)来开发“增量”功能。
发布于 2010-09-21 13:16:07
拥有单元测试的好处之一是让您能够自信地重构。
如果重构不改变公共接口,那么您将单元测试保持原样,并确保重构后它们都通过。
如果重构确实改变了公共接口,那么应该首先重写测试。重构直到新测试通过。
我绝不会避免任何重构,因为它破坏了测试。编写单元测试可能是一种痛苦,但从长远来看,它是值得的。
发布于 2014-12-06 13:00:47
与其他答案相反,重要的是要注意,当被测试的系统(SUT)被重构时,一些测试方法可能变得脆弱,如果测试是白盒的话。
如果我使用一个模拟框架来验证在模拟上调用的方法的顺序(当顺序无关,因为调用没有副作用);那么如果我的代码对那些不同顺序的方法调用比较干净,并且我重构,那么我的测试就会中断。一般来说,模拟会给测试带来脆弱性。
如果我通过公开SUT的私有成员或受保护成员来检查SUT的内部状态(我们可以在visual basic中使用"friend“,或者在c#中将访问级别升级为”in“,并使用”in“;在许多面向对象语言(包括c#)中,可以使用"测试专用子类”),那么类的内部状态就会突然变得重要--您可能会将类重构为黑匣子,但是白盒测试将失败。假设一个字段被重用为不同的东西(不是很好的实践!)当SUT改变状态时--如果我们将它分成两个字段,我们可能需要重写损坏的测试。
测试特定的子类也可以用于测试受保护的方法,这可能意味着从生产代码的角度来看重构是从测试代码的角度来看的一个重大变化。将几条线移进或移出受保护的方法可能不会产生任何副作用,但会破坏测试。
如果我使用"测试钩“或任何其他特定于测试的或条件编译代码,就很难确保测试不会因为内部逻辑上的脆弱依赖而中断。
因此,为了防止测试与SUT的内部细节相结合,它可能有助于:
上面所有的点都是测试中使用的白盒耦合的例子.因此,为了完全避免重构破坏测试,请使用SUT的黑匣子测试。
免责声明:为了在这里讨论重构,我更广泛地使用这个词,包括改变内部实现而不产生任何明显的外部影响。一些纯粹主义者可能不同意,只提到Martin和Kent的“重构”一书,这本书描述了原子重构操作。
在实践中,我们倾向于采取比所描述的原子操作更大的非破坏步骤,特别是,使生产代码与外部行为相同的更改可能不会让测试通过。但我认为将“替代行为相同的另一种算法”作为重构是公平的,我认为Fowler也同意。Martin自己说,重构可能会破坏测试:
当您编写一个模拟测试时,您正在测试SUT的出站呼叫,以确保它与其供应商进行适当的交谈。经典的测试只关心最终状态--而不是该状态是如何导出的。因此,Mockist测试更多地耦合到方法的实现中。改变对协作者调用的性质通常会导致模拟测试中断。与实现耦合的...也会干扰重构,因为与传统测试相比,实现更改更有可能破坏测试。福勒- 模拟不是存根
https://softwareengineering.stackexchange.com/questions/5898
复制相似问题