unittest 测试框架的使用

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 1. unittest 框架解析2. 批量执行测试脚本1)构建测试套件addTest() 方法makeSuite() 方法TestLoader() 方法2)用例的执行顺序3)忽略测试用例的执行3. unittest 断言4. HTML 报告生成5. 异常捕获与错误截图6. 数据驱动1)测试多个不同数据2)测试某个文件中的多组数据txt 文件或者 csv 文件JSON 文件

1. unittest 框架解析

unittest 是 python 的单元测试框架,主要有以下作用:


提供测试用例的组织和执行: unittest 框架可以将成百上千条测试用例组织在一起进行执行

提供丰富的比较方法: 再用例执行完成之后都需要将实际结果和预期结果进行比较(断言),从而断定用例是否通过,unittest 中提供了丰富的断言方法

提供丰富的日志: 当测试用例执行失败会抛出清晰的失败原因,当所有的用例执行完成后会提供丰富的执行结果,执行时间、失败用例数、成功用例数等

unittest 中有四个很重要的概念: test fixture、test case、test suite、test runner


test fixture


对一个测试用例环境的搭建和销毁,就是一个 fixture,通过重写 setUp() 方法和 tearDown() 方法来实现


setUp() 方法可以进行测试环境的搭建,比如获取浏览器的驱动、设置测试 URL、连接数据库等操作


tearDown() 方法及逆行环境的销毁,可以关闭浏览器、关闭数据库等


test case


一个 test case 就是一个测试用例,即一个完整的测试流程,包括 setUp 方法、tearDown 方法以及完成测试过程的代码


test suite


测试套件,test suite 用来将多个测试用例组装在一起


test runner


在 unittest 框架中,通过 textTestRunner 类下的 run() 方法来执行测试用例或者测试套件


下面是一个使用了 unittest 框架的简单测试脚本


脚本中的类必须继承 unittest.TestCase 类,之后这个类就是一个 TestCase

每一个 TestCase 中都必须含有 setUp 方法和 tearDown 方法

执行测试代码的方法必须以 “test_” 开头

unittest 中提供了 main 方法来执行本测试用例


from selenium import webdriver
import time
import unittest
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
class bing(unittest.TestCase):
    def setUp(self):
        print("------setUp------")
        self.driver = webdriver.Chrome()
        self.url = "https://cn.bing.com/"
        self.driver.maximize_window()
        time.sleep(3)
    def tearDown(self):
        print("------tearDown------")
        self.driver.quit()
    def test_search(self):
        driver = self.driver
        url = self.url
        driver.get(url)
        time.sleep(3)
        driver.find_element_by_id("sb_form_q").send_keys("python")
        time.sleep(3)
        driver.find_element_by_id("sb_form_q").send_keys(Keys.ENTER)
        time.sleep(3)
    def test_closeImg(self):
        driver = self.driver
        url = self.url
        driver.get(url)
        time.sleep(3)
        driver.find_element_by_id("id_sc").click()
        time.sleep(3)
        a = driver.find_element_by_xpath("//*[@id='qs_iotd_ctrl']/div/div[3]/div")
        ActionChains(driver).move_to_element(a).perform()
        time.sleep(3)
        a.click()
        time.sleep(3)
if __name__ == "__main__":
    unittest.main()

2. 批量执行测试脚本

1)构建测试套件

将多个测试用例组织起来形成一个 test suite 测试套件,就可以一次性执行多个测试用例

假设有如下两个测试用例:

testBing.py


