Week12-unittest單元測試

week12html

1 核心工做原理python

unittest中最核心的四個概念是:test case, test suite, test runner, test fixture。數據庫

  • TestCase(測試用例): 全部測試用例的基類,它是軟件測試中最基本的組成單元。一個test case就是一個測試用例,是一個完整的測試流程,包括測試前環境的搭建(setUp),執行測試代碼(run),以及測試後環境的還原(tearDown)。測試用例是一個完整的測試單元,能夠對某一問題進行驗證。
  • TestSuite(測試套件):多個測試用例TestCase集合就是TestSuite,TestSuite也能夠嵌套TestSuite。
  • TestLoder:是用來加載 TestCase到TestSuite中,其中有幾個loadTestsFrom_()方法,就是從各個地方尋找TestCase,建立他們的實例,而後add到TestSuite中,再返回一個TestSuite實例。
  • TextTestRunner:是來執行測試用例的,其中的run(test)會執行TestSuite/TestCase中的run(result)方法。
  • TextTestResult:測試結果會保存到TextTestResult實例中,包括運行了多少用例,成功與失敗多少等信息。
  • TestFixture(測試夾具):又叫測試腳手,測試代碼的運行環境,指測試準備前和執行後要作的工做,包括setUp和tearDown方法。

其餘與unittest相似的單元測試庫:nose/pytest框架

命令行less

從命令行中能夠運行單元測試的模塊,類,甚至單獨的測試方法 。ide

python -m unittest test_module1 test_module2 #同時測試多個module
python -m unittest test_module.TestClass
python -m unittest test_module.TestClass.test_method

顯示更詳細的測試結果的說明使用 -v flag:單元測試

python -m unittest -v test_module

查看全部的命令行選項使用命令:測試

python -m unittest -h

2 測試流程ui

  1. 寫好TestCase:一個class繼承unittest.TestCase,就是一個測試用例,其中有多個以test開頭的方法,那麼每個這樣的,在load的時候會生成一個TestCase實例。若是一個class中有四個test開頭的方法,最後load到suite中時則有四個測試用例;(執行順序按照test方法的ASCII碼順序)
  2. 由TestLoder加載TestCase到TestSuite;
  3. 而後由TextTestRunner來運行TestSuite,運行的結果保存在TextTestResult中。

說明:this

a:經過命令行或者unittest.main()執行時,main會調用TextTestRunner中的run來執行,或者能夠直接經過TextTestRunner來執行用例

b:Runner執行時,默認將結果輸出到控制檯,咱們能夠設置其輸出到文件,在文件中查看結果,也能夠經過HTMLTestRunner將結果輸出到HTML

3 unittest實例

3.1 準備待測方法

mathfunc.py

def add(a, b):
    return a+b

def minus(a, b):
    return a-b

def multi(a, b):
    return a*b

def divide(a, b):
    return a/b

3.2 爲以上方法寫測試用例

test_mathfunc.py

import unittest
from mathfunc import *

class TestMathFunc(unittest.TestCase):
    """Test mathfunc.py"""

    def test_add(self):
        """Test method add(a, b)"""
        self.assertEqual(3, add(1, 2))
        self.assertNotEqual(3, add(2, 2))

    def test_minus(self):
        """Test method minus(a, b)"""
        self.assertEqual(1, minus(3, 2))

    def test_multi(self):
        """Test method multi(a, b)"""
        self.assertEqual(6, multi(2, 3))

    def test_divide(self):
        """Test method divide(a, b)"""
        self.assertEqual(2, divide(6, 3))
        self.assertEqual(2, divide(5, 2))

if __name__ == '__main__':
    unittest.main()

執行結果:

這裏有個插曲如何從unittest run變成普通的run?

若是當前是unittest in xxx.py的時候,菜單欄裏選Run,而後選Edit Configurations,而後會發現有Python和Python tests兩個部分,把你要運行的文件配置從Python tests刪掉加在Python裏就能夠了。這是由於你最開始直接run的時候沒選,因此默認按照單元測試run的,這個保存到本地配置裏了,因此你下次每次運行都會按照單元測試跑。

