文章目录
前言
unittest是python内置的一个单元测试框架,它具备完整的测试结构,支持自动化测试的执行,对测试用例集进行组织,并且提供了丰富的断言方法,最后生成测试报告。Unittest框架的初衷是用于单元测试,但也不限于此,在实际工作中,由于它强大的功能,提供的完整的测试流程,我们往往将其用于自动化测试的各个方面,如接口测试,ui自动化测试等
一、初识unittest
1.查看unittest中的方法
代码如下(示例):
import unittest # 先导入unittest模块
print(dir(unittest)) # 可以看到有很多很多方法
2.概念和原理
(1)test fixture:测试固定装置,形象的说,把整个测试过程看作大的装置,这个装置里不仅具有测试执行部件(run),还有测试之前环境准备(setUp)和测试之后环境清理(tearDown)的部件,有机的结合起来就是一个更大的测试装置,即test fixture。
(2)test case:测试用例,一个完整的测试流程就是一个测试用例,通过一些特定的输入得到响应,并对响应进行校验的过程。我们通过去继承TestCase这个父类,可以创建新的测试用例。
(3)test suite:测试套件,也称为测试集合。多个测试用例组合在一起就形成了测试集,当然测试集里不仅能包含测试用例,也可以再次嵌套测试集,测试集可以用于代码的组织和运行。
(4)test runner:是Unittest中的重要组成部分,主要职责为执行测试,通过图形、文本或者返回一些特殊值的方式来呈现最终的运行结果。例如执行的用例数、成功和失败的用例数。
下图中展示了他们之间的关系,test fixture是包含了以test case为核心的整个组件,多个test case可以集合到一个test suite中,最后调用test runner执行并生成结果。
二、一个简单的例子
下面代码中将体现以下知识点:
- setUp() 是在每条测试用例执行前,都会执行的环境准备操作;
- tearDown()是在每条测试用例执行结束后,都会执行的环境恢复操作;
- 测试用例的名称必须以test开头,否则不会被执行;
- 测试用例执行顺序,默认是根据用例名称来执行的,所以这里加上序号进行直观的区分。
代码如下(示例):
import time
import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
class Baidu(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome() # 启动浏览器
url = 'https://www.baidu.com/'
self.driver.get(url) # 输入并访问被测网站
self.driver.maximize_window()
self.driver.implicitly_wait(10)
def tearDown(self):
self.driver.quit() # 关闭浏览器
def test01_search(self):
self.driver.find_element(By.XPATH,'//input[@id="kw"]').send_keys('python') # 百度搜索框中输入pyton
self.driver.find_element(By.XPATH,'//input[@id="su"]').click() # 点击百度一下
time.sleep(3)
def test02_clean(self):
self.driver.find_element(By.XPATH,'//input[@id="kw"]').send_keys('unittest') # 百度搜索框中输入unittest
self.driver.find_element(By.XPATH,'//input[@id="su"]').click() # 点击百度一下
time.sleep(3)
if __name__ == '__main__':
unittest.main
三、装饰器
前面讲了 setUp() 和 setDown() 的作用是在每条测试用例执行前准备测试环境以及用例测试结束后恢复测试环境,如果我们执行的测试类下所有测试用例的环境准备和环境复原的操作都是一样的,那么我们就没必要每条测试用例执行前都执行一次 setUp() 和 setDown() 的操作。可以在运行测试类前只执行一次环境的准备,在测试类运行结束后只执行一次环境复原的操作即可,这时我们就可以引入装饰器@classmethod
下面代码中将体现以下知识点:
- 前置条件和后置条件 若在所有的case之中只执行一次,需要使用setUpClass() 和 tearDownClass() 方法;
- setUpClass() 和 tearDownClass() 方法必须要使用@classmethod装饰器,否则报错;
- setUpClass() 和 tearDownClass() 方法中必须要使用类方法,即cls,而不是self
代码如下(示例):
import time
import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
class Baidu(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.driver = webdriver.Chrome() # 启动浏览器
url = 'https://www.baidu.com/'
cls.driver.get(url) # 输入并访问被测网站
cls.driver.maximize_window()
cls.driver.implicitly_wait(10)
@classmethod
def tearDownClass(cls):
cls.driver.quit() # 关闭浏览器
def test01_search(self):
self.driver.find_element(By.XPATH,'//input[@id="kw"]').send_keys('python') # 百度搜索框中输入pyton
self.driver.find_element(By.XPATH,'//input[@id="su"]').click() # 点击百度一下
time.sleep(3)
def test02_clean(self):
self.driver.find_element(By.XPATH,'//i[@title="清空"]').click() # 点击清空按钮
time.sleep(3)
if __name__ == '__main__':
unittest.main
四、跳过用例的执行(skip)
在执行测试用例时,有时候有些用例是不需要执行的,那我们怎么办呢?难道删除这些用例?那下次执行时如果又需要执行这些用例时,又把它补回来?这样操作就太麻烦了。
unittest提供了一些跳过指定用例的方法
- @unittest.skip(reason):强制跳转。reason是跳转原因
- @unittest.skipIf(condition,reason):condition为True的时候跳转
- @unittest.skipUnless(condition,reason):condition为False的时候跳转
- @unittest.expectedFailure:如果test失败了,这个test不计入失败的case数目
代码如下(示例):
@unittest.skipIf(2>1, 'condition为True的时候跳转')
def test01_search(self):
'''百度搜索框中输入pyton,点击百度一下'''
self.driver.find_element(By.XPATH,'//input[@id="kw"]').send_keys('python')
self.driver.find_element(By.XPATH,'//input[@id="su"]').click()
time.sleep(3)
@unittest.skip('强制跳转')
def test02_clean(self):
'''点击清空按钮'''
self.driver.find_element(By.XPATH,'//i[@title="清空"]').click()
time.sleep(3)
@unittest.expectedFailure
def test03_search(self):
'''百度搜索框中输入pyton,点击百度一下'''
self.driver.find_element(By.XPATH,'//input[@id="kw"]').send_keys('python')
self.driver.find_element(By.XPATH,'//input[@id="su"]').click()
time.sleep(3)
@unittest.skipUnless(3 < 2,'condition为False的时候跳转')
def test04_clean(self):
'''点击清空按钮'''
self.driver.find_element(By.XPATH,'//i[@title="清空"]').click()
time.sleep(3)
五、断言
我们在执行测试用例时,怎么来判断这条用例是否通过呢?唯一的办法就是拿实际结果和预期结果进行比较,如果一致用例就是通过的,否则用例就是失败的。在python中这种比较的方法就叫做断言,unittest框架提供了一系列的断言方法。
常用的断言方法如下:
序号 | 断言方法 | 描述 |
---|---|---|
1 | assertEqual(arg1, arg2, msg=None) | 验证arg1=arg2,不等则fail |
2 | assertNotEqual(arg1, arg2, msg=None) | 验证arg1 != arg2, 相等则fail |
3 | assertTrue(expr, msg=None) | 验证expr是true,如果为false,则fail |
4 | assertFalse(expr,msg=None) | 验证expr是false,如果为true,则fail |
5 | assertIs(arg1, arg2, msg=None) | 验证arg1、arg2是同一个对象,不是则fail |
6 | assertIsNot(arg1, arg2, msg=None) | 验证arg1、arg2不是同一个对象,是则fail |
7 | assertIsNone(expr, msg=None) | 验证expr是None,不是则fail |
8 | assertIsNotNone(expr, msg=None) | 验证expr不是None,是则fai |
9 | assertIn(arg1, arg2, msg=None) | 验证arg1是arg2的子串,不是则fail |
10 | assertNotIn(arg1, arg2, msg=None) | 验证arg1不是arg2的子串,是则fail |
代码如下(示例):
六、测试执行
以下代码示例分为四个步骤,1 导入被测包,2 创建测试套件, 3 向测试套件添加被测用例,4 运行测试结果
方式一:addTest()
- 优点:
- 可以共同添加多个文件/多个类中的多条测试用例–前提是要导入这个测试文件;
- 测试用例的执行顺序是按照添加用例时的自定义的顺序来的;
- 缺点:
- 只能一条一条的向suite中添加被测用例,数据多的时候非常麻烦;
- 适用于在少量的测试用例执行时使用,但是如果有几十上百个用例文件,这样做就会很浪费时间(这个后面讲discover方法);
语法示例:
addTests(self, tests)
tests:传入被测试的用例名称
代码如下(示例):
import unittest
# 导入所需类
from testtt.test1 import Baidu # 装有测试用例的类-包含了多条测试用例
from web_auto.radar_unittest import Rad # 装有测试用例的类-包含了多条测试用例
# 创建一个测试套件
suite = unittest.TestSuite()
# 向测试套件中一条一条的添加用例
suite.addTest(Baidu('test02_clean'))
suite.addTest(Rad('login'))
suite.addTest(Baidu('test01_search'))
# 也可以使用列表形式进行批量添加,这里看个人喜好
# test_cases = [Rad('login'),Baidu('test02_clean'),Baidu('test01_search')]
# suite.addTests(test_cases)
# 运行测试结果
runner = unittest.TextTestRunner(verbosity=2) # verbosity 参数可以控制输出的错误报告的详细程度,默认是 1;如果设为 0,则不输出每一用例的执行结果;如果设为 2,则输出详细的执行结果
runner.run(suite)
方式二:unittest.makeSuite()
- 优点:
- 可以增加一个类下的所有测试用例到suite中
- 缺点:
- 正常测试过程中我们不可能把所有项目脚本都放一个测试类文件中,所以该方式还是会比较局限
语法示例:
makeSuite(testCaseClass, prefix='test', sortUsing=util.three_way_cmp,suiteClass=suite.TestSuite)
testCaseClass:传入被测试的测试类名
代码如下(示例):
# 导入所需类
from testtt.test1 import Baidu # 装有测试用例的类
import unittest
# 把这个类里面的所有测试用例加入到集合,一起运行
suite = unittest.TestSuite(unittest.makeSuite(Baidu))
# 运行测试用例
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)
方式三:discover()
- 优点:
- 可以执行多个文件中的测试用例,大大提升了用例的添加效率,也是未来实际测试过程中最常用的一个
- 缺点:
- 测试用例的执行顺序是按照符合的文件名进行排序;
- 单个文件下再按照用例名的ASCLL码【0-9 A-Z a-z】进行排序,逐一执行
语法示例:
discover(start_dir, pattern='test*.py', top_level_dir=None)
start_dir:测试模块名或测试用例所在目录
pattern='test*.py':表示用例文件名的匹配方式,此处匹配的是以test开头的.py类型的文件,*表示匹配任意字符
top_level_dir:测试模块的顶层目录
代码如下(示例):
tests = unittest.defaultTestLoader.discover(start_dir=r'D:\auto\testtt',pattern='test*.py')
# 运行测试结果
runner = unittest.TextTestRunner(verbosity=2)
runner.run(tests)
# unittest.TextTestRunner().run(tests) 或是这样也可以运行
# unittest.main(defaultTest='tests') 或是这样也可以运行
七、测试报告
测试报告生成的结果
我们先来看看测试报告生成的结果,接下来在来解析如何生成,以下代码中将体现以下知识点:
- 生成测试报告,这里我们使用的是BeautifulReport模块,需要先安装该模块。pip install BeautifulReport
- 运行完成后生成的报告结果,会统计到常用的维度,如成功失败数量、每条用例的运行时间等;
- 其中用例描述中的内容是在代码中方法下方备注的(见代码示例中贴的代码)
- 展开”操作“选项中的文本备注,是代码中打印的内容
- 我们将对前边学习的内容做一个合并总结,下面的例子中,需要新建三个文件,其中两个编写测试用例的文件,比如这里是test1.py和 test2.py ,一个调用运行测试用例的文件,比如这里是run_testcase.py ,里面分别有如下代码
代码示例
代码如下(示例):-- 分别创建test1.py和test2.py,都放以下代码即可(放其他的也行,这里是为了测试简便)
import time
import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
class Baidu(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.driver = webdriver.Chrome() # 启动浏览器
url = 'https://www.baidu.com/'
cls.driver.get(url) # 输入并访问被测网站
cls.driver.maximize_window()
cls.driver.implicitly_wait(10)
@classmethod
def tearDownClass(cls):
cls.driver.quit() # 关闭浏览器
def test01_search(self):
'''百度搜索框中输入pyton,点击百度一下'''
self.driver.find_element(By.XPATH,'//input[@id="kw"]').send_keys('python')
self.driver.find_element(By.XPATH,'//input[@id="su"]').click()
time.sleep(3)
def test02_clean(self):
'''点击清空按钮'''
self.driver.find_element(By.XPATH,'//i[@title="清空"]').click()
time.sleep(3)
代码如下(示例):-- run_testcase.py,用来运行测试用例
from testtt.test1 import Baidu # 导入所需的用例模块
from testtt.test2 import Baidu # 导入所需的用例模块
import unittest
from BeautifulReport.BeautifulReport import BeautifulReport
# 创建一个测试套件
suite = unittest.TestSuite()
# 按照添加用例的顺序执行用例--列表的形式,或是使用以上提到的任意一种形式
test_cases = [Rad('login'),Baidu('test01_search'),Baidu('test02_clean')]
suite.addTests(test_cases)
# 运行测试并生成测试报告
bf = BeautifulReport(suite)
bf.report(description='test测试报告', filename='report1.html',
report_dir=r'D:\auto\testtt\report', theme='theme_cyan')
常见问题
1.BeautifulReport无法生成测试报告
解决办法:
a)PyCharm右上角,点击图中位置,选择Edit Configurations
b)在弹出窗口中点击添加,–选择左边列表中的python,–在右侧Script Path中选择本脚本所在路径,–右下角点击Apply,–点击ok
c)右上角选择当前脚本,点击绿色运行即可。注意这里通过右键 python tests for xxx的方式运行可能还是不会产生报告