from selenium import webdriver
import time
import unittest
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
class bing(unittest.TestCase):
    def setUp(self):
        print("------setUp------")
        self.driver = webdriver.Chrome()
        self.url = "https://cn.bing.com/"
        self.driver.maximize_window()
        time.sleep(3)
    def tearDown(self):
        print("------tearDown------")
        self.driver.quit()
    def test_search(self):
        driver = self.driver
        url = self.url
        driver.get(url)
        time.sleep(3)
        driver.find_element_by_id("sb_form_q").send_keys("python")
        time.sleep(3)
        driver.find_element_by_id("sb_form_q").send_keys(Keys.ENTER)
        time.sleep(3)
    def test_closeImg(self):
        driver = self.driver
        url = self.url
        driver.get(url)
        time.sleep(3)
        driver.find_element_by_id("id_sc").click()
        time.sleep(3)
        a = driver.find_element_by_xpath("//*[@id='qs_iotd_ctrl']/div/div[3]/div")
        ActionChains(driver).move_to_element(a).perform()
        time.sleep(3)
        a.click()
        time.sleep(3)
if __name__ == "__main__":
    unittest.main()

testBaidu.py

from selenium import webdriver
import time
import unittest
from selenium.webdriver.common.action_chains import ActionChains
class baidu(unittest.TestCase):
    def setUp(self):
        print("------setUp------")
        self.driver = webdriver.Chrome()
        self.url = "https://www.baidu.com/"
        self.driver.maximize_window()
        time.sleep(3)
    def tearDown(self):
        print("------tearDown------")
        self.driver.quit()
    def test_search(self):
        url = self.url
        driver = self.driver
        driver.get(url)
        time.sleep(3)
        driver.find_element_by_id("kw").send_keys("python")
        time.sleep(3)
        driver.find_element_by_id("su").click()
        time.sleep(3)
    def test_hao_search(self):
        url = self.url
        driver = self.driver
        driver.get(url)
        time.sleep(3)
        driver.find_element_by_link_text("hao123").click()
        time.sleep(3)
    def test_baiduTranslation(self):
        url = self.url
        driver = self.driver
        driver.get(url)
        time.sleep(3)
        a = driver.find_element_by_link_text("更多")
        ActionChains(driver).move_to_element(a).perform()
        time.sleep(3)
        driver.find_element_by_xpath("//*[@id='s-top-more']/div[1]/a[1]").click()
        time.sleep(3)
if __name__ == '__main__':
    unittest.main()

unittest 中提供了多种方法来构建测试套件

addTest() 方法

TestSuite 类的 addTest 方法可以把不同的测试类中的测试方法组装带测试套件中,但是 addTest 方法一次只能把一个类中的一个方法添加到测试套件中


import unittest
from src_selenium.src_unittest import testBing
from src_selenium.src_unittest import testBaidu
def createSuite():
    # addTest
    suite = unittest.TestSuite()
    suite.addTest(testBing.bing("test_closeImg"))
    suite.addTest(testBing.bing("test_search"))
    suite.addTest(testBaidu.baidu("test_search"))
    suite.addTest(testBaidu.baidu("test_hao_search"))
    return suite
# 执行测试套件
if __name__ == '__main__':
    suite = createSuite()
    # verbosity 设置日志级别,2最高,0最低
    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(suite)

makeSuite() 方法

makSuite 方法配合 addTest 方法可以实现一次将某个测试类中的所有测试方法添加到测试套件

只需要在 makeSuite 方法中传入测试类名即可

import unittest
from src_selenium.src_unittest import testBing
from src_selenium.src_unittest import testBaidu
def createSuite():
    # makeSuite
    suite = unittest.TestSuite()
    suite.addTest(unittest.makeSuite(testBing.bing))
    suite.addTest(unittest.makeSuite(testBaidu.baidu))
    return suite
# 执行测试套件
if __name__ == '__main__':
    suite = createSuite()
    # verbosity 设置日志级别,2最高,0最低
    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(suite)

TestLoader() 方法

TestLoader 方法的作用与 makeSuite 方法一样,都是将某个测试类中的所有测试方法添加到测试套件中


不同的是 TestLoader 不需要配合 addTest 使用,直接使用 unittest.TestLoader().loadTestsFromTestCase() 方法即可,


import unittest
from src_selenium.src_unittest import testBing
from src_selenium.src_unittest import testBaidu
def createSuite():
    # TestLoader
    suite1 = unittest.TestLoader().loadTestsFromTestCase(testBing.bing)
    suite2 = unittest.TestLoader().loadTestsFromTestCase(testBaidu.baidu)
    suite = unittest.TestSuite([suite1, suite2])
    return suite