.F..
======================================================================
FAIL: test_divide (__main__.TestMathFunc)
Test method divide(a, b)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:/Users/zhouxy/PycharmProjects/untitled/utest/test_mathfunc.py", line 25, in test_divide
self.assertEqual(2, divide(5, 2))
AssertionError: 2 != 2.5

----------------------------------------------------------------------
Ran 4 tests in 0.004s

FAILED (failures=1)

這就是一個簡單的測試,有幾點須要說明的:

  • 在第一行給出了每個用例執行的結果的標識,成功是 .,失敗是 F,出錯是 E,跳過是 S。從上面也能夠看出,測試的執行跟方法的順序沒有關係,test_divide寫在了第4個,可是倒是第2個執行的。
  • 每一個測試方法均以 test 開頭,不然是不被unittest識別的。
  • 在unittest.main()中加 verbosity 參數能夠控制輸出的錯誤報告的詳細程度,默認是 1,若是設爲 0,則不輸出每一用例的執行結果,即沒有上面的結果中的第1行;若是設爲 2,則輸出詳細的執行結果。
if __name__ == '__main__':
    unittest.main(verbosity=2)
test_add (__main__.TestMathFunc)
Test method add(a, b) ... ok
test_divide (__main__.TestMathFunc)
Test method divide(a, b) ... FAIL
test_minus (__main__.TestMathFunc)
Test method minus(a, b) ... ok
test_multi (__main__.TestMathFunc)
Test method multi(a, b) ... ok

======================================================================
FAIL: test_divide (__main__.TestMathFunc)
Test method divide(a, b)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:/Users/zhouxy/PycharmProjects/untitled/utest/test_mathfunc.py", line 25, in test_divide
    self.assertEqual(2, divide(5, 2))
AssertionError: 2 != 2.5

----------------------------------------------------------------------
Ran 4 tests in 0.001s

FAILED (failures=1)

4 組織TestSuite

        a:肯定測試用例的順序,哪一個先執行哪一個後執行?

        b:若是測試文件有多個,怎麼進行組織?

TestLoder加載TestCase有幾種方法,在文件夾中咱們再新建一個文件,test_suite.py:

import unittest
from test_mathfunc import TestMathFunc
#構建測試集
suite = unittest.TestSuite()
tests = [TestMathFunc('test_add'),TestMathFunc('test_minus'),TestMathFunc('test_divide')]
suite.addTests(tests) 

runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)

執行結果:

test_add (test_mathfunc.TestMathFunc)
Test method add(a, b) ... ok
test_minus (test_mathfunc.TestMathFunc)
Test method minus(a, b) ... ok
test_divide (test_mathfunc.TestMathFunc)
Test method divide(a, b) ... FAIL

======================================================================
FAIL: test_divide (test_mathfunc.TestMathFunc)
Test method divide(a, b)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Users\zhouxy\PycharmProjects\untitled\utest\test_mathfunc.py", line 25, in test_divide
    self.assertEqual(2, divide(5, 2))
AssertionError: 2 != 2.5

----------------------------------------------------------------------
Ran 3 tests in 0.002s

FAILED (failures=1)

上面用了TestSuite的 addTests() 方法,並直接傳入了TestCase列表,還有其餘方法:

# 直接用addTest方法添加單個TestCase
suite.addTest(TestMathFunc("test_multi"))

# 用addTests + TestLoader
# loadTestsFromName(),傳入'模塊名.TestCase名'
suite.addTests(unittest.TestLoader().loadTestsFromName('test_mathfunc.TestMathFunc'))
suite.addTests(unittest.TestLoader().loadTestsFromNames(['test_mathfunc.TestMathFunc']))  # loadTestsFromNames(),相似,傳入列表

# loadTestsFromTestCase(),傳入TestCase
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMathFunc))

