單元測試框架Uinttest一文詳解

一談及unittest,你們都知道,unittest是Python中自帶的單元測試框架,它裏面封裝好了一些校驗返回的結果方法和一些用例執行前的初始化操做。unittest單元測試框架不只能夠適用於單元測試,還能夠適用web自動化測試用例的開發與執行,該測試框架可組織執行測試用例,而且提供了豐富的斷言方法,判斷測試用例是否經過,最終生成測試結果。css

在聊unittest時,須要先明白,最基礎的四個概念:TestCase,TestSuite,TestRunner,TestFixture,看以下靜態圖:html

unittest運行流程

先編寫好TestCase,而後由TestLoader加載TestCase到TestSuite,其次由TextTestRunner來運行TestSuite,運行的結果保存在TextTestResult中,咱們經過命令行或者unittest.main()執行時,main會調用TextTestRunner中的run來執行,或者咱們能夠直接經過TextTestRunner來執行用例。python

unittest模塊的各個屬性說明

unittest.TestCase:TestCase類,全部測試用例類繼承的基本類。git

class BaiduTest(unittest.TestCase):

unittest.main():使用它能夠方便的將一個單元測試模塊變爲可直接運行的測試腳本,main()方法使用TestLoader類來搜索全部包含在該模塊中以「test」命名開頭的測試方法,並自動執行他們。執行方法的默認順序是:根據ASCII碼的順序加載測試用例,數字與字母的順序爲:0-9,A-Z,a-z。因此以A開頭的測試用例方法會優先執行,以a開頭會後執行。github

unittest.TestSuite():unittest框架的TestSuite()類是用來建立測試套件的。web

unittest.TextTextRunner():unittest框架的TextTextRunner()類,經過該類下面的run()方法來運行suite所組裝的測試用例,入參爲suite測試套件。數據庫

unittest.defaultTestLoader(): defaultTestLoader()類,經過該類下面的discover()方法可自動更具測試目錄start_dir匹配查找測試用例文件(test*.py),並將查找到的測試用例組裝到測試套件,所以能夠直接經過run()方法執行discover。用法以下:python3.x

discover=unittest.defaultTestLoader.discover(test_dir, pattern='test_*.py')

unittest.skip():裝飾器,當運行用例時,有些用例可能不想執行等,可用裝飾器暫時屏蔽該條測試用例。一種常見的用法就是好比說想調試某一個測試用例,想先屏蔽其餘用例就能夠用裝飾器屏蔽。瀏覽器

@unittest.skip(reason): skip(reason)裝飾器:無條件跳過裝飾的測試,並說明跳過測試的緣由。app

@unittest.skipIf(reason):skipIf(condition,reason)裝飾器:條件爲真時,跳過裝飾的測試,並說明跳過測試的緣由。

@unittest.skipUnless(reason):skipUnless(condition,reason)裝飾器:條件爲假時,跳過裝飾的測試,並說明跳過測試的緣由。

@unittest.expectedFailure():expectedFailure()測試標記爲失敗。

setUp():setUp()方法用於每一個測試用例執行前的初始化工做。如測試用例中須要訪問數據庫,能夠在setUp中創建數據庫鏈接並進行初始化。如測試用例須要登陸web,能夠先實例化瀏覽器。

tearDown():tearDown()方法用於每一個測試用例執行以後的善後工做。如關閉數據庫鏈接、關閉瀏覽器。

setUpClass():setUpClass()方法用於全部測試用例前的設置工做。

tearDownClass():tearDownClass()方法用於全部測試用例執行後的清理工做。

addTest():addTest()方法是將測試用例添加到測試套件中。

run():run()方法是運行測試套件的測試用例,入參爲suite測試套件。

assert*():在執行測試用例的過程當中,最終用例是否執行經過,是經過判斷測試獲得的實際結果和預期結果是否相等決定的。

斷言方式以下:

assertEqual(a,b,[msg='測試失敗時打印的信息']):斷言a和b是否相等,相等則測試用例經過。

assertNotEqual(a,b,[msg='測試失敗時打印的信息']):斷言a和b是否相等,不相等則測試用例經過。

assertTrue(x,[msg='測試失敗時打印的信息']):斷言x是否True,是True則測試用例經過。

assertFalse(x,[msg='測試失敗時打印的信息']):斷言x是否False,是False則測試用例經過。

assertIs(a,b,[msg='測試失敗時打印的信息']):斷言a是不是b,是則測試用例經過。

assertNotIs(a,b,[msg='測試失敗時打印的信息']):斷言a是不是b,不是則測試用例經過。

assertIsNone(x,[msg='測試失敗時打印的信息']):斷言x是否None,是None則測試用例經過。

assertIsNotNone(x,[msg='測試失敗時打印的信息']):斷言x是否None,不是None則測試用例經過。

assertIn(a,b,[msg='測試失敗時打印的信息']):斷言a是否在b中,在b中則測試用例經過。

assertNotIn(a,b,[msg='測試失敗時打印的信息']):斷言a是否在b中,不在b中則測試用例經過。

assertIsInstance(a,b,[msg='測試失敗時打印的信息']):斷言a是是b的一個實例,是則測試用例經過。

assertNotIsInstance(a,b,[msg='測試失敗時打印的信息']):斷言a是是b的一個實例,不是則測試用例經過。

說了這麼多的屬性,接下來用一段代碼舉例:

unittest基本代碼示例:

import unittest
from unittestbasic1.unittest_operation import *

'''
@author: wenyihuqingjiu
@project: unittestdemo
@file: unittest_demo_basic.py
@time: 2019-09-26 23:38
@desc:
'''

class TestMathFunc(unittest.TestCase):

    # TestCase基類方法,全部case執行以前自動執行
    @classmethod
    def setUpClass(cls):
        print("*************這裏是全部測試用例前的準備工做*************")

    # TestCase基類方法,全部case執行以後自動執行
    @classmethod
    def tearDownClass(cls):
        print("*************這裏是全部測試用例後的清理工做*************")

    # TestCase基類方法,每次執行case前自動執行
    def setUp(self):
        print("-------------這裏是一個測試用例前的準備工做-------------")

    # TestCase基類方法,每次執行case後自動執行
    def tearDown(self):
        print("-------------這裏是一個測試用例後的清理工做-------------")

    @unittest.skip("我想臨時跳過這個測試用例.")
    def test_add(self):
        self.assertEqual(3, add(1, 2))
        self.assertNotEqual(3, add(2, 2))  # 測試業務方法add

    def test_minus(self):
        self.skipTest('跳過這個測試用例')
        self.assertEqual(1, minus(3, 2))  # 測試業務方法minus

    def test_multi(self):
        self.assertEqual(6, multi(2, 3))  # 測試業務方法multi

    def test_divide(self):
        self.assertEqual(2, divide(6, 3))  # 測試業務方法divide
        self.assertEqual(2.5, divide(5, 2))


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

基本函數以下:

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

運行代碼,結果以下所示:

 

理論加實踐,結合起來看,應該更容易明白些,切莫作紙上談兵。

添加用例運行方式

弄明白了unittest的基本屬性,接下來分享下添加用例運行的不一樣方式

方式一:使用addTests單個添加

用addTests方法單個添加用例,代碼示例以下:

import unittest
from unittestbasic2.unittest_operation2 import TestMathFunc

'''
@author: wenyihuqingjiu
@project: unittestdemo
@file: unittest_demo_suite1.py
@time: 2019-09-26 23:38
@desc:
'''

if __name__ == '__main__':
    suite = unittest.TestSuite()
    # 單個添加測試用例
    suite.addTest(TestMathFunc("test_multi"))
    suite.addTest(TestMathFunc("test_divide"))
    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(suite)

方式二:測試用例添加到用例集中

從方法一中看,單個添加用例比較麻煩,該方法能夠將用例添加到一個用例集中,再經過suite統一運行,代碼示例以下:

import unittest
from unittestbasic2.unittest_operation2 import TestMathFunc

