下面将展示一个测试驱动开发(TDD)的示例,希望能给想要开始实践TDD的朋友一个演示。本例将使用python进行演示。如果你以前没有用过python,别担心,它是一门非常简洁易懂的语言。我还将解释出现在下面示例中的python语法。假设python语法中没有乘法(*)运算符,我们要自己实现一个简单的乘法函数。在开始之前,我们必须记住TDD的核心,即:先写测试再写功能代码,用测试来“驱动”功能实现。换句话说,“只有在测试失败时才能添加或修改功能代码”。具体步骤如下:添加测试。执行测试(测试失败)。实现功能代码。执行测试并通过测试(如果测试失败,则返回步骤3)。回到第一步。步骤很简单,步骤3~4可以重复n次,直到测试通过。希望我们能一起实现这个demo,让你和我一起体验TDD的乐趣。注意:请务必先安装python。unix系统默认会安装python,执行python--version。如果打印出python版本,说明已经安装了python。如下:~python--versionPython3.7.2假设我们要实现一个叫做multiply的函数,它可以输入两个数字参数,返回两个数字相乘的结果。创建一个名为tdd-demo.py的文件,我们可以先编写如下测试代码:(2,3),6)if__name__=='__main__':unittest.main()unittest是python的单元测试标准库,它提供了更加人性化的测试结果展示,也提供了很多测试方法和hooks.本文只使用assertEqual(a,b),意思是断言a==b。想进一步了解unittest框架的读者可以点击这里。if__name__=='__main__'代码末尾:unittest.main()表示运行文件时执行unittest.main(),即运行单元测试。上面测试代码的意思是测试multiply(2,3)应该等于6。执行pythontdd-demo.py命令。?pythontdd-demo.pyE============================================================================错误:test_multiply(__main__.MultiplyTest)-------------------------------------------------------------------回溯(最近调用最后):文件“tdd-demo.py”,第7行,在test_multiplyself.assertEqual(multiply(2,3),6)NameError:name'multiply'isnotdefined-------------------------------------------------------------------Ran1testin0.000sFAILED(errors=1)执行后返回以上错误,NameError:name'multiply'is未定义,意思是multiply这个函数是未定义的。知道测试失败的原因后,我们添加函数代码如下:"""tdd-demo.py"""importunittestdefmultiply():passclassMultiplyTest(unittest.TestCase):deftest_multiply(self):self.assertEqual(multiply(2,3),6)if__name__=='__main__':unittest.main()我们定义了一个空白函数multiply,现在运行另一个测试看看?pythontdd-demo.pyE=========================================================================错误:test_multiply(__main__.MultiplyTest)------------------------------------------------追溯(最近一次通话最后一次):文件“tdd-demo.py”,第11行,在test_multiplyself.assertEqual(multiply(2,3),6)TypeError:multiply()接受0个位置参数,但给出了2个----------------------------------------------------------------------Ran1testin0.000sFAILED(errors=1)上面的错误表示函数multiply定义了0个参数,但是multiply(2,3)传递了2个参数。由此我们知道我们忘记给函数乘法定义传递参数,我们修改代码如下:"""tdd-demo.py"""importunittestdefmultiply(a,b):passclassMultiplyTest(unittest.TestCase):deftest_multiply(self):self.assertEqual(multiply(2,3),6)if__name__=='__main__':unittest.main()重新执行pythontdd-demo.py?pythontdd-demo.pyF=========================================================================失败:test_multiply(__main__.MultiplyTest)------------------------------------------------------------------回溯(最近调用最后):文件“tdd-demo.py”,第11行,在test_multiplyself.assertEqual(multiply(2,3),6)AssertionError:None!=6---------------------------------------------------------------Ran1testin0.000sFAILED(failures=1)符合预期,AssertionError:None!=6,函数multiply(2,3)的结果返回None,返回未定义函数的值返回None(在python中当返回函数的值未定义,默认返回None),让我们通过这个测试!"""tdd-demo.py"""importunittestdefmultiply(a,b):return6classMultiplyTest(unittest.TestCase):deftest_multiply(self):self.assertEqual(multiply(2,3),6)if__name__=='__main__':unittest.main()使用了上面的tricky方法,不过现在不用着急,后面我们会修改它,我们先执行一下测试看看?pythontdd-demo.py.----------------------------------------------------------------------在0.000sOK中进行1次测试成功!!但是我们知道我们的乘法函数现在根本不起作用!没有人敢用每次乘法返回6的计算器!!此时,测试已经通过。如果我们要修改功能代码,我们需要添加一个新的测试。为了节省篇幅,下面只列出部分代码。classMultiplyTest(unittest.TestCase):deftest_multiply(self):self.assertEqual(multiply(2,3),6)self.assertEqual(multiply(3,5),15)我们添加了一个新的测试,然后执行它看起来。?pythontdd-demo.pyF===========================================================================失败:test_multiply(__main__.MultiplyTest)-----------------------------------------------------------------回溯(最近调用最后):文件“tdd-demo.py”,第12行,在test_multiplyself.assertEqual(multiply(3,5),15)AssertionError:6!=15-----------------------------------------------------------------Ran1testin0.000sFAILED(failures=1)非常好,一切都如我们所料,刁钻的方法肯定过不了完美测试,乖乖写代码吧。还记得上面不能使用乘法(*)运算符吗?但可以使用加号(+)。我们知道2*3=3+3,也就是两个3相加,乘法a*b其实就是a和b相加。知道原理后,我们可以实现代码如下:defmultiply(a,b):result=0whilea>0:a=a-1result=result+breturnresultwhilea>0:表示当a>0,收缩执行块中的内容为:a=a-1result=result+b执行测试,看pythontdd-demo.py.------------------------------------------------------------------------在0.000秒内运行1次测试OK成功了!让我们将较大的数字相乘。类MultiplyTest(unittest.TestCase):deftest_multiply(self):self.assertEqual(multiply(2,3),6)self.assertEqual(multiply(3,5),15)deftest_multiply_with_larger_number(self):self.assertEqual(multiply(512,2),1024)self.assertEqual(multiply(10000,10000),100000000)具体的测试代码可以自己玩,但是数字不要太大,我们的计算器性能不是很好^_^。让我们执行测试看看?pythontdd-demo.py..-----------------------------------------------------------------在0.001sOK跑了2次测试成功哈哈,如果是incoming呢负参数?下面我把负数的测试代码加进去看看(ps:记住,要加功能之前先加测试代码,能通过测试的代码就是没有问题的代码。)classMultiplyTest(unittest.TestCase):deftest_multiply(self):self.assertEqual(multiply(2,3),6)self.assertEqual(multiply(3,5),15)deftest_multiply_with_larger_number(self):self.assertEqual(multiply(512,2),1024)self.assertEqual(multiply(10000,10000),100000000)deftest_multiply_with_negative_number(self):self.assertEqual(multiply(-5,10),-50)self.assertEqual(multiply(5,-10),-50)self.assertEqual(multiply(-5,-5),25)执行测试?pythontdd-demo.py..F====================================================================失败:test_multiply_with_negative_number(__main__.MultiplyTest)----------------------------------------------------------------------Traceback(最近调用最后):文件“tdd-demo.py”,第23行,在test_multiply_with_negative_numberself.assertEqual(multiply(-5,10),-50)AssertionError:0!=-50------------------------------------------------------------------在0.001sFAILED(failures=1)跑了3次测试真的失败了,AssertionError:0!=-50,multiply(-5,10)returned0,让我们看看我发现的代码错在哪里whilea>0:因为a=-5<0,直接返回result=0。知道原因后,我们可以先把负号提取出来,我们改一下代码。(ps:可以自己尝试实现,不断的靠测试代码来验证,你会发现测试代码是有保障的,功能代码可以大胆试错,后面你会发现函数的实现会比正常开发快很多。)最后我写了下面的代码:defmultiply(a,b):result=0is_negative=Falseifa<0:is_negative=Truea=-awhilea>0:a=a-1result=result+bifis_negative:result=-resultreturnresult判断a是否小于0,如果是则标记并提取负号,然后在result中加上负号,是不是和初学者学习负数算术时的计算步骤很相似?让我们再次运行测试?pythontdd-demo.py...--------------------------------------------------------------------在0.001s内进行3次测试,成功!!心思细腻的小朋友可能会发现,如果参数a或b为0会怎样?确实,没有考虑到这样的边界条件,这也是编程让大多数人感到头疼的地方。让我们添加一个测试来查看classMultiplyTest(unittest.TestCase):deftest_multiply(self):self.assertEqual(multiply(2,3),6)self.assertEqual(multiply(3,5),15)deftest_multiply_with_larger_number(self):self.assertEqual(multiply(512,2),1024)self.assertEqual(multiply(10000,10000),100000000)deftest_multiply_with_negative_number(self):self.assertEqual(multiply(-5,10),-50)self.assertEqual(multiply(5,-10),-50)self.assertEqual(multiply(-2,-3),6)deftest_multiply_with_zero_number(self):self.assertEqual(multiply(0,5),0)self.assertEqual(multiply(2,0),0)self.assertEqual(multiply(0,0),0)执行测试?pythontdd-demo.py....-----------------------------------------------------------------0.001sOK跑了4次居然成功了!我什至没有想到它。让我们回顾一下函数代码。当初始定义result=0和a=0时,直接返回result,即0。至此,本文的TDD之旅结束。如果你不爽,可以尝试使用TDD实现多个(大于2)数的乘法,或者在输入参数不是数字时返回或抛出一些有用的消息提示。这也是实际开发中经常用到的场景。最终的代码如下:"""tdd-demo.py"""importunittestdefmultiply(a,b):result=0is_negative=Falseifa<0:is_negative=Truea=-awhilea>0:a=a-1result=result+bifis_negative:result=-resultreturnresultclassMultiplyTest(unittest.TestCase):deftest_multiply(self):self.assertEqual(multiply(2,3),6)self.assertEqual(乘法(3,5),15)deftest_multiply_with_larger_number(self):self.assertEqual(multiply(512,2),1024)self.assertEqual(multiply(10000,10000),100000000)deftest_multiply_with_negative_number(self):self.assertEqual(multiply(-5,10),-50)self.assertEqual(multiply(5,-10),-50)self.assertEqual(multiply(-2,-3),6)deftest_multiply_with_zero_number(self):self.assertEqual(乘法(0,5),0)self.assertEqual(乘法(2,0),0)self.assertEqual(乘法(0,0),0)if__name__=='__main__':unittest.main()ps:本文的目的是简单介绍TDD的例子和步骤,并使用multiply的功能来演示TDD在具体开发中的实践。具体代码实现肯定有不完善的地方。如有错误或遗漏,望读者指出。如果你看完文章对TDD有了不同的理解,或者想在接下来的开发中使用TDD,那么本文就完成了它的使命。最后,祝愿每个程序员都能“控制”代码而不是被代码“控制”,而“控制”代码最好的方式就是用测试代码“控制”。本文仅使用一个简单的乘法函数作为TDD的演示。读者在实际项目开发中可能会遇到很多“难写”的测试代码。同时,迫于项目期限的压力,想要快速实现功能并上线,打算放弃使用TDD的方式。但也希望大家能保持先写测试的习惯。几个月后,我相信你会感谢自己坚持使用TDD的决定。延伸阅读:如??果python是你的主要开发语言:Pythontest-drivendevelopment或者更一般的书:Test-drivendevelopment
