單元測試
測試可以保證你的代碼在一系列給定條件下正常工作
測試允許人們確保對代碼的改動不會破壞現(xiàn)有的功能
測試迫使人們在不尋常條件的情況下思考代碼,這可能會揭示出邏輯錯誤
良好的測試要求模塊化和敬,解耦代碼凹炸,這是一個良好的系統(tǒng)設(shè)計的標(biāo)志
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os, sys
import time, datetime
import unittest
from unittest import TestCase
class TestSequenece(TestCase):
????def setUp(self):
????????self.lst = range(10)
????????print "setUp starting ..."
????def test_eq(self):
????????print "test_eq starting..."
????????self.assertEqual(self.lst, range(10))
????def test_in(self):
????????print "test_in starting..."
????????self.assertIn(1, self.lst)
????????self.assertNotIn(10, self.lst)
????def test_instance(self):
????????print "test_instance starting..."
????????self.assertIsInstance(self.lst, list)
????def tearDown(self):
????????print "tearDown starting..."
if __name__ == '__main__':
????unittest.main()
然后我們看一下執(zhí)行結(jié)果再分析:
setUp starting ...
test_eq starting...
tearDown starting...
.setUp starting ...
test_in starting...
tearDown starting...
.setUp starting ...
test_instance starting...
tearDown starting...
.
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
共運行三個測試, 每次測試成功通過都會輸出一個.號
TestCase直譯就是測試用例, 一個測試用例可以包含多個測試
test_xxxx就是測試項, 根據(jù)實際的功能代碼邏輯來編寫對應(yīng)的測試項, 運行時會自動查找所有以test開發(fā)的成員函數(shù)
assertXXXX斷言語句, 用來判斷測試結(jié)果是否符合測試預(yù)期結(jié)果.
setUp是執(zhí)行每個測試項前的準(zhǔn)備工作, 比如:可以做一些初始化工作
tearDown是執(zhí)行在每個測試項后的收尾工作,銷毀測試過程中產(chǎn)生的垃圾, 恢復(fù)現(xiàn)場等
Mock測試是什么鬼? 我們常常遇到這樣一種場景, 我們測試一些函數(shù), 而這些函數(shù)內(nèi)部調(diào)用另外帶有副作用的操作, 這可能導(dǎo)致我們在測試過程中對數(shù)據(jù)造成未知的副作用, 而這并不是我們希望在測試中看到的.
Mock測試可以替換到指定的Python對象或者方法, 并自定義指定對象或者方法的返回值, 從來模擬對象或者方法, 消除副作用.
Mock在Python3.3時加入到標(biāo)準(zhǔn)庫中, 2.X版本可以通過pip安裝
$ pip install mock
首先任意寫一個函數(shù)
# -*- coding: utf-8 -*-
#!/usr/bin/env python
import os, sys, time
def foo():
????lst = [1]
????lst = give_me_five(lst)
????return lst
def give_me_five(lst):
????return lst * 5
我們希望通過單元測試來測試這個函數(shù)的邏輯正確性.
# -*- coding: utf-8 -*-
#!/usr/bin/env python
import os, sys, time
# ?sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import unittest
from unittest import TestCase
import mock
import module
class Foo(object):
????pass
class TestMock(TestCase):
????# 1
????def test_method(self):
????????obj = Foo()
????????obj.method = mock.MagicMock(return_value=3)
????????print obj.method
????????self.assertEqual(obj.method(4), 3)
????# 2
????@mock.patch('module.foo')
????def test_decorator(self, foo):
????????# ?res = module.foo()
????????foo.return_value = [1, 2, 3]
????????self.assertEqual(foo(), [1, 2, 3])
????# 3
????def test_with(self):
????????with mock.patch('module.give_me_five') as give_me_five:
????????????give_me_five.return_value = "I'm Mock"
????????????self.assertEqual(module.foo(), "I'm Mock")
????# 4
????def test_module(self):
????????module.give_me_five = mock.Mock(return_value=[1] * 5)
????????module.give_me_five([1]) ?# 此時已經(jīng)變成了一個Mock對象, 并嘗試調(diào)用
????????module.give_me_five.assert_called_with([1]) ?# 對mock的參數(shù)進(jìn)行斷言
????????self.assertEqual(module.foo(), [1] * 5)
if __name__ == '__main__':
????unittest.main()
我們首先集成TestCase創(chuàng)建了一個單元測試
# 1位置, 我們通過mock提供的函數(shù)給obj的method方法設(shè)置返回值(可以看到類中并不包含method方法). 最后通過斷言來判斷返回值等于我們通過MagicMock設(shè)置的返回值
# 2位置, 我們通過mock提供的裝飾器,?patch()可以作為函數(shù)做裝飾, 類裝飾器, 上下文管理器?將module中的foo函數(shù)給mock掉,?并且并mock的函數(shù)生成的Mock對象作為類成員函數(shù)參數(shù)傳入, 指定了foo函數(shù)的返回值, 并通過了斷言測試
# 3位置, 將patch()作為一個上下文管理, 關(guān)于上下文管理器可以看我另一篇文章Python奇技淫巧, 用法和作為裝飾器基本類似
# 4位置, 我們調(diào)用module.foo函數(shù), 而我們并不關(guān)系foo()調(diào)用了那些函數(shù), 我只關(guān)心在成功調(diào)用module.give_me_five后, foo函數(shù)的邏輯正確性. 所以此次我們通過Mock函數(shù)給module.give_me_five指定我們希望的返回值. 這樣就能獨立的測試module.foo的邏輯
mock的主要思想: 通過mock對象對某些函數(shù)進(jìn)行替換, 對在測試上下文中, 這些被mock的函數(shù)被重定向到指定的mock對象
mock還有一些更高級的應(yīng)用
MagicMock是Mock的子類, 并且包含一些如__str__一樣的黑魔法函數(shù), 使用MagicMock甚至可以mock掉黑魔法函數(shù)
通過patch.object可以mock掉類中指定的成員函數(shù)
通過patch.dict可以將對象mock為字典
通過patch中的start和stop方法可以控制mock的生效范圍, 更加靈活的運行mock測試