unittest 测试框架的使用

1. unittest 框架解析

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

  • 提供测试用例的组织和执行: unittest 框架可以将成百上千条测试用例组织在一起进行执行
  • 提供丰富的比较方法: 再用例执行完成之后都需要将实际结果和预期结果进行比较(断言),从而断定用例是否通过,unittest 中提供了丰富的断言方法
  • 提供丰富的日志: 当测试用例执行失败会抛出清晰的失败原因,当所有的用例执行完成后会提供丰富的执行结果,执行时间、失败用例数、成功用例数等

unittest 中有四个很重要的概念: test fixturetest casetest suitetest 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)

请添加图片描述

3. unittest 断言

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

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

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

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

断言方法描述
assertEqual(arg1, arg2, msg=None)验证 “arg1== arg2” 是否通过
assertNotEqual(arg1, arg2, msg=None)验证 “arg1!= arg2” 是否通过
assertTrue(arg, msg=None)验证 “args 为 True” 是否通过
assertFalse(arg, msg=None)验证 “args 为 False” 是否通过
assertIs(arg1, arg2, msg=None)验证 “arg1 和 arg2 是同一个对象” 是否通过
assertNotIs(arg1, arg2, msg=None)验证 “arg1 和 arg2 不是同一个对象”是否通过
assertIsNone(arg, msg=None)验证 “arg 是 None” 是否通过
assertIsNotNone(arg, msg=None)验证 “arg 不是 None” 是否通过
assertIn(arg1, arg2, msg=None)验证 “arg1 是 arg2 的子串” 是否通过
assertNotIn(arg1, arg2, msg=None)验证 “arg1 不是 arg2 的子串” 是否通过
assertIsInstance(obj, cls, msg=None)验证 “obj 是 cls 的实例” 是否通过
assertNotIsInstance(obj, cls, msg=None)验证 “obj 不是 cls 的实例” 是否通过

断言用法小示例:

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="未打开页面")

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

请添加图片描述

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 报告并保存到指定位置

报告打开之后如下所示:

请添加图片描述

标红的就是未通过的用例,点击 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)

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

请添加图片描述

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,后面每一行为一组数据,这是固定格式,如下所示

请添加图片描述

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()
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hssq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值