專案架構:我們將test script統一放在tests這個資料夾中,tests是與src同級的目錄。(我使用的是Python36)
/proj
- test.py
- /src
- __init__.py
- calc.py
- employee.py
- /tests
- __init__.py
- test_calc.py
- test_employee.py
src/calc.py:這就是我們要測試的程式碼
def add(a, b):
return a+b
def substract(a, b):
return a-b
def multiply(a, b):
return a*b
def divide(a, b):
if b==0:
raise ValueError('Can not divide by zero!!')
return a/b
tests/test_calc.py:
import unittest
from src import calc
class TestCalc(unittest.TestCase):
def test_add(self):
self.assertEqual(calc.add(1, 2), 3)
self.assertEqual(calc.add(-1, 1), 0)
def test_divide(self):
self.assertEqual(calc.divide(4, 2), 2)
self.assertEqual(calc.divide(-2, 1), -2)
self.assertRaises(ValueError, calc.divide, 10, 0)
with self.assertRaises(ValueError):
calc.divide(10, 0)
* 函式命名一定要以test為開頭!unittest才會將這個函式當作一組測試。
* self.assertRaises(ValueError, calc.divide, 10, 0) 等同 with self.assertRaises(ValueError): calc.divide(10, 0)
執行unittest:
在/proj路徑下,執行 python -m unittest tests/test_calc.py
也可以下這樣的命令 python -m unittest discover tests,這個指令會運行tests資料夾內所有的unittest
(python -m unittest是指要load unittest module,後面接的是要給unittest的參數)
結果: "." 點的意思是成功,有兩個點,所以unit test是以TestCase裡的function為一單位。
我們修改test_calc.py故意寫一個錯誤,F代表有錯:
* 為什麼一定要在/proj路徑下指令?
若是進入/proj/tests下這樣的指令 python -m unittest test_calc.py,會發現test_calc.py找不到src這個module!
python -m unittest會以目前下指令的位置為top level的路徑,在test_calc.py中寫的是from src import calc,這是絕對路徑的寫法 ,絕對路徑是從top level的路徑出發,因此會若我們在/proj/tests下指令時會看/proj/tests下有無src module,在tests裡並沒有src所以才會報錯。
(相對路徑會使用到".","."意思是與現在檔案同一目錄、".."則指前一個目錄、"..."前兩個目錄以此類推)
那可不可以不要寫什麼 -m unittest呢?
我只想打 python test_calc.py 。
當然可以。
首先我們需要在test_cal.py加入if __name__ == '__main__',並運行unittest.main(),這個函式會運行main module裡的所有test case。
因為我們要執行的是這樣的命令 python test_calc.py,Python會以運行的檔案(__main__)位置做為top level路徑,能使用絕對路徑和相對路徑到達的只有在這個top level路徑下的東西。
要想執行test_calc.py能開到上一層目錄的東西,就要將PROJECT_DIR加到sys.path中,告訴Python要找module還可以找這裡,如此test_calc.py中的from src import calc才不會找不到module。
(Python找module會先找built-in module是否有該module,再來找sys.path這個list裡的目錄是否有該module,執行的檔案目錄會被加到sys.path中的第一順位)
import unittest
import sys
import os
TEST_DIR = os.path.dirname(os.path.abspath(__file__))
PROJECT_DIR = os.path.abspath(os.path.join(TEST_DIR, os.pardir))
sys.path.insert(0, PROJECT_DIR) ## "0"為最優先級
from src import calc
class TestCalc(unittest.TestCase):
def test_add(self):
self.assertEqual(calc.add(1, 2), 3)
self.assertEqual(calc.add(-1, 1), 0)
def test_divide(self):
self.assertEqual(calc.divide(4, 2), 1)
self.assertEqual(calc.divide(-2, 1), -2)
self.assertRaises(ValueError, calc.divide, 10, 0)
with self.assertRaises(ValueError):
calc.divide(10, 0)
if __name__ == '__main__':
unittest.main()
那我們也可以做一個script運行tests資料夾內的所有Unit Tests。
proj/test.py:
import unittest
loader = unittest.TestLoader()
start_dir = 'tests/' # path to test files
suite = loader.discover(start_dir)
runner = unittest.TextTestRunner()
runner.run(suite)
命令只要執行 python test.py 就可以了!
另外因為我們是執行 /proj 中的test.py,所以top level目錄是 /proj,那在我們的tests裡的那些test scripts就不需要將PROJECT_DIR加入到sys.path中,直接from src import 就可以了,因為只要在 /proj 下的都可以從 /proj出發以絕對路徑找到。
setUp() & tearDown():
setUp()與tearDown()能幫助我們初始化測試所需的物件與清除(在python不需要自己刪除物件就是了),其他用法像是在setUp()登入資料庫,在tearDown()登出。
直接看程式碼比較好了解。
src/employee.py:
class Employee():
raise_amt = 1.05
def __init__(self, name, pay):
self.name = name
self.pay = pay
@property
def email(self):
return '{}@email.com'.format(self.name)
def apply_raise(self):
self.pay = int(self.pay*self.raise_amt)
tests/test_employee.py:
import unittest
import sys
import os
TEST_DIR = os.path.dirname(os.path.abspath(__file__))
PROJECT_DIR = os.path.abspath(os.path.join(TEST_DIR, os.pardir))
sys.path.insert(0, PROJECT_DIR)
from src.employee import Employee
class TestEmployee(unittest.TestCase):
@classmethod
def setUpClass(cls):
print("setupClass")
@classmethod
def tearDownClass(cls):
print("teardownClass")
def setUp(self):
print("setUp")
self.emp1 = Employee('Tim', 50000)
self.emp2 = Employee('Andy', 60000)
def tearDown(self):
print("tearDown\n")
def test_email(self):
print("test_email")
self.assertEqual(self.emp1.email, 'Tim@email.com')
self.assertEqual(self.emp2.email, 'Andy@email.com')
def test_apply_raise(self):
print("test_apply_raise")
self.emp1.apply_raise()
self.emp2.apply_raise()
self.assertEqual(self.emp1.pay, 52500)
self.assertEqual(self.emp2.pay, 63000)
if __name__ == '__main__':
unittest.main()
執行unittest:
執行 python test_employee.py
結果:
參考資料:
1. Python Tutorial: Unit Testing Your Code with the unittest Module (youtube)
沒有留言:
張貼留言