TestLoader 用來從clases和modules建立test suites,一般也須要建立一個該類的實例,注意,用TestLoader的方法是沒法對case進行排序的(一個module執行的順序是根據測試用例的名稱),同時,suite中也能夠套suite。

discover加載測試用例

找到指定目錄下全部測試模塊,並可遞歸查到子目錄下的測試木塊,只有匹配到的文件名纔會被加載。若是啓動的不是頂層目錄,那麼頂層目錄必然單獨指定。

discover方法有三個參數:

 - case_dir:這個是待執行用例的目錄;

 - pattern:這個是匹配腳本名稱的規則,test*.py意思是匹配test開頭的全部腳本;

 - top_level_dir:這個是頂層目錄的名稱,通常默認等於None。

discover加載到的用例是一個list集合,須要從新寫入到一個list對象testcase裏,這樣就能夠用unittest裏面的TextTestRunner這裏類的run方法去執行

import unittest
import os

#測試用例路徑
test_dir = os.path.join(os.getcwd(),'_test')
discover = unittest.defaultTestLoader.discover(test_dir,pattern="test*.py",top_level_dir=None)

if __name__ == "__main__":
    runner = unittest.TextTestRunner()
    runner.run(discover)

5 將結果輸出到文件中

用例組織好了,但結果只能輸出到控制檯,這樣沒有辦法查看以前的執行記錄,咱們想將結果輸出到文件。

修改test_suite.py: 

import unittest
from test_mathfunc import TestMathFunc

if __name__ == '__main__':
    suite = unittest.TestSuite()
    tests = [TestMathFunc('test_add'),TestMathFunc('test_minus'),TestMathFunc('test_divide')]
    suite.addTests(tests)
    with open('UnittestTextReport.txt','a') as f:
        runner = unittest.TextTestRunner(stream=f,verbosity=2)
        runner.run(suite)

6 test fixture之setUp() tearDown()

上面整個測試基本跑了下來,但可能會遇到點特殊的狀況:若是個人測試須要在每次執行以前準備環境,或者在每次執行完以後須要進行一些清理怎麼辦?好比執行前須要鏈接數據庫,執行完成以後須要還原數據、斷開鏈接。總不能每一個測試方法中都添加準備環境、清理環境的代碼吧。

這就要涉及到咱們以前說過的test fixture了,修改test_mathfunc.py:

from mathfunc import *
import unittest

class TestMathFunc(unittest.TestCase):
    """Test mathfunc.py"""
    def setUp(self):
        print('do something before test.Prepare environment')

    def tearDown(self):
        print('do something after test.Clean up')

    def test_add(self):
        """Test method add(a, b)"""
        print('add')
        self.assertEqual(3, add(1, 2))
        self.assertNotEqual(3, add(2, 2))

    def test_minus(self):
        """Test method minus(a, b)"""
        print('minus')
        self.assertEqual(1, minus(3, 2))

    def test_multi(self):
        """Test method multi(a, b)"""
        self.assertEqual(6, multi(2, 3))

    def test_divide(self):
        """Test method divide(a, b)"""
        print('divide')
        self.assertEqual(2, divide(6, 3))
        self.assertEqual(2, divide(5, 2))

if __name__ == '__main__':
    unittest.main(verbosity=2)

添加 setUp() 和 tearDown() 兩個方法(實際上是重寫了TestCase的這兩個方法),這兩個方法在每一個測試方法執行前以及執行後執行一次,setUp用來爲測試準備環境,tearDown用來清理環境,已備以後的測試。

執行結果:

do something before test.Prepare environment
add
do something after test.Clean up
do something before test.Prepare environment
minus
do something after test.Clean up
do something before test.Prepare environment
divide
do something after test.Clean up

若是想要在全部case執行以前準備一次環境,並在全部case執行結束以後再清理環境,咱們能夠用setUpClass()tearDownClass():

