Set expectations
你不可能把一个老旧的代码野兽只用一晚就转变成优雅的奇迹marvel.你需要如下做法:
- 让自己有好的状态,用15分钟挥舞拳头诅咒之前的程序员
- 开始工作,这个codebase现在归你了。
一上来就给遗产代码增加测试不是好注意,这里有2个原因:
第一个问题是,你接管这个遗产代码后你期望做点什么,而客户的想法未必和你一样。所以先和客户交流,或者和产品经理充分交流,确定需求后再开始。
第二个问题是, 不得不处理 常常的矛盾的遗产代码。遗产代码常常太互相过度依赖或者不好理解,导致难以使用单元测试。除非你先进行重构。但是不经过单元测试就重构更容易导致bugs,特别是你还没有完全理解这些新代码。
这2个问题都导致快速的处理遗产代码是不可能的事情。
工作代码中那些不是十分明确需要改变的代码就别改。如果它运行的很好并且需求也和以前一样,唤醒沉睡的熊(比喻)只能让你远离你的最重要的事。
对于遗产代码,逐步前进,走你能确定的小步。保证自己写的新代码通过测试。当你写新代码时,对现存的代码做小的改进,让总体的代码基础变好。
Getting Started with Legacy Code
做3件事情:
get the code in source control
让代码处于版本控制Source Control。可能它已经处于版本控制,但你应该小心。确认你有所有的存取权利对任何你需要的代码仓库code repository。最好使用Git或者其他版本控制系统让你可以创建和管理branches。这样你可以轻松的通过使用分支来探索源代码的变化,根据需要让scratch pads缓存可以被保存或丢弃。
get the code running
产品环境的设置, 推迁移产品环境到一个更标准的地方。Docker是一个有用的container tools. 可以把产品环境复制到a Docker container.
get the Test Suite running
如果之前的团队不认真的写测试,你的工作就会变难。 你可能有各种测试不通过或者坏掉。
(前辈的一个简单原则:如果你不能在5分钟弄明白一个测试是做什么的,就删除它)
利用这个机会了解遗产代码是如何运行的,但此时还不能代码让它通过测试 。如果你不能弄明白如何让一个测试通过,注释它,甚至删除它;增加一个注释,当你更全面的了解了测试框架后再回来看它。当前全面的测试不是优先事项。 当前优先事项是为了的工作让基础的测试通过。
如果代码使用的是旧版本的RSpec,更新RSpec。
Test-Driven Exploration
black-box testing:
忽略内部结构,测试顶层的输入系统和返回的输出。集成测试,用户输入和系统输出。
因为集成测试是从外部工作的,可以用它来测试遗产代码。不用涉及全面的应用,而是涉及全部功能。
white-box testing:
使用关于系统内部的知识来精确地测试明确的路径。
角色测试:
首先,选择一个方法来测试。这个方法应该涉及一个你计划做的变化 。
然后,写这个测试,我们知道测试会失败。不用深挖内部。先期待一个不可能的结果。让测试告诉你真实结果是什么。
运行测试,测试报告error因为有缺少的依靠,或者测试报告fail因为代码运行了但spit out a different result。
分析:
大多时候会升起error, 因为这里肯定你不知道的一些对象依赖,一些value是不被期待的,或者你在其他方面打破了遗产代码函数的the delicate balance。
你需要弄明白事物。常常不得不创建更多的对象。
最终搞定errors后,你会进一步的理解程序的内部。
然后就可以进行下一个测试了,大多数是在其他测试案例中尝试现存的方法 .
Prescription
当开始给遗产代码写单元测试时,使用测试来探索代码的行为。尝试在不改变原code的情况下写一个passing test,用测试来反应问题。
Pry对此是有帮助的。一旦在console能明白事情,转化这个命令给测试以便它们能反复运行。
What Tool Should I Use? (Legacy Edition)
之前用什么工具,这里也用这个工具。如果没有使用factory,作者推荐用。设置一个factory工具来创建关联会节省时间。
⚠️提示 :
如果原来的程序员使用的工具不足以支持你想要的测试或者现存的测试你觉得是无用的,那么删除它们,从新开始。此时你可以挑选你想要的任何工具。
Dependency Removal 依赖移除
在遗产测试中,依赖是最大的挑战。可能TDD code最帮的优势就是测试会强迫代码的各个独立片段之间最大化的相互独立。
Keep Things Separate
如果你新增或者提取了大量函数,你需要考虑创建你自己的独立类而不是仅仅增加一个方法。
出于测试目的,移动新代码到一个新的类能让测试新的代码更容易,因为新代码较少依赖现存的程序。
不过这样做,也有短期的不好效果,让代码不好懂(因为逻辑增加了)。 在任何案例,你都在原始的混乱代码和更好的重构和组织版本之间的过渡状态。
Legacy Databases, Testing, and You
column contraint 列的约束。
你有可能遇到混乱的数据结构。如奇怪的命名约定或者不常见的ActiveRecord功能。但是它们不影响你测试功能的能力。
你需要小心的是十分数据库增加了非常不明显的约束代码。如foreign key约束。尤其是老版本rails。
从一个测试透明度来看,这个问题更严重。首先业务逻辑在rails代码之外,在数据库里。这样的逻辑很难发现和测试以及改变。更坏的是,foreignkey约束增加了某些对象件的依赖。在测试环境中,一些对象需要一起创建导致了奇怪的bugs。因此需要留意一个重数据库而不信任activerecord的开发团队的遗产代码。
Using Test Doubles to Remove Dependencies
使用double, mock, stub来启动测试,测试那些没有被测试过的代码。
在遗产代码中,这样的好处是使用test doubles可以从程序中隔离单独的class和方法,把关注点放在你所注意的部分。
比如当前测试会用到一个不是当前需要测试的相关方法,你只需要设定一个正确的返回值就行了。这样就把注意力集中在你需要注意的部分。
如,把credit_card_is_valid?设置成一个stub,方便之后的运行。
allow(order).to receive(:credit_card_is_valid?).at_least(:once).and_return(true)
Find the Seam
A seam是一个你可以改变程序的行为但无需改动代码的地方。通过在代码中找到seam并利用它们来测试遗产函数。
A test double可以称为a seam是因为在测试中增加的double,通过授权对一个方法调用产生一个指定的响应,改变了程序代码的行为,但实际上没有执行这个方法。同时,在测试环境中,这个方法的行为改变现存的开发代码也就不会影响到产品代码。
do one thing at a time, to the extent possible.
当你增加新的功能时,不要扩展测试范围。
当你扩展测试范围时,不要试图把代码弄的太整洁。(有时难以避免)
每一步,做的事情尽可能的少,这样一旦发现错误就容易定位到。