# 执行测试套件
if __name__ == '__main__':
    suite = createSuite()
    # verbosity 设置日志级别,2最高,0最低
    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(suite)

discover() 的应用


discover 方法可以将某个目录下的所有符合标准的脚本文件中的所有测试方法添加到测试套件中


使用 unittest.defaultTestLoader.discover() 方法,第一个参数填入某个目录的绝对路径,第二个参数填入标准文件名,testB*.py 则表示以 testB 开头的 python 文件,第三个参数表示测试模块的顶层目录,一版设置为 None 即可

import unittest
from src_selenium.src_unittest import testBing
from src_selenium.src_unittest import testBaidu
def createSuite():
   # discover 的应用
    discover = unittest.defaultTestLoader.discover("D:\\JAVA\\Python\\project\\src_selenium\\src_unittest", pattern="testB*.py", top_level_dir=None)
    print(discover)
    return discover
# 执行测试套件
if __name__ == '__main__':
    suite = createSuite()
    # verbosity 设置日志级别,2最高,0最低
    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(suite)

2)用例的执行顺序

unittest 框架在执行多个测试方法时,会根据类名或者方法名的 ASCII 码顺序进行执行,即 0 ~ 9,A ~ Z,a ~ z

就测试方法来说,顺序是根据 “ test_ ” 后的单词进行排序


3)忽略测试用例的执行

如果在某一次测试中不需要执行某一个测试方法,就需要把这个测试方法忽略不执行,使用 @unittest.skip() 注解就可以完成,写入的参数会在控制台打印出


@unittest.skip("test_hao_search 被忽略")
def test_hao_search(self):
    url = self.url
    driver = self.driver
    driver.get(url)
    time.sleep(3)
    driver.find_element_by_link_text("hao123").click()
    time.sleep(3)

101.png


3. unittest 断言

对于每一个单独的测试用例来说,必然会有预期结果和实际结果,通过比对预期结果和实际结果就可以判断测试用例是否通过


反映到代码中就是断言,断言通过则会继续执行下面的代码,否则对应的测试方法就会停止或者生成错误信息,但不会影响其他测试方法的执行


unittest 中提供了丰富的断言方法


msg 参数为断言未通过时的提示语,可以自定义,也可以不写此参数


1683889185096.png1683889185096.png1683889199702.png

1683889199702.png

断言用法小示例:

def test_search(self):
    driver = self.driver
    url = self.url
    driver.get(url)
    time.sleep(3)
    driver.find_element_by_id("sb_form_q").send_keys("python")
    time.sleep(3)
    driver.find_element_by_id("sb_form_q").send_keys(Keys.ENTER)
    time.sleep(3)
    print(driver.title)
    # 验证 “打开的网页 title 不是 ‘python - 搜索’” 是否通过
    self.assertNotEqual("python - 搜索", driver.title, msg="未打开页面")\

未通过就会在控制台给出错误信息

102.png

4. HTML 报告生成

脚本执行完成之后,还需要生成一个测试报告,这里就可以使用 HTMLTestRunner.py 来生成 HTML 形式的测试报告


HTMLTestRunner.py 文件,下载地址: http://tungwaiyip.info/software/HTMLTestRunner.html


下载后将其放入 python 安装目录的 Lib 目录下


HTMLTestRunner 支持python2.7。python3可以参见http://blog.51cto.com/hzqldjb/1590802来进行修改。


import HTMLTestRunner
import os.path
import sys
import time
import unittest
from src_selenium.src_unittest import testBing
from src_selenium.src_unittest import testBaidu
def createSuite():
    # makeSuite
    suite = unittest.TestSuite()
    suite.addTest(unittest.makeSuite(testBing.bing))
    suite.addTest(unittest.makeSuite(testBaidu.baidu))
    return suite
