一、什麼是unittest
unittest是Python單元測試框架,類似於JUnit框架。
unittest中有4個重要的概念:test fixture, test case, test suite, test runner
Testcase:
一個TestCase的實例就是一個測試用例。什麼是測試用例呢?就是一個完整的測試流程,包括測試前準備環境的搭建(setUp),執行測試代碼 (run),以及測試後環境的還原(tearDown)。元測試(unit test)的本質也就在這裏,一個測試用例是一個完整的測試單元,通過運行這個測試單元,可以對某一個問題進行驗證。
Test suite:
多個測試用例集合在一起,就是TestSuite,而且TestSuite也可以嵌套TestSuite。
Test runner:
是來執行測試用例的,其中的run(test)會執行TestSuite/TestCase中的run(result)方法。
TestLoader:
是用來加載TestCase到TestSuite中的,其中有幾個loadTestsFrom__()方法,就是從各個地方尋找TestCase,創建它們的實例,然後add到TestSuite中,再返回一個TestSuite實例。
Test fixture:
對一個測試用例環境的搭建和銷燬,是一個fixture,通過覆蓋 TestCase的setUp()和tearDown()方法來實現。這個有什麼用呢?比如說在這個測試用例中需要訪問數據庫,那麼可以在setUp() 中建立數據庫連接以及進行一些初始化,在tearDown()中清除在數據庫中產生的數據,然後關閉連接。注意tearDown的過程很重要,要爲以後的 TestCase留下一個乾淨的環境。關於fixture,還有一個專門的庫函數叫做fixtures,功能更加強大。
使用unittest編寫python的單元測試代碼,包括如下幾個步驟:
1、編寫一個python類,繼承 unittest模塊中的TestCase類,這就是一個測試類
2、在上面編寫的測試類中定義測試方法(這個就是指的測試用例),每個方法的方法名要求以 test 打頭,沒有額外的參數。 在該測試方法中 調用被測試代碼,校驗測試結果,TestCase類中提供了很多標準的校驗方法,如 最常見的assertEqual。
3、執行 unittest.main() ,該函數會負責運行測試,它會實例化所有TestCase的子類,並運行其中所有以test打頭的方法。
二、簡單用法
unittest是python自帶的一個單元測試框架,類似於java的junit,基本結構是類似的。基本用法如下:
1.用import unittest導入unittest模塊
2.定義一個繼承自unittest.TestCase的測試用例類,如class xxx(unittest.TestCase):
3.定義setUp和tearDown,這兩個方法與junit相同,即如果定義了則會在每個測試case執行前先執行setUp方法,執行完畢後執行tearDown方法。
4.定義測試用例,名字以test開頭,unittest會自動將test開頭的方法放入測試用例集中。
5.一個測試用例應該只測試一個方面,測試目的和測試內容應很明確。主要是調用assertEqual、assertRaises等斷言方法判斷程序執行結果和預期值是否相符。
6.調用unittest.main()啓動測試
7.如果測試未通過,則會顯示e,並給出具體的錯誤(此處爲程序問題導致)。如果測試失敗則顯示爲f,測試通過爲.,如有多個testcase,則結果依次顯示。
三、unittest模塊的常用方法
assertEqual(a, b) a == b
assertNotEqual(a, b) a != b
assertTrue(x) bool(x) is True
assertFalse(x) bool(x) is False
assertIs(a, b) a is b
assertIsNot(a, b) a is not b
assertIsNone(x) x is None
assertIsNotNone(x) x is not None
assertIn(a, b) a in b
assertNotIn(a, b) a not in b
assertIsInstance(a, b) isinstance(a, b)
assertNotIsInstance(a, b) not isinstance(a, b)
簡單案例:
demo.py文件內容如下:
#!/usr/bin/python
# -*- coding: utf-8 -*-
def add(a, b):
return a+b
def minus(a, b):
return a-b
test_demo_class.py 文件內容:
#!/usr/bin/python
# -*- coding: utf-8 -*-
import unittest
from .demo import add,minus
class TestDemo(unittest.TestCase):
"""Test mathfuc.py"""
@classmethod
def setUpClass(cls):
print ("this setupclass() method only called once.\n")
@classmethod
def tearDownClass(cls):
print ("this teardownclass() method only called once too.\n")
def setUp(self):
print ("do something before test : prepare environment.\n")
def tearDown(self):
print ("do something after test : clean up.\n")
def test_add(self):
"""Test method add(a, b)"""
self.assertEqual(3, add(1, 2))
self.assertNotEqual(4, add(2, 2))
def test_minus(self):
"""Test method minus(a, b)"""
self.assertEqual(1, minus(3, 2))
self.assertNotEqual(1, minus(3, 2),"hahahahah")
#下註解表示該用例未寫好,先不測
@unittest.skip("do't run as not ready")
def test_minus_with_skip(self):
"""Test method minus(a, b)"""
self.assertEqual(1, minus(3, 2))
self.assertNotEqual(1, minus(3, 2))
if __name__ == '__main__':
# verbosity=*:默認是1;設爲0,則不輸出每一個用例的執行結果;2-輸出詳細的執行結果
unittest.main()
執行結果:
Testing started at 17:54 ...
/Users/wh/Documents/0003-項目管理/024-smart-iot/venv/bin/python /Applications/PyCharm.app/Contents/plugins/python/helpers/pycharm/_jb_unittest_runner.py --target test.test_demo_class.TestDemo
Launching unittests with arguments python -m unittest test.test_demo_class.TestDemo in /Users/wh/Documents/0003-項目管理/024-smart-iot/git@gitlab.raysdata-lab.com:smart-iot/thingsboard-gateway-python
this setupclass() method only called once.
do something before test : prepare environment.
do something after test : clean up.
Failure
Traceback (most recent call last):
File "/Users/wh/.pyenv/versions/3.7.5/lib/python3.7/unittest/case.py", line 59, in testPartExecutor
yield
File "/Users/wh/.pyenv/versions/3.7.5/lib/python3.7/unittest/case.py", line 628, in run
testMethod()
File "/Users/wh/Documents/0003-項目管理/024-smart-iot/[email protected]:smart-iot/thingsboard-gateway-python/test/test_demo_class.py", line 29, in test_add
self.assertNotEqual(4, add(2, 2))
File "/Users/wh/.pyenv/versions/3.7.5/lib/python3.7/unittest/case.py", line 861, in assertNotEqual
raise self.failureException(msg)
AssertionError: 4 == 4
do something before test : prepare environment.
do something after test : clean up.
Failure
Traceback (most recent call last):
File "/Users/wh/.pyenv/versions/3.7.5/lib/python3.7/unittest/case.py", line 59, in testPartExecutor
yield
File "/Users/wh/.pyenv/versions/3.7.5/lib/python3.7/unittest/case.py", line 628, in run
testMethod()
File "/Users/wh/Documents/0003-項目管理/024-smart-iot/[email protected]:smart-iot/thingsboard-gateway-python/test/test_demo_class.py", line 34, in test_minus
self.assertNotEqual(1, minus(3, 2),"hahahahah")
File "/Users/wh/.pyenv/versions/3.7.5/lib/python3.7/unittest/case.py", line 861, in assertNotEqual
raise self.failureException(msg)
AssertionError: 1 == 1 : hahahahah
Skipped: do't run as not ready
Assertion failed
Assertion failed
Ran 3 tests in 0.005s
this teardownclass() method only called once too.
FAILED (failures=2, skipped=1)
Process finished with exit code 1
Assertion failed
Assertion failed
類TestDemo繼承自unittest.TestCase,在類中定義了3個testcase。
unittest.main()調用Testloader加載這3個testcase到Testsuite,再用Testrunner運行testsuite,生成Testresult。
從output中可以看出,執行每個testcase之前都會先setUp()初始化,執行完畢之後tearDown()清理環境。
四、unittest主要常用函數
官方用例
python的unittest模塊提供了一個測試框架,只要我們寫一個繼承unittest.TestCase的類,類中用setUp做初始化,用tearDown做清理。
主要用到的函數有:
failedinfo表示不成立打印信息failedinfo,爲可選參數
self.fail([msg])會無條件的導致測試失敗,不推薦使用。
self.assertEqual(value1, value2, failedinfo) # 斷言value1 == value2
self.assertTrue(表達式, failedinfo) # 斷言value爲真
self.assertFalse(表達式, failedinfo) # 斷言value爲假
斷言肯定發生異常,如果沒發生異常,則爲測試失敗。
參數1爲異常,參數二爲拋出異常的調用對象,剩餘參數爲傳遞給可調用對象的參數。
self.assertRaises(ValueError, self.widget.resize, -1, -1)
調用時機的加self,如self.assertEqual(self.seq, range(10)),self.assertTrue(value > 100)
五、unittest網易郵箱登錄案例
1.打開網易郵箱,寫一個簡單的登錄;
2.判斷title完全等於期望結果;
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author : Ailie
# @File : mailLogin.py
# @Software: PyCharmimport timeimport unittestfrom selenium
import webdriverfrom selenium.webdriver.support
import expected_conditions as ECclass mailLogin(unittest.TestCase):
def setUp(self):
url = 'https://mail.yeah.net/'
self.browser = webdriver.Firefox()
self.browser.get(url)
time.sleep(5) def test_login_01(self):
'''
用戶名、密碼爲空
'''
self.browser.switch_to.frame("x-URS-iframe")
self.browser.find_element_by_name('email').send_keys('')
self.browser.find_element_by_name('password').send_keys('')
self.browser.find_element_by_id('dologin').click()
self.browser.switch_to.default_content()
time.sleep(3)
name = self.browser.find_element_by_id('spnUid') if name == '[email protected]':
print('登錄成功') else:
print('登陸失敗') def test_login_02(self):
'''
用戶名正確、密碼爲錯誤
'''
self.browser.switch_to.frame("x-URS-iframe")
self.browser.find_element_by_name('email').send_keys('sanzang520')
self.browser.find_element_by_name('password').send_keys('xxx')
self.browser.find_element_by_id('dologin').click()
self.browser.switch_to.default_content()
time.sleep(3)
name = self.browser.find_element_by_id('spnUid') if name == '[email protected]':
print('登錄成功') else:
print('登陸失敗') def test_login_03(self):
'''
用戶名、密碼正確
'''
self.browser.switch_to.frame("x-URS-iframe")
self.browser.find_element_by_name('email').send_keys('sanzang520')
self.browser.find_element_by_name('password').send_keys('xxx')
self.browser.find_element_by_id('dologin').click()
self.browser.switch_to.default_content()
time.sleep(3)
name = self.browser.find_element_by_id('spnUid') if name == '[email protected]':
print('登錄成功') else:
print('登陸失敗') def tearDown(self):
self.browser.quit()if __name__ == "__main__":
unittest.main()