五)unittest框架
unittest框架是什么?为什么要使用unittest框架
- 在unittest框架中创建测试
- 在脚本中添加断言
- 通过unittest框架对脚本进行管理
- 自定义测试报告
- unittest框架中的参数化操作
python unittest:
Selenium python unittest framework
01通过unittest框架创建测试
- 必须继承于unittest.TestCase类
- 可定义setUp和tearDown方法进行初始化,也可使用setUpClass和tearDownClass来进行初始化
- 所有测试方法必须以test开头。测试方法会在运行时自动被调用。(凡是以test开头的叫测试方法,否则为普通方法,但可以在测试方法中调用普通方法)
- 可在pycharm自带的unittest框架运行,也可以以普通的方式运行。
02Unittest测试类的运行方式:
- pycharm中,以python自带的unittets框架来运行测试类。
右键->Run 'Unittests for ... '
凡是显示Run 'Unittests for...'这种菜单,九表示以pycharm内置的unittest模块开运行文件,这样运行不需要任何调用方法。
eg:
setUp方法:每个测试方法执行前执行一次。
tearDown方法:每个测试方法结束后执行一次。
setUpClass方法:整改测试执行前执行一次。
TearDownClass方法:整改测试执行后执行一次。
2.作为一个普通的py文件执行,需要修改配置
如下:
运行:
eg:
import unittest
class demo(unittest.TestCase):
def testFunc1(self):
print('这是testLogin方法')
def testFunc2(self):
print('这是一个测试方法')
if __name__ == '__main__':
print('测试已经开始,准备调用')
unittest.main() # 开始调用定义好的测试类
运行结果:测试已经开始,准备调用会被打印出来
03测试顺序
- 测试顺序是不固定的
eg:
2.若是要固定测试顺序 ,可以字母或数字来区分
import unittest
class demo(unittest.TestCase):
def testFunc1(self):
print('这是testLogin方法')
def testFunc2(self):
print('这是一个测试方法')
1)unittest的基本使用
- 介绍
- unittest是python自带的一个模块,可以用它进行单元测试
- 作用
- 以上所需selenium操作,仅仅完成了自动化测试的操作部分
- 关于是否和预期一致的对比,可以借助unittest来完成
- 为什么使用unittest
- 能够阻止多个用例去执行
- 提供丰富的断言方法
- 提供丰富的日志与测试结果
- 核心概念
- TestCase
测试用例:用类的方法,组织对一个功能的多项测试
-
- Fixture
夹具,用来固定测试环境
-
- TestSuite
测试套件:组织多个TestCase
-
- TestRunner
测试执行,用来执行TestSuite,并可以导出测试结果
-
- 其他
- TestResult :测试报告(某种格式的测试结果)
- TestLoader :根据某一规则找到TestSuite
- 其他
2)入门案例
三个步骤:
1、创建测试类
需要继承于unittest.TestCase
2、书写测试方法
需要以test开头的示例方法,且不能有参数
3、执行测试(在Pycharm可忽略)
eg:
import unittest
#1
class FirstTest(unittest.TestCase):
#2
def test_1(self):
print("test_1")
def test_2(self):
print("test_2")
#3
if __name__ == '__main__':
unittest.main()
3)断言
断言是自动化测试中比较预期结果和实际结果的一种方式。
自动化测试若是没有断言,只能叫做自动化操作。
1、Python的断言
- 用法:
-
- assert逻辑表达式
- assert逻辑表达式,字符串
- 说明
如果逻辑表达式为真,则继续执行
如果逻辑表达式为假,则抛出AssertionError,并包含字符串信息
2、unittest的断言
python自带的断言不够强大,只知道不成立,或者不一致,但不知道哪里不一致。
TestCase有assert开头的方法,非常强大。
断言一旦失败,是不会执行断言下面的语句,会自动停止这个方法。
方法列表:
方法 | 作用 | 好处 |
判断in | ||
assert(a,b) | ||
assertNotIn(a,b) | ||
判断真假 | ||
assertTrue | ||
assertFalses | ||
判断是否相等 | ||
assertEqual | ||
assertNotEqual | ||
判断类型 | ||
assertIsInstancce(a,b) | ||
assertNotIsInstance(a,b) | ||
判断容器内容是否一致 | ||
assertListEqual(a,b) | 列表是否一致 | 显示不同之处 |
assertTupleEqual(a,b) | 元组是否一致 | 显示不同之处 |
assertSetEqual(a,b) | 集合是否一致 | 显示不同之处 |
assertDictEqual(a,b) | 字典是否一致 | 显示不同之处 |
判断小数是否相等 | ||
assertAlmostEqual(a,b) | 忽略因二进制原因导致的微小误差 | |
assertNotAlmostEqual(a,b) | 忽略因二进制原因导致的微小误差 | |
判断大小关系 | ||
assertGreater(a,b) | ||
assertGreaterEqual(a,b) | ||
assertLess(a,b) | ||
assertLessEqual(a,b) | ||
判断是否符合正则 | ||
assertRegex(s,r) | 正则 | |
assertNotRegex(s,r) | ||
判断执行是否有异常 | ||
assertRaises(exc,fun,args,*kwds) | 是否会抛出特点异常,也可以用with | |
assertRaisesRegex(exc,fun,args,*kwds) | ||
其他 | ||
assertWarns(warn,fun,args,*kwds) | ||
assertWarnsRegex(warn,r,fun,args,*kwds) | ||
assertLogs(loogger,level) | ||
assertMultiLineEqual(a,b) | 多行文字是否相等 | |
assertSequenceEqual(a,b) | 两个序列元素顺序是否一致 | |
assertCountEqual(a,b) | 两个序列的是否有一致数量的的元素 |
eg:
import unittest
#1
class FirstTest(unittest.TestCase):
#2
def test_1(self):
print("test_1")
self.assetIn("a","helloworld") #第一种方式
#执行完后,获取到了一个response
response = "数据提交成功"
self.assetIn("成功",response) #错误,a不在hellworld
def test_2(self):
print("test_2")
#3
if __name__ == '__main__':
unittest.main()
eg:
import unittest
class demo(unittest.TestCase):
def testFunc1(self):
self.func()
print('这是testLogin方法')
self.assertEqual(1,2)
print('这句话不被执行')
def testFunc2(self):
print('这是一个测试方法')
self.assertNotEqual(1,2)
print('这个print会执行')
def func(self):
print('这是一个普通方法')
if __name__ == '__main__':
print('测试已经开始,准备调用')
unittest.main() # 开始调用定义好的测试类
4)子测试
可以用来执行多个相似的测试(参数不同),并得到多次测试结果
官方例子:
import unittest
class NumbersTest(unnittest.TestCase):
def test_even(self):
'''
Test that nnumbers between 0 and 5 are all even
'''
for i in range(0,5):
with self.subTest(i = i): #子测试,没有这一句,错误后,就不会继续运行后续的
self.assertEqual(i%2,0) #如果被2整除就通过
5)fixture
- 概念
fixture,即夹具。通过固定环境,来确保测试正常。
为什么要夹具:测试一个方法的时候,需要前提条件,测试完后,需要删除掉前提条件,夹具就有此应用。
- 说明
-
- 如果setUp相关方法执行有异常,则测试和tearDown相关方法也不会执行
- 如果setUp相关方法成功执行,不管测试是否有异常,tearDown相关方法都会执行
- 模块级>类级>实例级
- 模块级
一个模块,执行一次
import unittest
class NumbersTest(unnittest.TestCase):
def test_even(self):
'''
Test that nnumbers between 0 and 5 are all even
'''
for i in range(0,5):
with self.subTest(i = i): #子测试,没有这一句,错误后,就不会继续运行后续的
self.assertEqual(i%2,0) #如果被2整除就通过
- 类级
一个类的所有测试方法前后,执行一次
class Test(unittest,TestCase):
@classmethod
def setUpClass(cls):
cls._connection = createExpensiveConnectionObject()
@classmethod
def tearDownClass(cls):
cls._connection.destroy()
- 实例级
每次测试方法前后会执行
class WidgetTestCase(unnittest,TestCase):
def setUp(self):
self.widget = widget('The widget')
def tearDown(self):
self.widget.dispose()
eg:
import unittest
def setUpModule():
print("setUpModule")
def tearDownModule():
print("tearDownModule")
#1
class FirstTest(unittest.TestCase):
def setUpClass(cls):
print("setUpClass")
def tearDownClass(cls):
print("tearDownClass")
def setUp(self):
print("setUp")
def tearDown(self):
print("tearDown")
#2
def test_1(self):
print("test_1")
def test_2(self):
print("test_2")
#3
if __name__ == '__main__':
unittest.main()
结果:
6)TestSuite
- 作用:
用来自己组织待测试的内容和插件
使用方法
- 创建TestSuite对象
suite = unittest.TestSuite()
- 添加测试内容
suite.addTest() \ suite.addTests()
- 添加模块某个测试类的单个方法
suite.addTest(SomeTestCast("someTestMethod"))
-
- 添加测试类的多个方法
- 多次使用上面的方法
- 通过内置的map函数
- 添加测试类的多个方法
suite.addTests(map(SomeTestCast,["someTestMethod1","someTestMesthod2",...]))
-
- 添加测试类的所有方法
from unittest.loader import makeSuite #新版这个方法被去掉了
suite.addTest(makeSuite(SomeTestCast))
- 运行(一般会结合TestLoader使用,以下方法不用)
result = unittest.TestRasult()
suite.run(result)
eg:
import unittest
from unittest.loader import makeSuite
#unicode是Py文件包 unicode.test_unit_01,以下是同目录,不需要
from test_unit_01 import TestOne
#test_unit_01是写好的py文件,fixture的案例
suite = unittest.TestSuite()
suite.addTest(TestOne("test_01"))
suite.addTest(map(TestOne,["test_01","test_02"]))
suite.addTest(makeSuite(""))
runner = TextTestRunner()
runner.run(suite)
7)TestRunner
TestRunner在执行测试的时候,还把结果写道TestResul到中,方便后期查看。结合TestSuite使用
- 使用
runner = TextTestRunner()
runner.run(suite)
8)自动化用例管理TestLoader类
可以看作是通过指定规则,快速找到所有的测试用例(即一个TestSuite)
TestLoader类常见方法:
- loadTestsFromTestCase():可从事先定义好的测试类去加载这个测试类里面的测试方法
- loadTestsFromModule():通过测试模块(一个python文件)去加载
- loadTestsFromNames():通过测试方法名字加载
- discover():执行路径下所有的文件夹中的文件中的所有测试方法
如果一个项目里,遇到大量的测试用例、测试脚本,如何去管理,每次执行测试的数量、范围都不一样,如何去管理?
- 常用代码
test_dir = './'
#找到当前目录下的所有以test_开头的.py结尾的文件,并组成一个TestSuite
suite = unittest.defaultTestLoader.discover(test_dir,pattern = 'test_*.py')
#执行TestSuite
runner = unittest.TextTestRunner()
runner.run(suite)
eg:
import unittest
from unittest.loader import makeSuite
suite = unittest.TestSuite()
test_dir = './'
#找到当前目录下的所有以test_开头的.py结尾的文件,并组成一个TestSuite
suite = unittest.defaultTestLoader.discover(test_dir,pattern = 'test_*.py')
runner = TextTestRunner()
runner.run(suite)
01loadTestsFromTestCase()
测试实例:
stringtests:
import unittest
class StringTests(unittest.TestCase):
def testStr1(self):
self.assertIn('hello','hello world')
def testStr2(self):
self.assertIn('hello','bojunyixiao')
numbertests:
import unittest
class NumberTests(unittest.TestCase):
def testAdd(self):
self.assertEqual(1,1)
def testSub(self):
self.assertNotEqual(1,1)
ut1:
from stringtests import StringTests
from numbertests import NumberTests
import unittest
if __name__ == '__main__':
st = unittest.defaultTestLoader.loadTestsFromTestCase(StringTests)
nt = unittest.defaultTestLoader.loadTestsFromTestCase(NumberTests)
suit = unittest.TestSuite([st,nt]) #以列表的形式去组装st
unittest.TextTestRunner().run(suit)
结果:点代表成功,F代表失败,E表示测试脚本有问题
02loadTestsFromModule()
ut1:
import stringtests, numbertests
import unittest
if __name__ == '__main__':
names = [stringtests,numbertests]
modules = []
for item in names:
module = unittest.defaultTestLoader.loadTestsFromModule(item)
modules.append(module)
suit = unittest.TestSuite(modules)
unittest.TextTestRunner().run(suit)
03loadTestsFormNames()
方法一:麻烦
import unittest
if __name__ == '__main__':
names = ['numbertests.NumberTests.testSub','stringtests.StringTests.testStr1']
smoke_test = unittest.defaultTestLoader.loadTestsFromNames(names)
suite = unittest.TestSuite([smoke_test])
unittest.TextTestRunner().run(suite)
方法二:
改numbertests:
import unittest
class NumberTests(unittest.TestCase):
def testSomkeAdd(self):
self.assertEqual(1,1)
def testSystemSub(self):
self.assertNotEqual(1,1)
ut1:
from numbertests import NumberTests
import unittest
def getFullTestCaseName(names):
full_names = []
for item in names:
if 'Somke' in item:
full_names.append('numbertests.NumberTests.'+item)
return full_names
if __name__ == '__main__':
names = unittest.defaultTestLoader.getTestCaseNames(NumberTests)
print(names)
smoke_test = unittest.defaultTestLoader.loadTestsFromNames(getFullTestCaseName(names))
suite = unittest.TestSuite([smoke_test])
unittest.TextTestRunner().run(suite)
04discover() 最常用
一般文件夹:
reg:回归测试相关的测试脚本
smoke:冒烟测试相关的测试脚本
sys:系统测试相关的测试脚本
关于获取文件:https://www.cnblogs.com/miaokmm/p/11615058.html
import os
import unittest
if __name__ == '__main__':
#方法一,需import os
#case_path = os.path.join(os.getcwd(), 'smoke')
#print(case_path)
# dis = unittest.defaultTestLoader.discover(case_path,pattern='*.py')
#方法二
car_dir = 'smoke'
#和当前文件同路径下的smoke目录,专门用于存放测试文件
# pattern可匹配正则表达式
dis = unittest.defaultTestLoader.discover(car_dir, pattern='*.py')
suite = unittest.TestSuite(dis)
unittest.TextTestRunner().run(suite)
以下两种路径实现代码存放在path.py中,查看区别:
base_dir = os.path.dirname(os.getcwd()) # 第一种,当前目录
# os.getcwd() 定位路径为common
# os.path.dirname(os.getcwd()) 定位路径为API_work
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 第二种,该代码存放的根目录
# os.path.abspath(__file__) 定位路径为path.py
# os.path.dirname(os.path.abspath(__file__)) 定位路径为common
# os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 定位路径为API_work
05练习
利用unittest框架的知识,完成mms登录模块自动化测试脚本的编写。要求必须使用断言,且包含所有有效等价类和无效等价类,并且每个测试方法对应的用例以文档字符串形式写道测试方法名下面,比如:
def test_login_success(self):
#当用户名和密码正确时,能够成功登录系统
your code here
需求跟踪矩阵:
测试用例如下:
以下为密码或用户名提示错误是不符合实际的,仅作测试例子。
代码如下:
from selenium import webdriver
import unittest
class MMSLoginTest(unittest.TestCase):
def setUp(self) -> None:
self.driver = webdriver.Chrome()
self.url = 'http://localhost:8080/mms/login.html'
def tearDown(self) -> None:
self.driver.quit()
def test_login_success(self):
'''
测试当用户名和密码正确时,用户能够成功登陆到系统中
:return:
'''
self.driver.get(self.url)
self.driver.find_element('id', 'username').send_keys('admin')
self.driver.find_element('id', 'password').send_keys('123')
self.driver.find_element('xpath', "//input[@value='Login']").click()
loginname = self.driver.find_element('id', 'loginName').text
self.assertEqual('admin', loginname)
def test_login_failed_without_username(self):
'''
测试当用户名为空时,系统登陆失败
:return:
'''
self.driver.get(self.url)
self.driver.find_element('id', 'username').send_keys('')
self.driver.find_element('id', 'password').send_keys('123')
self.driver.find_element('xpath', "//input[@value='Login']").click()
errmsg = self.driver.find_element('xpath', "//div[contains(text(), '不能为空')]").text
self.assertEqual(errmsg, 'User Id不能为空')
def test_login_failed_without_password(self):
'''
测试当用户名密码为空时,系统登陆失败
:return:
'''
self.driver.get(self.url)
self.driver.find_element('id', 'username').send_keys('admin')
self.driver.find_element('id', 'password').send_keys('')
self.driver.find_element('xpath', "//input[@value='Login']").click()
errmsg = self.driver.find_element('xpath', "//div[contains(text(), '不能为空')]").text
self.assertEqual(errmsg, 'password不能为空')
def test_login_failed_with_incorrect_username(self):
'''
测试输入错误的用户名,系统登陆失败
:return:
'''
self.driver.get(self.url)
self.driver.find_element('id', 'username').send_keys('adminx')
self.driver.find_element('id', 'password').send_keys('123')
self.driver.find_element('xpath', "//input[@value='Login']").click()
errmsg = self.driver.find_element('xpath', "//div[contains(text(), '没有此用户')]").text
self.assertEqual(errmsg, '没有此用户')
def test_login_failed_with_incorrect_password(self):
'''
测试输入错误的登录密码,系统登陆失败
:return:
'''
self.driver.get(self.url)
self.driver.find_element('id', 'username').send_keys('admin')
self.driver.find_element('id', 'password').send_keys('admin123')
self.driver.find_element('xpath', "//input[@value='Login']").click()
errmsg = self.driver.find_element('xpath', "//div[contains(text(), '密码错误')]").text
self.assertEqual(errmsg, '密码错误')
if __name__ == '__main__':
unittest.main()
9)TestResult测试报告——HTMLTestRunner
测试报告
生成测试结果有多种方式
- txt格式的结果
在创建TextTestRunner的时候指定参数即可
stream默认为None,代表标准输出,可以指定为某个文件(通过调用open)
verbosity数字0静默 1默认(成功为点 失败为E ) 2显示完整信息
- pycharm导出html结果
使用Pycharm执行完后通过 图标 导出html测试结果
- 通过安装扩展生产HTM报告 一般使用这个
-
- 安装
pip install html-testRunner
-
- 使用执行测试,并生产报告
import HtmlTestRunner
runner = HtmlTestRunner.runner.HTMLTestRunner()
runner.run(suite)
-
- 把熬过的查看,报告默认放在当前目录的reports文件夹中。如果用浏览器无法查看时因为被“墙”的css样式。
import unittest
from unittest.loader import makeSuite
suite = unittest.TestSuite()
test_dir = './'
#找到当前目录下的所有以test_开头的.py结尾的文件,并组成一个TestSuite
suite = unittest.defaultTestLoader.discover(test_dir,pattern = 'test_*.py')
import HtmlTestRunner
runner = HtmlTestRunner.runner.HTMLTestRunner()
runner.run(suite)
使用HTMLTestRunner生成测试报告
- 配置方法(将HTMLTestRunner_cn.py放到python的site-packages目录下)
若是无法引用,可以放到要执行的当前文件同目录下
HTMLTestRunner_cn.py文件如下:(也可网上找一下,去下载)
- 生成测试报告
- 在测试报告中添加截图图片
- 失败重试功能
HTMLTestRunner()参数:
- output:是输出的文件夹,如果没有定义日志存放位置,日志报告会以html为后缀自动放在“工程目录/reports/file_d”这个路径的文件夹下,并以当前时间命名
- verbosity:=1时,默认值为1,不限制完整结果,即单个用例成功输出’.’,失败输出’F’,错误输出’E’;=0的时候,不输出信息;=2的时候,需要打印详细的返回信息;
- stream:默认值是sys.stderr,表示默认将结果输出到控制台,可以配置报告路径(但是要先用open()方法打开文件,是以一种文件流的方式),输出到指定位置;
- descriptions:默认值为True,
- combine_reports:默认值为False,将值设置为True可将测报告合并(以上py文件未设置)
- report_name:报告名称,report_name会自动加上时间后缀,时间格式为%Y-%m-%d-%H-%M-%S(以上py文件未设置)
实例:
#from HtmlTestRunner import HTMLTestRunner
from HTMLTestRunner_cn import HTMLTestRunner
from selenium import webdriver
import unittest
class MMSLoginTest(unittest.TestCase):
def setUp(self) -> None:
self.driver = webdriver.Chrome()
self.imgs = [] # 初始化存放测试截图的列表
#想要截图的地方都可以截图,一般错误的地方截图,imgs是框架定义的
self.url = 'http://localhost:8080/mms/login.html'
def tearDown(self) -> None:
self.driver.quit()
def test_login_success(self):
'''
测试当用户名和密码正确时,用户能够成功登陆到系统中
:return:
'''
self.driver.get(self.url)
self.imgs.append(self.driver.get_screenshot_as_base64()) #返回该屏幕快照的base64编码版本
self.driver.find_element('id', 'username').send_keys('admin')
self.driver.find_element('id', 'password').send_keys('123')
self.driver.find_element('xpath', "//input[@value='Login']").click()
# 执行截图操作,将当前截图加入到测试报告中
self.imgs.append(self.driver.get_screenshot_as_base64())
loginname = self.driver.find_element('id', 'loginName').text
self.assertEqual('admin', loginname)
def test_login_failed_without_username(self):
'''
测试当用户名为空时,系统登陆失败
:return:
'''
self.driver.get(self.url)
self.driver.find_element('id', 'username').send_keys('')
self.driver.find_element('id', 'password').send_keys('123')
self.imgs.append(self.driver.get_screenshot_as_base64())
self.driver.find_element('xpath', "//input[@value='Login']").click()
self.imgs.append(self.driver.get_screenshot_as_base64())
errmsg = self.driver.find_element('xpath', "//div[contains(text(), '不能为空')]").text
self.assertEqual(errmsg, 'User Id不能为空')
def test_login_failed_without_password(self):
'''
测试当密码为空时,系统登陆失败
:return:
'''
self.driver.get(self.url)
self.driver.find_element('id', 'username').send_keys('admin')
self.driver.find_element('id', 'password').send_keys('')
self.imgs.append(self.driver.get_screenshot_as_base64())
self.driver.find_element('xpath', "//input[@value='Login']").click()
self.imgs.append(self.driver.get_screenshot_as_base64())
errmsg = self.driver.find_element('xpath', "//div[contains(text(), '不能为空')]").text
self.assertEqual(errmsg, 'password不能为空')
def test_login_failed_with_incorrect_username(self):
'''
测试输入错误的用户名,系统登陆失败
:return:
'''
self.driver.get(self.url)
self.driver.find_element('id', 'username').send_keys('adminx')
self.driver.find_element('id', 'password').send_keys('123')
self.imgs.append(self.driver.get_screenshot_as_base64())
self.driver.find_element('xpath', "//input[@value='Login']").click()
self.imgs.append(self.driver.get_screenshot_as_base64())
errmsg = self.driver.find_element('xpath', "//div[contains(text(), '没有此用户')]").text
self.assertEqual(errmsg, '没有此用户')
def test_login_failed_with_incorrect_password(self):
'''
测试输入错误的登录密码,系统登陆失败
:return:
'''
self.driver.get(self.url)
self.driver.find_element('id', 'username').send_keys('admin')
self.driver.find_element('id', 'password').send_keys('admin123')
self.imgs.append(self.driver.get_screenshot_as_base64())
self.driver.find_element('xpath', "//input[@value='Login']").click()
self.imgs.append(self.driver.get_screenshot_as_base64())
errmsg = self.driver.find_element('xpath', "//div[contains(text(), '密码错误')]").text
self.assertEqual(errmsg, '错误')
if __name__ == '__main__':
test1 = unittest.defaultTestLoader.loadTestsFromTestCase(MMSLoginTest)
suite = unittest.TestSuite(test1)
runner = HTMLTestRunner(
tester= 'bx95',
title='带截图的测试报告',
description='xxx软件测试报告v0.1',
stream=open('sample_test_report.html', 'wb'), #stream生成报告文件流
verbosity=2, #报告信息的详细程度,一般是2
retry=1, #测试失败重试次数 ,可以不要
save_last_try=True #是否保留最后一次重试的结果,可以不要
)
runner.run(suite)
结果如下:
10)智能封装
要学会封装的方式,有助于更高效的工作效率。
eg:
import unittest
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.support.wait import WebDriverWait
class MyTest(unittest.TestCase):
def setUp(self) -> None:
self.driver = webdriver.Chrome()
self.driver.get('http://192.168.52.130/agileone')
def tearDown(self) -> None:
self.driver.quit()
def find_element(self, locator):
try: #显示等待,不需要再加入等待
element = WebDriverWait(self.driver, 30).until(lambda x: x.find_element(*locator)) #,匿名函数lambda,x传入的其实是driver
return element
except NoSuchElementException as e:
print('Error details: {}'.format(e.args[0])) #e.args[0]错误的详细信息
def test1(self):
self.find_element(('id', 'username')).send_keys('admin')
self.find_element(('id', 'password')).send_keys('admin')
self.find_element(('id', 'login')).click()
self.find_element(('partial link text', '会议')).click()
11)数据驱动
01在unittest框架中使用参数化
- 安装ddt:
pip install -i https://pypi.douban.com/simple ddt
参数化的几个形式
-
- 参数值为的单个参数形式
- 参数值为组合参数形式
- 从函数中返回参数值
- 从文件中返回参数值
参数化:
所谓参数化,是指利用不同的测试数据来测试相同的场景。为了提高代码的重用性,增加代码效率而采用一种代码编写的方法,叫参数化,也就是数据驱动。达到测试数据和测试业务相分离的效果。
eg1:
虽然通过以下方法提高了代码的复用性,但是报告上只生成了两个,统计结果不正确,不能解决根本问题
from HTMLTestRunner_cn import HTMLTestRunner
from selenium import webdriver
import unittest
import time
class MMSLoginTest(unittest.TestCase):
def setUp(self) -> None:
self.driver = webdriver.Chrome()
self.url = 'http://localhost:8080/mms/login.html'
#将所有的无效等价类,组合成一个列表,进行参数化
self.testdata = [
('','123','User Id不能为空'),
('amdin', '', 'password不能为空'),
('amdinx', '123', '没有此用户'),
('admin', '1234', '密码错误'),
]
def tearDown(self) -> None:
self.driver.quit()
def test_login_success(self):
'''
测试当用户名和密码正确时,用户能够成功登陆到系统中
:return:
'''
self.driver.get(self.url)
self.driver.find_element('id', 'username').send_keys('admin')
self.driver.find_element('id', 'password').send_keys('123')
self.driver.find_element('xpath', "//input[@value='Login']").click()
loginname = self.driver.find_element('id', 'loginName').text
self.assertEqual('admin', loginname)
def test_login_failed(self):
'''
无效测试用例
:return:
'''
self.driver.get(self.url)
time.sleep(1)
for data in self.testdata:
time.sleep(1)
self.driver.find_element('id', 'username').send_keys(data[0])
self.driver.find_element('id', 'password').send_keys(data[1])
self.driver.find_element('xpath', "//input[@value='Login']").click()
time.sleep(1)
errmsg = self.driver.find_element('xpath', "//div[contains(text(),'{}')]".format(data[2])).text
self.assertEqual(errmsg, data[2])
#点击确定以便重新输入
self.driver.find_element('xpath',"//span[contains(text(),'确定')]").click()
#进入下一次循环之气那先情况,避免循环执行有问题
self.driver.find_element('id', 'username').clear()
self.driver.find_element('id', 'password').clear()
if __name__ == '__main__':
test1 = unittest.defaultTestLoader.loadTestsFromTestCase(MMSLoginTest)
suite = unittest.TestSuite(test1)
unittest.TextTestRunner().run(suite)
解决:
既要实现参数化又要避免测试用例统计错误?
02参数值为的单个参数形式
from selenium import webdriver
import unittest
import time
from ddt import ddt,data,unpack
#以装饰器的方式来使用
@ddt
class MMSLoginTest(unittest.TestCase):
def setUp(self) -> None:
self.driver = webdriver.Chrome()
self.url = 'http://localhost:8080/mms/login.html'
def tearDown(self) -> None:
self.driver.quit()
#参数值为的单个参数形式
@data(1,2,3,4) #参数化,要提高数据,每个数据都是单个的
def test_data(self,value): #value接收data里面的每个值
print(value)
if __name__ == '__main__':
test1 = unittest.defaultTestLoader.loadTestsFromTestCase(MMSLoginTest)
suite = unittest.TestSuite(test1)
unittest.TextTestRunner().run(suite)
03参数值为组合参数形式
用的比较多
from selenium import webdriver
import unittest
import time
from ddt import ddt,data,unpack
#以装饰器的方式来使用
@ddt
class MMSLoginTest(unittest.TestCase):
def setUp(self) -> None:
self.driver = webdriver.Chrome()
self.url = 'http://localhost:8080/mms/login.html'
def tearDown(self) -> None:
self.driver.quit()
#组合用法
@data(
('', '123', 'User Id不能为空'),
('amdin', '', 'password不能为空'),
('amdinx', '123', '没有此用户'),
('admin', '1234', '密码错误'),
)
@unpack #把元素里面的每个组合再拆开
def test_login_failed(self,username,password,err):
'''
无效测试用例
:return:
'''
self.driver.get(self.url)
time.sleep(1)
self.driver.find_element('id', 'username').send_keys(username)
time.sleep(1)
self.driver.find_element('id', 'password').send_keys(password)
time.sleep(1)
self.driver.find_element('xpath', "//input[@value='Login']").click()
time.sleep(1)
errmsg = self.driver.find_element('xpath', "//div[contains(text(),'{}')]".format(err)).text
self.assertEqual(errmsg, err)
# 点击确定以便重新输入
self.driver.find_element('xpath', "//span[contains(text(),'确定')]").click()
# 进入下一次循环之气那先情况,避免循环执行有问题
self.driver.find_element('id', 'username').clear()
self.driver.find_element('id', 'password').clear()
if __name__ == '__main__':
test1 = unittest.defaultTestLoader.loadTestsFromTestCase(MMSLoginTest)
suite = unittest.TestSuite(test1)
unittest.TextTestRunner().run(suite)
计数准确:
解决:如果数据过多怎么办?都写在代码中不现实
-
- 从函数中返回参数值
- 从文件中返回参数值
04从函数中返回参数值
需要定义到类的外面,一般而言,我们会再定义一个util工具py文件,专门定义一些工具类。
util.py
class Util:
@classmethod
def get_data(cls):
testdata = [
('', 'admin123', 'User Id不能为空'),
('admin', '', 'password不能为空'),
('adminx', 'admin123', '没有此用户'),
('admin', 'xxxxx', '密码错误')
]
return testdata
logintest02.py:
05从文件中返回参数值
有效提高代码复用性。
data.txt
,admin123,User Id不能为空
admin,,password不能为空
adminx,admin123,没有此用户
admin,xxxxx,密码错误
util.py增加文件读取
class Util:
''' @classmethod
def get_data(cls):
testdata = [
('', 'admin123', 'User Id不能为空'),
('admin', '', 'password不能为空'),
('adminx', 'admin123', '没有此用户'),
('admin', 'xxxxx', '密码错误')
]
return testdata'''
@classmethod
def get_data_from_file(cls, path):
rows = []
with open(path, 'r', encoding='utf-8') as f:
for line in f:
user_data = line.strip().split(',')
rows.append(user_data)
return rows
logintest02.py:
from selenium import webdriver
import unittest
import time
from ddt import ddt,data,unpack
from util import Util
#以装饰器的方式来使用
@ddt
class MMSLoginTest(unittest.TestCase):
def setUp(self) -> None:
self.driver = webdriver.Chrome()
self.url = 'http://localhost:8080/mms/login.html'
def tearDown(self) -> None:
self.driver.quit()
@data(*Util.get_data_from_file('data.txt')) #*先去解包,否则会以大的列表传入,列表里再有4个元组
@unpack #把元素里面的每个组合再拆开
def test_login_failed(self,username,password,err):
'''
无效测试用例
:return:
'''
self.driver.get(self.url)
time.sleep(1)
self.driver.find_element('id', 'username').send_keys(username)
time.sleep(1)
self.driver.find_element('id', 'password').send_keys(password)
time.sleep(1)
self.driver.find_element('xpath', "//input[@value='Login']").click()
time.sleep(1)
errmsg = self.driver.find_element('xpath', "//div[contains(text(),'{}')]".format(err)).text
self.assertEqual(errmsg, err)
# 点击确定以便重新输入
self.driver.find_element('xpath', "//span[contains(text(),'确定')]").click()
# 进入下一次循环之气那先情况,避免循环执行有问题
self.driver.find_element('id', 'username').clear()
self.driver.find_element('id', 'password').clear()
if __name__ == '__main__':
test1 = unittest.defaultTestLoader.loadTestsFromTestCase(MMSLoginTest)
suite = unittest.TestSuite(test1)
unittest.TextTestRunner().run(suite)
12)断言失败后截图
如何实现断言失败后再截图功能
- 通过异常处理的方式
- 通过装饰器的方式
unittest当中,若是断言失败了则不会再去执行后面的语句了。
def test_demo1(self):
print('ok')
self.assertEqual(1,2)
print('hello') #如果以上断言失败了,这个print将不会执行,无法实现失败后截图
01通过异常处理方式
eg:
单并不实用,这样代码量太多了,代码冗余。
02通过装饰器的方式
不改变函数的形式下,加上功能
from selenium import webdriver
import unittest
import time
from ddt import ddt,data,unpack
from util import Util
#以装饰器的方式来使用
@ddt
class MMSLoginTest(unittest.TestCase):
def setUp(self) -> None:
self.driver = webdriver.Chrome()
self.url = 'http://localhost:8080/mms/login.html'
def tearDown(self) -> None:
self.driver.quit()
def addpic(func): #addpic只是一个普通的函数,作为一个高阶函数,需要接收一个方法引用作为参数
def wrapper(self, *args, **kwargs): #将参数全部接收,# 由于我们不知道被调用的函数到底有几个参数,写一个万能装饰器,传可变参数
try:
func(self, *args, **kwargs)
except AssertionError as e:
import os, time
day = time.strftime('%Y%m%d', time.localtime(time.time()))
screenshot_path = os.getcwd() + r'\reports\screenshot_%s' % day
if not os.path.exists(screenshot_path):
os.makedirs(screenshot_path)
tm = time.strftime('%H%M%S', time.localtime(time.time()))
self.driver.get_screenshot_as_file(screenshot_path + '\\{}_{}.png'.format('screen_shot', tm))
raise e # 将异常继续抛出给unittest,必须要写
return wrapper
@addpic
def test_demo1(self):
self.assertEqual(1, 2)
if __name__ == '__main__':
test1 = unittest.defaultTestLoader.loadTestsFromTestCase(MMSLoginTest)
suite = unittest.TestSuite(test1)
unittest.TextTestRunner().run(suite)