'''
@author: wenyihuqingjiu
@project: unittestdemo
@file: unittest_demo_suite2.py
@time: 2019-09-26 23:38
@desc:
'''

if __name__ == '__main__':
    suite = unittest.TestSuite()
    # 將測試用例添加到一個用例集中
    tests = [TestMathFunc("test_add"), TestMathFunc("test_minus")]
    suite.addTests(tests)  # 將測試用例列表添加到測試組中
    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(suite)

方式三:TestLoader方法加載用例

使用TestLoader方法加載用例,就是沒法對case進行排序,執行順序是隨機的。實現方法有以下三種,代碼示例以下:

import unittest
from unittestbasic2.unittest_operation2 import TestMathFunc

'''
@author: wenyihuqingjiu
@project: unittestdemo
@file: unittest_demo_suite3.py
@time: 2019-09-26 23:44
@desc:
'''

if __name__ == '__main__':
    suite = unittest.TestSuite()
    # 第一種方法:傳入'模塊名.TestCase名'
    # 用addTests + TestLoader。不過用TestLoader的方法是沒法對case進行排序的
    # loadTestsFromName(),傳入'模塊名.TestCase名'
    #
    # suite.addTests(unittest.TestLoader().loadTestsFromName('unittest_operation2.TestMathFunc'))
    # suite.addTests(unittest.TestLoader().loadTestsFromName('unittest_operation1.TestMathFunc'))
    # 這裏還能夠把'模塊名.TestCase名'放到一個列表中
    #
    # suite.addTests(unittest.TestLoader().loadTestsFromNames(['unittest_operation2.TestMathFunc', 'unittest_operation1.TestMathFunc']))

    # 第二種方法:傳入TestCase 須要導入對應模塊
    suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMathFunc))
    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(suite)

方式四:經過discover()方法

看了前三種實現方式,都比較單一,實際運用中,case數量不只僅只有一兩個,一個一個去添加的話,那得多麻煩。故而引用discover()方法,匹配指定文件夾下以test開頭的測試用例,代碼示例以下:

import unittest

'''
@author: wenyihuqingjiu
@project: unittestdemo
@file: unittest_demo_suite4.py
@time: 2019-09-26 23:46
@desc:
'''

if __name__ == '__main__':
    path = './testcase'
    all_cases = unittest.defaultTestLoader.discover(path, 'test*.py')
    # 找到某個目錄下全部的以test開頭的Python文件裏面的測試用例
    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(all_cases)

如上所述,就是常見的用例執行方式了,不過,運用最多的仍是最後一種方式。

測試報告

在作測試時,天然是要生成測試報告的,一份好的測試報告,能夠很直觀的看出當前代碼構建的系統情況,也能夠幫助本身很快定位問題。

前段時間,在github上搜索到一份帶截圖的測試報告模板,因此引用使用了一番,總體很不錯,本身也略小有調整,將報告模板引用到代碼中便可使用。

說到測試報告,再使用以前的代碼,就實現不了,須要引用HTMLTestRunner。我通常使用html格式的報告,還有一種是xml格式的,使用jenkins構建代碼的話,那就須要生成xml格式的測試報告了。

html格式測試報告

先來看如何生成html格式的測試報告,代碼示例以下:

import unittest
from utils.HTMLTestRunner_cn import HTMLTestRunner
import time

'''
@author: wenyihuqingjiu
@project: unittestdemo
@file: unittest_demo_suite5.py
@time: 2019-09-26 23:58
@desc:
'''

if __name__ == '__main__':
    path = './testcase'
    report_file = '../'
    # 找到某個目錄下全部的以test開頭的Python文件裏面的測試用例
    all_cases = unittest.defaultTestLoader.discover(path, 'test*.py')

    report_time = time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime(time.time()))
    report_file_path = report_file + '/report/' + report_time + '-result.html'
    file_result = open(report_file_path, 'wb')
    runner = HTMLTestRunner(stream=file_result, title="簡單運算demo", description="溫一壺清酒", verbosity=2, retry=2,
                           save_last_try=True)
    runner.run(all_cases)

運行後,查看測試報告,以下所示:

