下文將展現一個測試驅動開發(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的決定。
擴展閱讀: