下文将展现一个测试驱动开发(TDD)的实例,但愿能给想要开始实践TDD的朋友一个演示。本实例将采用python进行演示,若是您以前没用过python,也没必要担忧,这是一个很简洁易懂的语言。本人也会在下文实例中对出现的python语法进行解释。html
假设python语法中没有 乘法(*) 这个操做符,咱们要本身实现一个简单的乘法运算函数。python
开始以前咱们要记住TDD的核心,那就是:写功能代码以前先写测试,用测试去“驱动”功能实现。换句话说就是“只有在测试失败的时候才能添加或修改功能代码”。具体步骤以下:程序员
步骤很简单,3~4步能够重复执行n遍直到测试经过。编程
但愿咱们能够一块儿实现这个demo,这样你就能和我一块儿体验到TDD的乐趣。框架
执行python --version
,若是打印出python版本则表示已经安装了python。以下:函数
~ python --version Python 3.7.2
假设咱们要实现一个名为multiply的函数,函数能够输入两个数字参数,返回两个数字相乘的结果。建立一个文件名为tdd-demo.py,咱们能够先写以下测试代码:性能
"""tdd-demo.py""" import unittest class MultiplyTest(unittest.TestCase): def test_multiply(self): self.assertEqual(multiply(2, 3), 6) if __name__ == '__main__': unittest.main()
assertEqual(a, b)
,意思是断言 a == b
,想了解更多关于unittest框架的读者能够点击这里。if __name__ == '__main__':unittest.main()
表明运行该文件时执行unittest.main()
,既:运行单元测试。上面测试代码的意思就是测试 multiply(2, 3)
应该等于 6
。单元测试
执行 python tdd-demo.py
命令。测试
➜ python tdd-demo.py E ====================================================================== ERROR: test_multiply (__main__.MultiplyTest) ---------------------------------------------------------------------- Traceback (most recent call last): File "tdd-demo.py", line 7, in test_multiply self.assertEqual(multiply(2, 3), 6) NameError: name 'multiply' is not defined ---------------------------------------------------------------------- Ran 1 test in 0.000s FAILED (errors=1)
执行后返回如上报错,NameError: name 'multiply' is not defined
,意思是multiply这个函数未定义。知道测试失败的缘由后,咱们添加功能代码以下:unix
"""tdd-demo.py""" import unittest def multiply(): pass class MultiplyTest(unittest.TestCase): def test_multiply(self): self.assertEqual(multiply(2, 3), 6) if __name__ == '__main__': unittest.main()
咱们定义了一个空白的函数multiply,如今再跑一次测试看看
➜ python tdd-demo.py E ====================================================================== ERROR: test_multiply (__main__.MultiplyTest) ---------------------------------------------------------------------- Traceback (most recent call last): File "tdd-demo.py", line 11, in test_multiply self.assertEqual(multiply(2, 3), 6) TypeError: multiply() takes 0 positional arguments but 2 were given ---------------------------------------------------------------------- Ran 1 test in 0.000s FAILED (errors=1)
上述错误的意思是函数multiply定义了0个传参,可是multiply(2, 3)
传递了2个参数。由此咱们知道是忘记给函数multiply定义传参了,咱们修改代码以下:
"""tdd-demo.py""" import unittest def multiply(a, b): pass class MultiplyTest(unittest.TestCase): def test_multiply(self): self.assertEqual(multiply(2, 3), 6) if __name__ == '__main__': unittest.main()
从新执行 python tdd-demo.py
➜ python tdd-demo.py F ====================================================================== FAIL: test_multiply (__main__.MultiplyTest) ---------------------------------------------------------------------- Traceback (most recent call last): File "tdd-demo.py", line 11, in test_multiply self.assertEqual(multiply(2, 3), 6) AssertionError: None != 6 ---------------------------------------------------------------------- Ran 1 test in 0.000s FAILED (failures=1)
和预期的同样,AssertionError: None != 6
,函数multiply(2, 3)
的结果返回None,没定义函数的返回值固然返回None(python中未定义函数返回值时,则默认返回None),让咱们来经过这个测试!
"""tdd-demo.py""" import unittest def multiply(a, b): return 6 class MultiplyTest(unittest.TestCase): def test_multiply(self): self.assertEqual(multiply(2, 3), 6) if __name__ == '__main__': unittest.main()
上面用了一个取巧的办法,不过如今先别急,咱们会在后面修改它,咱们先执行测试看看。
➜ python tdd-demo.py . ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
成功了!!
可是咱们知道咱们的multiply函数如今根本还不能用!每次乘法都返回6的计算器也没人敢用!!
这时候测试都经过了,想要修改功能代码,咱们就须要添加新的测试了。
为了节省篇幅,下文只列出部分代码。
class MultiplyTest(unittest.TestCase): def test_multiply(self): self.assertEqual(multiply(2, 3), 6) self.assertEqual(multiply(3, 5), 15)
咱们添加了新的测试,再执行看看。
➜ python tdd-demo.py F ====================================================================== FAIL: test_multiply (__main__.MultiplyTest) ---------------------------------------------------------------------- Traceback (most recent call last): File "tdd-demo.py", line 12, in test_multiply self.assertEqual(multiply(3, 5), 15) AssertionError: 6 != 15 ---------------------------------------------------------------------- Ran 1 test in 0.000s FAILED (failures=1)
很好,一切如咱们所预料,取巧的办法确定无法经过完善的测试,乖乖写代码吧。
还记得上文说的,不可以使用 乘法(*) 操做符吗? 可是 加法(+) 是可使用的,咱们知道 2 * 3 = 3 + 3
,也就是2个3相加,乘法a * b
实际上是a个b相加。知道原理后咱们能够实现代码以下:
def multiply(a, b): result = 0 while a > 0: a = a - 1 result = result + b return result
while a > 0:
表示当 a > 0 时,执行缩进块里的内容既:
a = a - 1 result = result + b
执行测试看看
python tdd-demo.py . ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
成功了!咱们用大点的数字相乘看看。
class MultiplyTest(unittest.TestCase): def test_multiply(self): self.assertEqual(multiply(2, 3), 6) self.assertEqual(multiply(3, 5), 15) def test_multiply_with_larger_number(self): self.assertEqual(multiply(512, 2), 1024) self.assertEqual(multiply(10000, 10000), 100000000)
具体的测试代码,能够自行发挥,可是数字不要太大,咱们的计算器性能不太好^_^。咱们执行测试看看
➜ python tdd-demo.py .. ---------------------------------------------------------------------- Ran 2 tests in 0.001s OK
成功了哈哈,那若是是传入的参数是负数呢?如今让我添加负数的测试代码看看(ps:记住,想要添加功能前先添加测试代码。能经过测试的代码就是没问题的代码。)
class MultiplyTest(unittest.TestCase): def test_multiply(self): self.assertEqual(multiply(2, 3), 6) self.assertEqual(multiply(3, 5), 15) def test_multiply_with_larger_number(self): self.assertEqual(multiply(512, 2), 1024) self.assertEqual(multiply(10000, 10000), 100000000) def test_multiply_with_negative_number(self): self.assertEqual(multiply(-5, 10), -50) self.assertEqual(multiply(5, -10), -50) self.assertEqual(multiply(-5, -5), 25)
执行测试
➜ python tdd-demo.py ..F ====================================================================== FAIL: test_multiply_with_negative_number (__main__.MultiplyTest) ---------------------------------------------------------------------- Traceback (most recent call last): File "tdd-demo.py", line 23, in test_multiply_with_negative_number self.assertEqual(multiply(-5, 10), -50) AssertionError: 0 != -50 ---------------------------------------------------------------------- Ran 3 tests in 0.001s FAILED (failures=1)
果真失败了,AssertionError: 0 != -50
, multiply(-5, 10) 返回了0,让咱们看看代码哪里出问题了。找到了 while a > 0
: 由于a = -5 < 0
因此直接返回result = 0
了。知道缘由后,咱们能够把负数符号先抽出来,让咱们改一下代码。(ps:能够本身试着去实现,期间不断地靠测试代码来验证,你会发现有测试代码做保证,功能代码即可以大胆试错,后面你会发现实现功能会比正常开发快不少。)最后我写出以下代码:
def multiply(a, b): result = 0 is_negative = False if a < 0: is_negative = True a = - a while a > 0: a = a - 1 result = result + b if is_negative: result = - result return result
判断 a 是否小于0,若是是的话,就标记一下并把负号抽出来,再把结果添加上负号,是否是很像初学负数运算时的计算步骤。咱们再跑测试看看
➜ python tdd-demo.py ... ---------------------------------------------------------------------- Ran 3 tests in 0.001s OK
成功了!!心思细腻的小朋友可能会发现若是传参a或b为0的时候会怎么样?的确,这样的边界状况没有考虑到,这也是编程让大部分人以为头疼的地方。咱们添加测试看看
class MultiplyTest(unittest.TestCase): def test_multiply(self): self.assertEqual(multiply(2, 3), 6) self.assertEqual(multiply(3, 5), 15) def test_multiply_with_larger_number(self): self.assertEqual(multiply(512, 2), 1024) self.assertEqual(multiply(10000, 10000), 100000000) def test_multiply_with_negative_number(self): self.assertEqual(multiply(-5, 10), -50) self.assertEqual(multiply(5, -10), -50) self.assertEqual(multiply(-2, -3), 6) def test_multiply_with_zero_number(self): self.assertEqual(multiply(0, 5), 0) self.assertEqual(multiply(2, 0), 0) self.assertEqual(multiply(0, 0), 0)
执行测试
➜ python tdd-demo.py .... ---------------------------------------------------------------------- Ran 4 tests in 0.001s OK
竟然成功了!本身都没想到,咱们回顾一下功能代码,最初定义了result = 0
,a = 0
的时候就直接返回了result
,也就是0
。
到这里,本文的TDD之旅就结束了。若是你不尽兴,能够试着用TDD的方式来实现多个(大于2个)数字相乘,或者当输入的参数不是数字的时候,返回或抛出一些有用的message提示。这也是实际开发中常常用到的场景。
最终的代码以下:
"""tdd-demo.py""" import unittest def multiply(a, b): result = 0 is_negative = False if a < 0: is_negative = True a = - a while a > 0: a = a - 1 result = result + b if is_negative: result = - result return result class MultiplyTest(unittest.TestCase): def test_multiply(self): self.assertEqual(multiply(2, 3), 6) self.assertEqual(multiply(3, 5), 15) def test_multiply_with_larger_number(self): self.assertEqual(multiply(512, 2), 1024) self.assertEqual(multiply(10000, 10000), 100000000) def test_multiply_with_negative_number(self): self.assertEqual(multiply(-5, 10), -50) self.assertEqual(multiply(5, -10), -50) self.assertEqual(multiply(-2, -3), 6) def test_multiply_with_zero_number(self): self.assertEqual(multiply(0, 5), 0) self.assertEqual(multiply(2, 0), 0) self.assertEqual(multiply(0, 0), 0) if __name__ == '__main__': unittest.main()
ps:本文的目的就是简单介绍一下TDD的实例和步骤,借用乘法multiply的这个函数来演示TDD在具体开发中的实践。具体代码实现确定有不完善的地方,若是有错误或遗漏的地方,望读者指出。
若是您看完文章,对于TDD有了不同的认识,或想要在接下来的开发中使用TDD,那么本文就已经完成了它的使命。
最后祝每一个程序员都能“控制”代码而不是被代码“控制”,而“控制”代码最好的方式就是用测试代码“控制它”。本文只是以一个简单的乘法函数做为TDD的演示,读者可能在实际的项目开发中会遇到不少测试代码“很差写”的状况。同时迫于项目deadline的压力,想要快速的实现功能并上线,就打算放弃使用TDD的方式。但我也但愿你可以保持先写测试的习惯,几个月后,我相信你会感谢本身当时坚持TDD的决定。
扩展阅读: