測試驅動開發(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的決定。

擴展閱讀:

相關文章
相關標籤/搜索