为什么 TDD
TDD 测试驱动开发是一种开发方法论,先写测试再实践代码
- ✅ 更有效率与目的的开发:撰写程序前已规划好「期望」
- ✅ 重构代码时更为方便:重构程序前重要的片段已经被测试过,如果过程中出错测试会指向哪个环节出错
- ✅ 程序都是可以被测试的:测试先于实践代码
- ✅ 更容易实现 Atomic Commit:TDD 更容易划清提交的界线,使每个提交只做一件事,尽可能的小但完整
- ✅ 更松散的耦合:容易撰写松散耦合的代码
- ✅ 测试成为文档:测试可以帮助开发者更好的描述代码的行为
陈旧代码是那些有价值但你害怕改动的代码,为什么害怕改动?因为复杂、没有记载、太久没人碰过,TDD 目的便是打造一个工作流程能够验证代码的行为,让开发者能够更有信心的重构、更动代码。
TDD 规则与执行流程
-
三个规则:
- 在撰写一个单元测试前,不可撰写任何产品程序
- 只撰写刚好无法通过的单元测试,不能编译也算无法通过
- 只撰写刚好能通过当前测试的产品程序
-
三个执行流程:
- 红:撰写失败测试
- 绿:撰写最低限度的代码使测试通过
- 重构:审视代码
实际单元测试 TDD: FizzBuzz
一、前置准备
# FizzBuzz 规格文件:
1. 接受数字并输出字符串2. 如果输入的数字是 3 的倍数,输出 "Fizz"3. 如果输入的数字是 5 的倍数,输出 "Buzz"4. 如果输入的数字是 3 和 5 的倍数,输出 "FizzBuzz"
二、红灯
根据第一个需求:「输入数字输出字符串」,我们可以先写一个测试作为开端,并可以期望它会失败,因为我们的实践代码还是空白:
test('输入数字_输出字符串', () => { expect(fizzbuzz(1)).toBe('1');});
// 实践代码export const fizzbuzz = (input) {};
三、绿灯
在撰写实践代码后可以期望测试通过「输入数字_输出字符串」测试:
export const fizzbuzz = (input: number): string => { return input.toString();};
四、重构
绿灯时就算是达成了产品需求,这时候代码就算达成了需求但可能还不够完美,可以于该阶段进行重构,如果没有要重构则可以继续重复红灯、绿灯、重构的流程。
五、最终成果
最终经过 4 次红灯、绿灯、重构的流程后代码如下:
// 实践代码export const fizzbuzz = (input: number): string => { // 初始版本 // const isFizz = input % 3 === 0 // const isBuzz = input % 5 === 0 // const isFizzBuzz = isFizz && isBuzz // if (isFizzBuzz) { // return 'FizzBuzz' // } // if (isFizz) { // return 'Fizz' // } // if (isBuzz) { // return 'Buzz' // } // return input.toString()
// 重构后 const fizz = input % 3 === 0 ? 'Fizz' : ''; const buzz = input % 5 === 0 ? 'Buzz' : ''; const output = `${fizz}${buzz}`; return output || input.toString();};
// 單元測試代碼test('输入数字_输出字符串', () => { expect(fizzbuzz(1)).toBe('1');});
test('输入3的倍数_输出Fizz', () => { expect(fizzbuzz(9)).toBe('Fizz');});
test('输入5的倍数_输出Buzz', () => { expect(fizzbuzz(10)).toBe('Buzz');});
test('输入3和5的倍数_输出FizzBuzz', () => { expect(fizzbuzz(15)).toBe('FizzBuzz');});
总结
有良好的自动化测试作为文件可以替代码形成保护网,确保程序的行为被记载与验证。
TDD 借由测试主导开发流程
并非撰写测试就是 TDD,以上实际撰写过 TDD 就可以体会其目标并不在于撰写测试(测试只是它的副作用)而是在于通过测试主导出良好的软件!
TDD 着重测试行为(What)而非实践过程(How)
撰写测试于实践代码后,容易使测试与实践代码相互耦合,使用 TDD 强制我们从行为层面去审视需求,避免写出着重在实践过程的测试。
好测试的代码与好代码不谋而合
好测试的代码具备相同的特质:高内聚低耦合、关注点分离……借由 TDD 开发的程序自然的需要具备这些特质。
延伸阅读
- Test-Driven Development in JS with Acceptance Tests - Bran van der Meer
- Unit Testing Is The BARE MINIMUM - Continuous Delivery