以本身的產品作了個登陸測試,生成的測試報告更全面,代碼示例以下:

from selenium import webdriver
import unittest
import time
from utils.HTMLTestRunner_cn import HTMLTestRunner

'''
@author: wenyihuqingjiu
@project: unittestdemo
@file: unittestlogin.py
@time: 2019-09-22 11:38
@desc:
'''


class case_01(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.driver = webdriver.Chrome()
        cls.driver.maximize_window()

    @classmethod
    def tearDownClass(cls):
        cls.driver.quit()

    def add_img(self):
        self.imgs.append(self.driver.get_screenshot_as_base64())
        return True

    def setUp(self):
        # 在是python3.x 中,若是在這裏初始化driver ,由於3.x版本 unittestbasic1 運行機制不一樣,會致使用力失敗時截圖失敗
        self.driver.get("")
        self.driver.implicitly_wait(30)
        self.imgs = []
        self.addCleanup(self.cleanup)

    def cleanup(self):
        pass

    def test_case1(self):
        """ 正常登陸"""
        self.driver.find_element_by_xpath(
            "//*[@id='app']/div/div[1]/div[1]/div[2]/div[1]/div/div[1]/div/div/div/div/div[3]").click()
        self.add_img()
        self.driver.find_element_by_css_selector("input[type='text']").send_keys("")
        time.sleep(1)
        self.add_img()
        self.driver.find_element_by_css_selector("input[type='password']").send_keys("")
        time.sleep(1)
        self.add_img()
        self.driver.find_element_by_css_selector("button[type='button']").click()
        time.sleep(3)
        self.add_img()
        user_name = self.driver.find_element_by_css_selector(".user-name").text
        self.assertEqual("一壺清酒stage1111", user_name, msg="登陸失敗!!")
        self.driver.find_element_by_css_selector(".user-name").click()
        time.sleep(3)
        self.driver.find_element_by_xpath(
            "//*[@id='app']/div/div[2]/div[1]/div[2]/div[5]/div[3]/ul/li[2]/span/i").click()
        print("退出登陸")
        time.sleep(5)

    def test_case2(self):
        """ 帳號不存在"""
        self.driver.find_element_by_xpath(
            "//*[@id='app']/div/div[1]/div[1]/div[2]/div[1]/div/div[1]/div/div/div/div/div[3]").click()
        self.driver.find_element_by_css_selector("input[type='text']").send_keys("")
        time.sleep(1)
        self.driver.find_element_by_css_selector("input[type='password']").send_keys("")
        time.sleep(1)
        self.driver.find_element_by_css_selector("button[type='button']").click()
        time.sleep(1)
        self.add_img()
        print("登陸失敗!")

    def test_case3(self):
        """ 密碼錯誤"""
        self.driver.find_element_by_xpath(
            "//*[@id='app']/div/div[1]/div[1]/div[2]/div[1]/div/div[1]/div/div/div/div/div[3]").click()
        self.driver.find_element_by_css_selector("input[type='text']").send_keys("")
        time.sleep(1)
        self.driver.find_element_by_css_selector("input[type='password']").send_keys("")
        time.sleep(1)
        self.driver.find_element_by_css_selector("button[type='button']").click()
        time.sleep(1)
        self.add_img()
        print("登陸失敗!")

    @unittest.skip("臨時跳過這個測試用例")
    def test_case4(self):
        """ 跳過該用例"""
        self.driver.find_element_by_xpath(
            "//*[@id='app']/div/div[1]/div[1]/div[2]/div[1]/div/div[1]/div/div/div/div/div[3]").click()
        self.driver.find_element_by_css_selector("input[type='text']").text()
        time.sleep(1)
        self.driver.find_element_by_css_selector("input[type='password']").text()
        time.sleep(1)
        self.driver.find_element_by_css_selector("button[type='button']").click()
        time.sleep(3)

    @unittest.skipIf(3 > 2, "判斷爲真,此用例不執行")
    def test_case5(self):
        """ skipIf判斷,爲真則不執行"""
        self.driver.find_element_by_xpath(
            "//*[@id='app']/div/div[1]/div[1]/div[2]/div[1]/div/div[1]/div/div/div/div/div[3]").click()
        self.driver.find_element_by_css_selector("input[type='text']").text()
        time.sleep(1)
        self.driver.find_element_by_css_selector("input[type='password']").text()
        time.sleep(1)
        self.driver.find_element_by_css_selector("button[type='button']").click()
        time.sleep(3)

    @unittest.skipUnless(3 < 2, "判斷爲假,此用例不執行")
    def test_case6(self):
        """ skipIf判斷,爲假則不執行"""
        self.driver.find_element_by_xpath(
            "//*[@id='app']/div/div[1]/div[1]/div[2]/div[1]/div/div[1]/div/div/div/div/div[3]").click()
        self.driver.find_element_by_css_selector("input[type='text']").text()
        time.sleep(1)
        self.driver.find_element_by_css_selector("input[type='password']").text()
        time.sleep(1)
        self.driver.find_element_by_css_selector("button[type='button']").click()
        time.sleep(3)

    def test_case7(self):
        """ 斷言失敗"""
        self.driver.find_element_by_xpath(
            "//*[@id='app']/div/div[1]/div[1]/div[2]/div[1]/div/div[1]/div/div/div/div/div[3]").click()
        self.driver.find_element_by_css_selector("input[type='text']").send_keys("")
        time.sleep(1)
        self.driver.find_element_by_css_selector("input[type='password']").send_keys("")
        time.sleep(1)
        self.driver.find_element_by_css_selector("button[type='button']").click()
        time.sleep(3)
        user_name = self.driver.find_element_by_css_selector(".user-name").text
        self.assertEqual("一壺清酒stage", user_name, msg="登陸失敗!!")
        self.driver.find_element_by_css_selector(".user-name").click()
        time.sleep(3)
        self.driver.find_element_by_xpath(
            "//*[@id='app']/div/div[2]/div[1]/div[2]/div[5]/div[3]/ul/li[2]/span/i").click()
        print("退出登陸")


if __name__ == "__main__":
    suites = unittest.TestSuite()
    suites.addTest(case_01("test_case1"))
    suites.addTest(case_01("test_case2"))
    suites.addTest(case_01("test_case3"))
    suites.addTest(case_01("test_case4"))
    suites.addTest(case_01("test_case5"))
    suites.addTest(case_01("test_case6"))
    suites.addTest(case_01("test_case7"))
    report_time = time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime(time.time()))
    report_file = '../'
    report_file_path = report_file + '/report/' + report_time + '-result.html'
    file_result = open(report_file_path, 'wb')
    # retry重試次數
    runer = HTMLTestRunner(stream=file_result, title="帶截圖的測試報告", description="溫一壺清酒", verbosity=2, retry=0,
                           save_last_try=True)
    runer.run(suites)

查看測試報告以下所示:

xml格式測試報告

生成xml格式的測試報告,代碼示例以下:

import unittest
import time
import xmlrunner

'''
@author: wenyihuqingjiu
@project: unittestdemo
@file: unittest_demo_suite6.py
@time: 2019-09-27 00:11
@desc:
'''

if __name__ == '__main__':
    path = './testcase'
    report_file = '../'
    # 找到某個目錄下全部的以test開頭的Python文件裏面的測試用例
    all_cases = unittest.defaultTestLoader.discover(path, 'test*.py')

    report_time = time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime(time.time()))
    report_file_path = report_file + '/report/'
    runner = xmlrunner.XMLTestRunner(verbosity=2, output=report_file_path)    # output 報告存放路徑
    runner.run(all_cases)

生成的報告以下所示:

問題

生成的測試報告,發送到郵箱中,預覽附件時,部分文字是亂碼,樣式按鈕也不可點擊,是由於樣式文件被瀏覽器攔截了嘛?

預覽文件以下:

 

本文僅表明做者觀點,系做者@溫一壺清酒發表。
歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利。
文章出處:http://www.cnblogs.com/hong-fithing/
相關文章
相關標籤/搜索