在重构遗留代码的同时开发单元测试?

软件测试 单元测试 tdd 遗留系统
2022-01-24 19:56:31

我有一个大型代码库,我需要对其进行重构以使其更好地组合在一起(一个大问题是每个组件之间的界限不够清晰)。由于需要进行一些回归测试来验证任何重构,因此是否有意义:

  1. 为特定代码段创建单元测试
  2. 运行单元测试并确保它通过
  3. 重构代码并检查单元测试是否仍然通过

开始使用 TDD 方法进行重构过程似乎是一个好主意,尽管我最担心的是我在现有代码之后编写单元测试(而不是在单元测试之后编写代码)。

由于我在代码之后编写单元测试,我是否应该首先重构,进行自己的回归测试,然后继续使用 TDD 步骤(编写失败的测试、编写通过代码、重构、重复)?

4个回答

我推荐阅读两本书,因为它们有助于将遗留代码迁移到 TDD:

在 .Net 中有效地使用旧代码
棕地应用程序开发(较新的书,但基于 .NET)。

由于我在代码之后编写单元测试,我是否应该首先重构,进行自己的回归测试,然后继续使用 TDD 步骤(编写失败的测试、编写通过代码、重构、重复)?

在重构之前编写测试。在您开始修改之前,测试将更像是一种规范(代码是 X )。这样,当你重构代码时,测试与现有代码匹配,所以如果你的重构破坏了某些东西,它会让现有客户不满意。

以上 2 本书将为您提供更多关于如何决定在哪里将事情分解成更小(且更易于管理)的部分以及何时不处理的想法。

IMO,您应该在进行任何重构之前继续编写单元测试以通过语句。原因是您希望在进行更改之前处于已知状态。

我认为 Stacy 称之为“抽象分支”

对于遗留代码,我发现最好的方法是在你的代码中找到夹点,在那里很容易插入一个覆盖输入和输出的接口。

复制你的第一个实现,重构,然后有一个回归实现,它运行旧方式和新方式,并在发现任何差异时立即停止。

这样做,我们已经设法无缝地替换了一些东西,我的直觉告诉我,无论我多么努力,我们都会遇到麻烦,但它没有问题地工作。

(* 但要注意性能等非功能性差异!)

你需要一个测试来证明重构没有破坏任何东西。

我非常喜欢@Squirrel 描述的基于 AB 理论的测试。

我也是Michael Feathers 的《有效地使用遗留代码》中的特征测试的忠实粉丝。

就个人而言,这是我在 90% 的遗留代码工作中使用的技术(它是表征测试的一种变体)。

  1. 减少到功能,
  2. 跑步,
  3. 通过批准测试 (www.approvaltests.com) 捕获结果。

第 1 步:假设您正在重构一堆功能代码。这意味着; 所有使用的参数都传入,没有副作用,总是确定性的(纯的);返回所有有用的结果。

例如,你有一个方法string Foo(int a, boolean b)这很容易测试,只需输入一些数据,然后按照步骤 2 和 3进行操作,您就会得到很好的特性测试。

当然这不是现实世界,在现实世界中我会得到类似的东西:void Foo()

所以首先我会弄清楚它的副作用是什么。它们可能是以下组合:1. 更改全局变量,2. 更改数据库,3. 更改文件,4. 进行 Web API 调用。

那么我将记录所有这些更改:

Log(sql)

Statement.Execute(sql)

现在我可以将功能视为

Log Foo()

然后运行它,看看会发生什么,它可能不是确定性的,因为你没有传入任何变量,当你开始看到发生了什么时,你可以开始查看需要传入的内容。假设这是一个数据库。那么你需要有效地进行回滚。

database.startTransaction()

log = LogToString()

Foo()

database.rollback();

Approvals.Approve(log)