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()


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
27天前
|
人工智能 搜索推荐 数据管理
探索软件测试中的自动化测试框架选择与优化策略
本文深入探讨了在现代软件开发流程中,如何根据项目特性、团队技能和长期维护需求,精准选择合适的自动化测试框架。
83 8
|
1月前
|
人工智能 JavaScript 前端开发
自动化测试框架的演进与实践###
本文深入探讨了自动化测试框架从诞生至今的发展历程,重点分析了当前主流框架的优势与局限性,并结合实际案例,阐述了如何根据项目需求选择合适的自动化测试策略。文章还展望了未来自动化测试领域的技术趋势,为读者提供了宝贵的实践经验和前瞻性思考。 ###
|
2天前
|
存储 测试技术 API
pytest接口自动化测试框架搭建
通过上述步骤,我们成功搭建了一个基于 `pytest`的接口自动化测试框架。这个框架具备良好的扩展性和可维护性,能够高效地管理和执行API测试。通过封装HTTP请求逻辑、使用 `conftest.py`定义共享资源和前置条件,并利用 `pytest.ini`进行配置管理,可以大幅提高测试的自动化程度和执行效率。希望本文能为您的测试工作提供实用的指导和帮助。
34 15
|
10天前
|
数据采集 人工智能 自然语言处理
Midscene.js:AI 驱动的 UI 自动化测试框架,支持自然语言交互,生成可视化报告
Midscene.js 是一款基于 AI 技术的 UI 自动化测试框架,通过自然语言交互简化测试流程,支持动作执行、数据查询和页面断言,提供可视化报告,适用于多种应用场景。
113 1
Midscene.js:AI 驱动的 UI 自动化测试框架,支持自然语言交互,生成可视化报告
|
23天前
|
Linux Shell 网络安全
Kali Linux系统Metasploit框架利用 HTA 文件进行渗透测试实验
本指南介绍如何利用 HTA 文件和 Metasploit 框架进行渗透测试。通过创建反向 shell、生成 HTA 文件、设置 HTTP 服务器和发送文件,最终实现对目标系统的控制。适用于教育目的,需合法授权。
55 9
Kali Linux系统Metasploit框架利用 HTA 文件进行渗透测试实验
|
28天前
|
安全 Ubuntu Linux
Metasploit Pro 4.22.6-2024111901 (Linux, Windows) - 专业渗透测试框架
Metasploit Pro 4.22.6-2024111901 (Linux, Windows) - 专业渗透测试框架
44 9
Metasploit Pro 4.22.6-2024111901 (Linux, Windows) - 专业渗透测试框架
|
1月前
|
Java 测试技术 API
探索软件测试中的自动化测试框架
本文深入探讨了自动化测试在软件开发中的重要性,并详细介绍了几种流行的自动化测试框架。通过比较它们的优缺点和适用场景,旨在为读者提供选择合适自动化测试工具的参考依据。
|
1月前
|
数据管理 jenkins 测试技术
自动化测试框架的设计与实现
在软件开发周期中,测试是确保产品质量的关键步骤。本文通过介绍自动化测试框架的设计原则、组件构成以及实现方法,旨在指导读者构建高效、可靠的自动化测试系统。文章不仅探讨了自动化测试的必要性和优势,还详细描述了框架搭建的具体步骤,包括工具选择、脚本开发、执行策略及结果分析等。此外,文章还强调了持续集成环境下自动化测试的重要性,并提供了实际案例分析,以帮助读者更好地理解和应用自动化测试框架。
|
1月前
|
监控 测试技术 定位技术
探索软件测试中的自动化测试框架选择与实施###
本文不概述传统意义上的摘要内容,而是直接以一段对话形式引入,旨在激发读者兴趣。想象一下,你是一名勇敢的探险家,面前摆满了各式各样的自动化测试工具地图,每张地图都指向未知的宝藏——高效、精准的软件测试领域。我们将一起踏上这段旅程,探讨如何根据项目特性选择合适的自动化测试框架,并分享实施过程中的关键步骤与避坑指南。 ###
45 4
|
1月前
|
敏捷开发 测试技术 持续交付
自动化测试之美:从零开始搭建你的Python测试框架
在软件开发的马拉松赛道上,自动化测试是那个能让你保持节奏、避免跌宕起伏的神奇小助手。本文将带你走进自动化测试的世界,用Python这把钥匙,解锁高效、可靠的测试框架之门。你将学会如何步步为营,构建属于自己的测试庇护所,让代码质量成为晨跑时清新的空气,而不是雾霾中的忧虑。让我们一起摆脱手动测试的繁琐枷锁,拥抱自动化带来的自由吧!