if __name__ == '__main__':
    # 创建一个存放 HTML 报告的文件夹
    curPath = sys.path[0]  # 获取当前文件的路径
    if not os.path.exists(curPath + "/result"):
        # 判断是否存在此文件夹,不存在则创建一个
        os.makedirs(curPath + "/result")
    # 用当前时间作为 HTML 报告的文件名
    now = time.strftime("%Y-%m-%d-%H%M%S", time.localtime(time.time()))  # 获取当前时间
    # 拼接报告地址
    fileName = curPath + "/result/" + now + "report.html"
    # 出报告
    with open(fileName, "wb") as f:
        runner = HTMLTestRunner.HTMLTestRunner(f, title="测试报告", description="用例执行情况", verbosity=2)
        suite = createSuite()
        runner.run(suite)

在测试套件的主函数中添加上述代码,就可以生成一个 HTML 报告并保存到指定位置

报告打开之后如下所示:


103.png


标红的就是未通过的用例,点击 fail 还会显示详细的错误信息


5. 异常捕获与错误截图

在用例执行时如果可以将错误现场自动截图,那么就会给我们定位错误带来方便


编写一个函数,函数的主要功能就是截图并且保存到指定位置,此函数不以 “test_” 开头,即不让 unittest 自动执行该函数,将异常捕获之后,只在需 expect 中调用即可


使用 webdriver 下的 get_screenshot_as_file() 方法进行截图并保存

def test_search(self):
    driver = self.driver
    url = self.url
    driver.get(url)
    time.sleep(3)
    driver.find_element_by_id("sb_form_q").send_keys("python")
    time.sleep(3)
    driver.find_element_by_id("sb_form_q").send_keys(Keys.ENTER)
    time.sleep(3)
    try:
        self.assertNotEqual("python - 搜索", driver.title)
    except:
        self.saveScreenshot(driver, "search.png")
def saveScreenshot(self, driver, filename):
    # 创建文件夹保存截图
    if not os.path.exists("./img"):
        os.makedirs("./img")
    # 使用时间作为文件名
    now = time.strftime("%Y-%m-%d-%H%M%S", time.localtime(time.time()))
    driver.get_screenshot_as_file("./img/" + now + "-" + filename)
    time.sleep(1)

常被捕获之后就不会在控制台显示了,我们处理异常的方式是截图错误现场并保存起来

104.png


6. 数据驱动

前面我们所有的数据和用例都是写在一起的,但是如果想在同一个用例中测试多个不同数据,按照之前的方法就需要编写多个用例,但其实可以使用 ddt 数据驱动来完成,ddt 可以使一个用例测试多个数据


unittest 没有自带的数据驱动,所以我们需要使用 pip 另外下载 ddt 数据驱动


ddt 中常用的注解:


@ddt: 修饰测试类

@data: 修饰测试方法,参数是测试数据

@file_data: 修饰测试方法,参数是 JSON 文件名,以文件中的数据作为测试数据

@unpack: 当传递的数据是元组或者列表是,使用此注解修饰测试方法,ddt 就会自动将数据映射到参数上


1)测试多个不同数据

一个参数的多个不同值


使用 @ddt 参数修饰测试类,@data 注解修饰测试方法并传入参数的不同值,在方法的参数列表中添加一个参数作为本方法的测试参数

from ddt import ddt, data, unpack, file_data
from selenium import webdriver
import time
import unittest
from selenium.webdriver.common.keys import Keys
@ddt
class bing(unittest.TestCase):
    def setUp(self):
        print("------setUp------")
        self.driver = webdriver.Chrome()
        self.url = "https://cn.bing.com/"
        self.driver.maximize_window()
        time.sleep(3)
    def tearDown(self):
        print("------tearDown------")
        self.driver.quit()
    # 分别测试 bing 搜索 python、java、rust
    @data("python", "java", "rust")
    def test_search(self, value):
        driver = self.driver
        url = self.url
        driver.get(url)
        time.sleep(3)
        driver.find_element_by_id("sb_form_q").send_keys(value)
        time.sleep(3)
        driver.find_element_by_id("sb_form_q").send_keys(Keys.ENTER)
        time.sleep(3)
