测试驱动开发(TDD)实例演示

下文将展现一个测试驱动开发(TDD)的实例,但愿能给想要开始实践TDD的朋友一个演示。本实例将采用python进行演示,若是您以前没用过python,也没必要担忧,这是一个很简洁易懂的语言。本人也会在下文实例中对出现的python语法进行解释。html

假设python语法中没有 乘法(*) 这个操做符,咱们要本身实现一个简单的乘法运算函数。python

开始以前咱们要记住TDD的核心,那就是:写功能代码以前先写测试,用测试去“驱动”功能实现。换句话说就是“只有在测试失败的时候才能添加或修改功能代码”。具体步骤以下:程序员

  1. 添加测试。
  2. 执行测试(测试失败)。
  3. 实现功能代码。
  4. 执行测试并经过测试(若测试失败,回到第3步)。
  5. 回到第一步。

步骤很简单,3~4步能够重复执行n遍直到测试经过。编程

但愿咱们能够一块儿实现这个demo,这样你就能和我一块儿体验到TDD的乐趣。框架

  • 注意:请确保先安装python,unix系统默认会安装python

执行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()
  • unittest是python用于单元测试的标准库,它提供更人性化的测试结果展现,同时也提供不少测试方法和钩子。本文只用到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 != -50multiply(-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

成功了!!心思细腻的小朋友可能会发现若是传参ab0的时候会怎么样?的确,这样的边界状况没有考虑到,这也是编程让大部分人以为头疼的地方。咱们添加测试看看

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 = 0a = 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的决定。

扩展阅读:

相关文章
相关标签/搜索