无需过度抽象的自动验收测试中的代码重用
Code reuse in automated acceptance tests without excessive abstraction
我最近被聘为我工作团队的一员,该团队的重点是为我们公司的 3D 建模软件编写验收测试套件。我们使用内部 C# 框架来编写和 运行ning 它们,这实际上相当于子classing TestBase class 并覆盖 Test() 方法,通常所有设置,测试,拆解完成。
在编写测试时,我注意到我的很多代码最终变成了我经常重写的样板代码。我一直有兴趣尝试提取该代码以使其可重用并使我的代码更干燥,但我一直在努力寻找一种方法来做到这一点,而不会过度抽象我的测试,因为它们应该在很大程度上是独立的。我尝试了多种方法,但它们 运行 遇到了问题:
- 使用继承: 最天真的解决方案,一开始效果很好,让我可以快速编写测试,但 运行 进入了通常的陷阱,即测试 classes 变得过于死板,无法在堂兄弟 classes 之间共享我的代码,并且逻辑在继承树中被混淆。例如,我有抽象的 RotateVolumeTest 和 TranslateVolumeTest classes,它们都继承自 ModifyVolumeTest,但我很快就知道我将要旋转 和 平移一个体积,所以这将是一个问题。
- 通过接口使用组合:这解决了以前方法的很多问题,让我可以灵活地为我的测试重用代码,但它会导致很多抽象和似乎 'class bloat' -- 现在我有 IVolumeModifier、ISetsUp 等,所有这些都使代码在测试中实际执行的操作变得不那么清晰。
- 静态实用程序中的帮助方法 class: 这非常有帮助,尤其是对于使用工厂模式快速生成测试所需的复杂对象。但是,感觉 'icky' 将一些我知道实际上不是很通用的方法放在那里,而是用于需要共享非常具体代码的一小部分测试。
- 使用像 xUnit.net 或类似的测试框架通过测试套件中的 [SetUp] 和 [TearDown] 方法共享代码,通常都在同一个 class 中: 我非常喜欢这种方法,因为它提供了我想要的可重用性而无需从测试代码中抽象出来,但我的团队对此不感兴趣;我试图展示为我们的测试采用这样的框架的潜在好处,但共识似乎是不值得在重写我们现有的测试 classes 时进行重构。这是一个有效的观点,我认为我不太可能进一步说服他们,尤其是作为一个相对较新的雇员,所以除非我想让我的测试 classes 与代码库的其余部分大不相同,这个不在 table.
- 复制并粘贴代码到需要的地方:这是我们当前使用的方法,以及#3 和向 TestBase 添加方法。我知道对于测试代码 copy/paste 编码是否接受 table 当然不是用于生产的意见不同,但我觉得使用这种方法会使我的测试更难维护或更改长 运行,因为我现在有 N 个地方,如果出现错误,我需要修复逻辑(已经有很多,只需要修复一个)。
在这一点上,我真的不确定我还有什么其他选择,只能选择 #5,因为它会降低我快速或稳健地编写测试的能力,以便与当前代码保持一致根据。非常感谢任何想法或意见。
我个人认为对于一个成功的测试框架来说最重要的是抽象。使编写测试尽可能简单。对我来说,关键点是你最终会进行更多的测试,而作者更多地关注他们正在测试什么,而不是如何编写测试。我见过的每一个不关注抽象的测试框架都以多种方式失败,最终成为可维护性的噩梦。
如果只在一个测试中没有在其他任何地方使用该逻辑 class 则保留该测试 class 但如果在多个地方需要它,请稍后重构
除了#5,我会选择所有选项。
我最近被聘为我工作团队的一员,该团队的重点是为我们公司的 3D 建模软件编写验收测试套件。我们使用内部 C# 框架来编写和 运行ning 它们,这实际上相当于子classing TestBase class 并覆盖 Test() 方法,通常所有设置,测试,拆解完成。
在编写测试时,我注意到我的很多代码最终变成了我经常重写的样板代码。我一直有兴趣尝试提取该代码以使其可重用并使我的代码更干燥,但我一直在努力寻找一种方法来做到这一点,而不会过度抽象我的测试,因为它们应该在很大程度上是独立的。我尝试了多种方法,但它们 运行 遇到了问题:
- 使用继承: 最天真的解决方案,一开始效果很好,让我可以快速编写测试,但 运行 进入了通常的陷阱,即测试 classes 变得过于死板,无法在堂兄弟 classes 之间共享我的代码,并且逻辑在继承树中被混淆。例如,我有抽象的 RotateVolumeTest 和 TranslateVolumeTest classes,它们都继承自 ModifyVolumeTest,但我很快就知道我将要旋转 和 平移一个体积,所以这将是一个问题。
- 通过接口使用组合:这解决了以前方法的很多问题,让我可以灵活地为我的测试重用代码,但它会导致很多抽象和似乎 'class bloat' -- 现在我有 IVolumeModifier、ISetsUp 等,所有这些都使代码在测试中实际执行的操作变得不那么清晰。
- 静态实用程序中的帮助方法 class: 这非常有帮助,尤其是对于使用工厂模式快速生成测试所需的复杂对象。但是,感觉 'icky' 将一些我知道实际上不是很通用的方法放在那里,而是用于需要共享非常具体代码的一小部分测试。
- 使用像 xUnit.net 或类似的测试框架通过测试套件中的 [SetUp] 和 [TearDown] 方法共享代码,通常都在同一个 class 中: 我非常喜欢这种方法,因为它提供了我想要的可重用性而无需从测试代码中抽象出来,但我的团队对此不感兴趣;我试图展示为我们的测试采用这样的框架的潜在好处,但共识似乎是不值得在重写我们现有的测试 classes 时进行重构。这是一个有效的观点,我认为我不太可能进一步说服他们,尤其是作为一个相对较新的雇员,所以除非我想让我的测试 classes 与代码库的其余部分大不相同,这个不在 table.
- 复制并粘贴代码到需要的地方:这是我们当前使用的方法,以及#3 和向 TestBase 添加方法。我知道对于测试代码 copy/paste 编码是否接受 table 当然不是用于生产的意见不同,但我觉得使用这种方法会使我的测试更难维护或更改长 运行,因为我现在有 N 个地方,如果出现错误,我需要修复逻辑(已经有很多,只需要修复一个)。
在这一点上,我真的不确定我还有什么其他选择,只能选择 #5,因为它会降低我快速或稳健地编写测试的能力,以便与当前代码保持一致根据。非常感谢任何想法或意见。
我个人认为对于一个成功的测试框架来说最重要的是抽象。使编写测试尽可能简单。对我来说,关键点是你最终会进行更多的测试,而作者更多地关注他们正在测试什么,而不是如何编写测试。我见过的每一个不关注抽象的测试框架都以多种方式失败,最终成为可维护性的噩梦。
如果只在一个测试中没有在其他任何地方使用该逻辑 class 则保留该测试 class 但如果在多个地方需要它,请稍后重构
除了#5,我会选择所有选项。