if __name__ == "__main__":
    unittest.main()

多个参数的多个不同值


修饰的注解不变,变得只是 @data 注解中的数据,可以使用列表表示一组测试数据


需要注意的是:当有多个参数时,需要使用 @unpack 注解修饰测试方法以映射多个参数


如下形式表示两个参数的多组数据


@data([3, 2], [4, 3], [5, 3])

from selenium import webdriver
import time
import unittest
from ddt import data, ddt, file_data, unpack
@ddt
class baidu(unittest.TestCase):
    def setUp(self):
        print("------setUp------")
        self.driver = webdriver.Chrome()
        self.url = "https://www.baidu.com/"
        self.driver.maximize_window()
        time.sleep(3)
    def tearDown(self):
        print("------tearDown------")
        self.driver.quit()
    @data(["python", "python_百度搜索"], ["java", "python_百度搜索"], ["rust", "python_百度搜索"])
    @unpack
    def test_search(self, value, expect_value):
        url = self.url
        driver = self.driver
        driver.get(url)
        time.sleep(3)
        driver.find_element_by_id("kw").send_keys(value)
        time.sleep(3)
        driver.find_element_by_id("su").click()
        time.sleep(3)
        self.assertEqual(expect_value, driver.title, msg="网页未打开")
if __name__ == '__main__':
    unittest.main()

2)测试某个文件中的多组数据

txt 文件或者 csv 文件

同样使用使用 @data() 注解修饰测试方法,但是参数填入的是解析 txt/csv 文件的方法

需要注意的是:文件的开头一行必须是 data,后面每一行为一组数据,这是固定格式,如下所示



105.png


import csv
from selenium import webdriver
import time
import unittest
from selenium.webdriver.common.action_chains import ActionChains
from ddt import data, ddt, file_data, unpack
def get_txt(file_name):
    tmp_data = []
    with open("./data/" + file_name, "r") as f:
        readers = csv.reader(f, delimiter=",", quotechar="|")
        next(readers, None)
        for row in readers:
            rows = []
            for i in row:
                rows.append(i)
            tmp_data.append(rows)
        print(tmp_data)
        return tmp_data
@ddt
class baidu(unittest.TestCase):
    def setUp(self):
        print("------setUp------")
        self.driver = webdriver.Chrome()
        self.url = "https://www.baidu.com/"
        self.driver.maximize_window()
        time.sleep(3)
    def tearDown(self):
        print("------tearDown------")
        self.driver.quit()
    @data(*get_txt("test_Baidu.txt"))
    @unpack
    def test_search(self, value, expect_value):
        url = self.url
        driver = self.driver
        driver.get(url)
        time.sleep(3)
        driver.find_element_by_id("kw").send_keys(value)
        time.sleep(3)
        driver.find_element_by_id("su").click()
        time.sleep(3)
        self.assertEqual(expect_value, driver.title, msg="网页未打开")
if __name__ == '__main__':
    unittest.main(verbosity=2)

JSON 文件

使用 @file_data(json 文件名) 修饰测试方法即可调用 json 文件中的数据


import os.path
from ddt import ddt, data, unpack, file_data
from selenium import webdriver
import time
import unittest
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
@ddt
class bing(unittest.TestCase):
    def setUp(self):
        print("------setUp------")
        self.driver = webdriver.Chrome()
        self.url = "https://cn.bing.com/"
        self.driver.maximize_window()
        time.sleep(3)
    def tearDown(self):
        print("------tearDown------")
        self.driver.quit()
    # @data("python", "java", "rust")
    @file_data("./data/test_Bing.json")
    def test_search(self, value):
        driver = self.driver
        url = self.url
        driver.get(url)
        time.sleep(3)
        driver.find_element_by_id("sb_form_q").send_keys(value)
        time.sleep(3)
        driver.find_element_by_id("sb_form_q").send_keys(Keys.ENTER)
        time.sleep(3)