class TestMathFunc(unittest.TestCase):
    """Test mathfunc.py"""

    @classmethod
    def setUpClass(cls):
        print('This setUpClass() method only called once.')

    @classmethod
    def tearDownClass(cls):
        print('This tearDownClass() method only called once too.')

執行結果:

This setUpClass() method only called once.
add
minus
divide
This tearDownClass() method only called once too.

7 跳過某個case

unittest提供了幾種方法:

7.1 skip裝飾器

    @unittest.skip("I don't want to run this case")
    def test_divide(self):
        """Test method divide(a, b)"""
        print('divide')
        self.assertEqual(2, divide(6, 3))
        self.assertEqual(2, divide(5, 2))

執行結果:

test_add (test_mathfunc.TestMathFunc)
Test method add(a, b) ... ok
test_minus (test_mathfunc.TestMathFunc)
Test method minus(a, b) ... ok
test_divide (test_mathfunc.TestMathFunc)
Test method divide(a, b) ... skipped "I don't want to run this case"

----------------------------------------------------------------------
Ran 3 tests in 0.001s

OK (skipped=1)

divide()方法被skip了。

skip裝飾器一共有三個 unittest.skip(reason)unittest.skipIf(condition, reason)unittest.skipUnless(condition, reason),skip無條件跳過,skipIf當condition爲True時跳過,skipUnless當condition爲False時跳過。

7.2 TestCase.skipTest()方法

    def test_divide(self):
        """Test method divide(a, b)"""
        print('divide')
        self.assertEqual(2, divide(6, 3))
        self.skipTest('skip this exmple')
        self.assertEqual(2, divide(5, 2))

執行結果:

This setUpClass() method only called once.
add
minus
divide
This tearDownClass() method only called once too.

8 用HTMLTestRunner輸出HTML報告

HTMLTestRunner是一個第三方的unittest HTML報告庫,首先咱們下載HTMLTestRunner.py,並放到當前目錄下或者環境變量。

import unittest
from test_mathfunc import TestMathFunc
from HTMLTestRunner import HTMLTestRunner
import time

if __name__ == '__main__':
    suite = unittest.TestSuite()
    tests = [TestMathFunc('test_add'),TestMathFunc('test_minus'),TestMathFunc('test_divide')]
    suite.addTests(tests)
    now = time.strftime('%Y%m%d%H%M%S')
    with open(now+'HTMLReport.html','wb') as f:
        runner = HTMLTestRunner(stream=f,
                                         title='計算器測試',
                                         description = '測試報告',
                                         verbosity=2)
        runner.run(suite)

在同目錄下生產測試報告文件:20180718193134HTMLReport.html

9 斷言

 

總結

  1. unittest是Python自帶的單元測試框架,咱們能夠用其來做爲咱們自動化測試框架的用例組織執行框架。
  2. unittest的流程:寫好TestCase,而後由TestLoader加載TestCase到TestSuite,而後由TextTestRunner來運行TestSuite,運行的結果保存在TextTestResult中,咱們經過命令行或者unittest.main()執行時,main會調用TextTestRunner中的run來執行,或者咱們能夠直接經過TextTestRunner來執行用例。
  3. 一個class繼承unittest.TestCase便是一個TestCase,其中以 test 開頭的方法在load時被加載爲一個真正的TestCase。
  4. verbosity參數能夠控制執行結果的輸出,0 是簡單報告、1 是通常報告、2 是詳細報告。
  5. 能夠經過addTest和addTests向suite中添加case或suite,能夠用TestLoader的loadTestsFrom__()方法。
  6. 用 setUp()tearDown()setUpClass()以及 tearDownClass()能夠在用例執行前佈置環境,以及在用例執行後清理環境
  7. 咱們能夠經過skip,skipIf,skipUnless裝飾器跳過某個case,或者用TestCase.skipTest方法。
  8. 參數中加stream,能夠將報告輸出到文件:能夠用TextTestRunner輸出txt報告,以及能夠用HTMLTestRunner輸出html報告。
相關文章
相關標籤/搜索