if __name__ == "__main__":
    unittest.main()


相关实践学习
通过日志服务实现云资源OSS的安全审计
本实验介绍如何通过日志服务实现云资源OSS的安全审计。
目录
相关文章
|
2月前
|
Web App开发 人工智能 JavaScript
主流自动化测试框架的技术解析与实战指南
本内容深入解析主流测试框架Playwright、Selenium与Cypress的核心架构与适用场景,对比其在SPA测试、CI/CD、跨浏览器兼容性等方面的表现。同时探讨Playwright在AI增强测试、录制回放、企业部署等领域的实战优势,以及Selenium在老旧系统和IE兼容性中的坚守场景。结合六大典型场景,提供技术选型决策指南,并展望AI赋能下的未来测试体系。
|
24天前
|
安全 Linux 网络安全
Metasploit Pro 4.22.8-2025091701 (Linux, Windows) - 专业渗透测试框架
Metasploit Pro 4.22.8-2025091701 (Linux, Windows) - 专业渗透测试框架
177 2
Metasploit Pro 4.22.8-2025091701 (Linux, Windows) - 专业渗透测试框架
|
24天前
|
Linux 网络安全 iOS开发
Metasploit Framework 6.4.90 (macOS, Linux, Windows) - 开源渗透测试框架
Metasploit Framework 6.4.90 (macOS, Linux, Windows) - 开源渗透测试框架
279 1
Metasploit Framework 6.4.90 (macOS, Linux, Windows) - 开源渗透测试框架
|
1月前
|
安全 Linux 网络安全
Metasploit Framework 6.4.88 (macOS, Linux, Windows) - 开源渗透测试框架
Metasploit Framework 6.4.88 (macOS, Linux, Windows) - 开源渗透测试框架
449 0
|
1月前
|
缓存 安全 Linux
Metasploit Pro 4.22.8-2025082101 (Linux, Windows) - 专业渗透测试框架
Metasploit Pro 4.22.8-2025082101 (Linux, Windows) - 专业渗透测试框架
121 0
|
3月前
|
Web App开发 开发框架 .NET
Playwright 自动化测试系列(6)| 第三阶段:测试框架集成​指南:参数化测试 + 多浏览器并行执行
Pytest 与 Playwright 集成可提升自动化测试效率,支持参数化测试、多浏览器并行执行及统一报告生成。通过数据驱动、Fixture 管理和并行优化,显著增强测试覆盖率与执行速度,适用于复杂 Web 应用测试场景。
|
4月前
|
安全 Linux 网络安全
Metasploit Pro 4.22.7-2025061201 (Linux, Windows) - 专业渗透测试框架
Metasploit Pro 4.22.7-2025061201 (Linux, Windows) - 专业渗透测试框架
143 3
Metasploit Pro 4.22.7-2025061201 (Linux, Windows) - 专业渗透测试框架
|
2月前
|
SQL 安全 Linux
Metasploit Pro 4.22.8-2025073001 (Linux, Windows) - 专业渗透测试框架
Metasploit Pro 4.22.8-2025073001 (Linux, Windows) - 专业渗透测试框架
143 0
|
3月前
|
测试技术 API C++
Playwright 自动化测试系列(7)| 第三阶段:测试框架集成​​Page Object 模式
本课程详解Playwright测试框架中的Page Object模式,通过电商登录-下单实战演示PO架构设计与高级技巧,结合Pytest实现多用户测试。重点解析PO模式提升代码复用性、降低维护成本的核心价值,并提供常见问题解决方案,助力构建高可维护性的自动化测试体系。
|
3月前
|
Java 测试技术 API
自动化测试框架深度解析与选择指南
Apache JMeter是Apache组织基于Java开发的一款压力测试工具,旨在测试软件的性能承受能力。它支持多种协议测试及功能测试,提供灵活的断言创建能力,如同创建带断言的脚本来验证程序是否返回预期结果。

热门文章

最新文章