1、unittest简介
unittest是Python自带的单元测试框架,具备编写用例、组织用例、执行用例、输出报告等自动化框架的条件,主要适用于单元测试,可以用来作自动化测试框架的用例组织执行框架。
2、unittest框架的特性:
- 提供用例组织与执行:当测试用例只有几条的时候可以不考虑用例的组织,但是当测试用例数量较多时,此时就需要考虑用例的规范与组织问题。unittest单元测试框架就是用 来解决这个问题的。
- 提供丰富的断言方法:既然是测试,就有一个预期结果和实际结果的比较问题。比较就是通过断言来实现,unittest单元测试框架提供了丰富的断言方法,通过捕获返回值,并且与预期值进行比较,从而得出测试通过与否。
- 提供丰富的日志:每一个失败用例我们都希望知道失败的原因,所有用例执行结束我们 有希望知道整体执行情况,比如总体执行时间,失败用例数,成功用例数。unittest单元 测试框架为我们提供了这些数据。
3、unittest核心工作原理
unittest的静态类图:
大体流程︰编写TestCase,由TestLoader加载TestCase到TestSuite ,然后由 TextTestRunner来运行TestSuite,最后将运行的结果保存在TextTestResult中。
4、封装前置和后置
(1)setup(),tearDown()(每次执行都会执行一遍)
好处:每次执行一遍
坏处:有100条用例时,会连续执行100次。此时,需要使用setUpClass()和tearDownClass()提高效率,节省时间
用法:[1]使用init实例化浏览器,[2]setup()获取URL、设置等待操作,[3]tearDown()测试完成清除工作,如关闭浏览器和数据库还原等【使用一个类封装此部分代码,然后在运行类中继承类+导入】
(2)setUpClass()和tearDownClass()(多条用例时只会执行一次)【用第二种】
好处:运行消耗比setUp()节省时间
用法:
-
- 新建一个Myunit.py文件,定义TestWebUI类
-
-
- [1]使用init实例化浏览器
- [2] @classmethod装饰器结合setUpClass()方法启动浏览器、获取URL、设置等待操作
- [3]@classmethod装饰器结合tearDownClass()方法完成清除工作,如关闭浏览器和数据库还原等【使用一个类封装此部分代码,然后在运行类中继承类+导入】
-
-
- 新建Test文件,导入Myunit.py模块的所有测试类和方法,继承TestWebUI类,从而实例化浏览器
##2.11.2 前置和后置
import unittest
class TestStringMethods(unittest.TestCase):
def setUp(self):
print('每条测试用例开始执行前做的操作.....')
def test_isupper(self):
self.assertTrue('FOO'.endswith('O'))
self.assertFalse('Foo'.isupper())
print('第一条测试用例')
def test_strendswich(self):
self.assertEqual('foo'.endswith('o'), True)
print('第二条测试用例')
def tearDown(self):
print('每条测试用例执行完毕后做的操作.....')
if __name__ == '__main__':
unittest.main()
-------------------
##2.11.4 setUpClass和tearDownClass
import unittest
from selenium import webdriver
from time import sleep
class TestWebUI(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.driver = webdriver.Chrome()
@classmethod
def tearDownClass(cls):
cls.driver.quit()
def test_QQLogin(self):
self.driver.get('https://mail.qq.com/cgi-bin/loginpage')
self.assertEqual(self.driver.title,'登录QQ邮箱')
def test_MaoyanMovie(self):
self.driver.get('https://maoyan.com/')
self.assertEqual(self.driver.title,'猫眼电影 - 一网打尽好电影')
if __name__ == '__main__':
unittest.main()
5、Unittest组成(存放测试用例)
unittest单元测试中最核心的四个部分是:
- TestCase(测试用例)
- TestSuite(测试套件)
- TestRunner(测试运行器)
- TestFixture(测试环境数据准备和清理)。
- TestCase:一个TestCase的实例就是一个测试用例。而一个完整的测试用例包括测试前准备环境的搭建(setUp)、实现测试过程的代码(run),以及测试后环境的还原(tearDown)。
- TestSuite【常用】:一个功能的验证需要多个测试用例,将多个测试用例集合在一起来执行。TestSuite就是用来组装单个测试用例,可以通过addTest方法加载TestCase到TestSuite中,从而返回一个TestSuite实例。
而且 TestSuite也可以嵌套TestSuite。
# 构造测试集:
suite = unittest.TestSuite()
suite.addTest(类名("方法名"))
方法一:使用addTest()把用例逐个添加到suite
在断言case的情况下,if name主模块下添加测试集合。步骤:
● 先创建一个测试集合suite
● 将测试用例一个个使用addTest()把列表添加到suite
● 创建一个runner变量获取unittest.TextTestRunner() ,或者使用HTMLTestRunner(),然后使用run(列表)方法执行所有用例
if __name__=="__main__":
suite=unittest.Testsuite()
suite.addTest(TestCase01('test_06'))
suite.addTest(TestCase01('test _04'))
suite.addTest(TestCase01('test_02'))
suite.addTest(TestCase01('test_05'))
suite.addTest(TestCase01('test_01'))
suite.addTest(TestCase01('test_07'))
runner=unittest.TextTestRunner()
runner.run(suite)
方法二:用例放入列表中,再添加到suite
在断言case的情况下,if name主模块下添加测试集合。步骤:
● 先创建一个测试集合suite
● 定义一个列表,创建数组添加所有case(最后执行,按照添加的顺序执行)
● 使用addTests()把列表添加到suite
● 创建一个runner变量获取unittest.TextTestRunner() ,或者使用HTMLTestRunner(),然后使用run(列表)方法执行所有用例
if __name__=="__main__":
#创建一个测试集合suite
suite=unittest.Testsuite()
#定义列表,把所有用例加到列表上,[类名1("方法名1"),类名2("方法名2")]
tests=[testCase01('test_06"),testCase01('test_04"),testCase01('test_02"),testCase01('test_05"),testCase01('test_01"),testCase01('test_07")]
suite.addTests(tests)
方式一:
runner=unittest.TextTestRunner()
runner.run(suite)
方式二:
#报告存放的文件夹
dir_path=r'D:\workspace\seleniumtest'
#报告命名加上时间格式化
nowtime=time.strftime("%Y-%m-%d %H-%M-%S",time.localtime())
#报告绝对路径,打开文件,写入测试结果
filename = open(dir_path+ nowtime+"/report.html", "wb")
runner = HTMLTestRunner(stream=filename, verbosity=2, title="自动化测试报告 ",description = "报告详细如下:")
runner.run(suite)
#写入文件完成后,关闭文件
filename.close()
3.TestRunner:在unittest单元测试框架中,通过TextTestRunner类提供的run()方法来执行TestSuite/TestCase。Testrunner可以使用图形界面、文本界面,或返回一个特殊的值等方式来表示测试结果的执行。
# 运行测试集: runner = unittest.TextTestRunner() runner.run(suite)
4.TestFixture(测试固件):对一个测试用例环境的搭建和销毁,就是一个fixture,通过覆盖TestCase的setUp()和tearDown()方法来实现。
比如说在测试用例中需要访问数据库,那么可以在 setUp()中建立数据库连接以及进行一些初始化,在tearDown()中清除在数据库中产生的 数据,然后关闭连接。注意tearDown的过程很重要,要为以后的TestCase留下一个干净的环境。
-
-
- 也叫测试夹件,主要工作是【初始化和善后】
- 测试固件分为两种,一种是类级别的,一种是方法级别的
- 类级别的测试固件,所有测试用例执行之前与之后运行一次
- 方法级别的测试固件,每个测试用例执行之前与之后都运行一次
-
- 5、通过TestLoader类的discover 方法来【常用】
-
discover()方法: 由TestLoader类提供,使unittest单元测试框架自动识别测试用例
discover(start_dir, pattern='test*.py', top_level_dir=None):找到指定目录下所有测试模块,并可递归查到子目录下的测试模块,只有匹配到测试用例文件名才能被加载到测试集。如果启动的不是顶层目录,那么顶层目录必须单独指定。
(1)start_dir:要测试的模块名或测试用例目录
假设有测试目录,对于这样的目录结构,若将discover()方法中的start_dir参数定义为“./test_case/”目录,只能加载test_a.py文件中的测试用例。如果要让unittest框架找到test_case的子目录中的测试文件,需要在每个子目录下放一个__init__.py文件。
test_project/
test_case/
test_bbb/
test_ccc/
test_c.py
test_b.py
test_ddd/
test_d.py
test_a.py
(2)pattern='test*.py':表示用例文件名的匹配规则。此处匹配文件名以"test"开头的".py"类型的文件,"*"表示任意多个字符
(3)top_level_dir=None:测试模块的顶层目录,如果没有顶层目录,默认为None
解释:discover()方法会自动根据测试目录(test dir)匹配查找测 试用例文件(test*.py),并将查找到的测试用例组装到测试套件 中,因此,可以直接通过run()方法执行discover,大大简化了 测试用例的查找与执行。
# eg:runtest.py -------->测试用例执行文件
import unittest,time
from HTMLTestRunner import HTMLTestRunner
test_dir = './test_case'
discover = unittest.defaultTestLoader.discover(test_dir, pattern='test_*.py')
if __name__ == '__main__':
now = time.strftime("%Y-%m-%d %H_%M_%S")
filename = test_dir + '/' + now + 'result.html'
f = open(filename, 'wb')
runner = HTMLTestRunner(stream=f, title='测试报告', description='用例执行情况:')
runner.run(discover)
f.close()
方法二:实现直接执行,不需要一个个方法导入。等同于,添加一个集合,执行case。
#coding=utf-8
import sys,os,unittest
case_path=os.getcwd()+"/UnittestCase/"
print(case_path)
discover=unittest.defaultTestLoader.discover(case_path,pattern='test_*.py')
#unittest.TextTestRunner().run(discover)
if __name__ == '__main__':
now = time.strftime("%Y-%m-%d %H_%M_%S")
filename = test_dir + '/' + now + 'result.html'
f = open(filename, 'wb')
runner = HTMLTestRunner(stream=f, title='测试报告', description='用例执行情况:')
runner.run(discover)
f.close()
方法三:封装一个方法,再直接调用
import os,time,unittest
from HTMLTestRunner import HTMLTestRunner
def getAllCases():
'''获取tesTcase下面的所有测试模块'''
Testsuite = unittest.defaultTestLoader.discover(
start_dir=os.path.join(os.path.dirname(__file__),'TestCases'),
pattern='test*.py')
return Testsuite
def RunMain():
'''生成测试报告写入指定Reports目录'''
now=time.strftime("%Y_%m_%d_%H_%M_%S")
fp=open(os.path.join(os.path.dirname(__file__),'report', now + 'report.html'),'wb')
runner=HTMLTestRunner(stream=fp,title='Python+Selenium自动化测试实战',
description='基于python语言PO自动化测试')
runner.run(getAllCases())
if __name__ == '__main__':
RunMain()
6、断言方法
断言即在测试用例执行过程中,通过判断测试得到实际结果和预期结果是否相等。
断言方法包括:
- assertEqual(first,second,msg=None):断言第一个参数和第二个参数相等,若不相等则测试失败。msg为可选参数,定意测试失败时打印的信息
- assertNotEqual(first,second,msg=None):断言第一个参数和第二个参数不相等,若相等则测试失败
- assertTrue(表达式,msg=None):断言表达式结果为True
- assertFalse(表达式,msg=None):断言表达式结果为False
- assertIn(first,second,msg=None):断言第一个参数在第二个参数中
- assertNotIn(first,second,msg=None):断言第一个参数不在第二个参数中
- assertIs(first,second,msg=None):断言第一个参数和第二个参数是同一个对象
- assertIsNot(first,second,msg=None):断言第一个参数和第二个参数不是同一个对象
- assertIsNone(表达式,msg=None):断言表达式为None对象
- assertIsNotNone(表达式,msg=None):断言表达式不为None对象
- assertIsInstance(obj,cls,msg=None):断言obj是cls的一个实例
- assertNotIsInstance(obj,cls,msg=None):断言obj不是cls的实例
#coding=utf-8
import requests
import unittest
url="http://www.imooc.com"
data={
"username":"1111",
"password":"22222"
}
class TestCase1(unittest.TestCase):
def setUp(self):
print("case开始执行")
self.driver = webdriver.Chrome()
def tearDown(self):
print("case结束执行")
self.driver.quit()
@classmethod
def setUpClass(c1s):
print("case类开始执行")
@classmethod
def tearDownClass(c1s):
pass #无执行内容时,使用pass直接执行略过
#print("case类执行结束")
'#使用方法时要查看判断的类型是什么,当前是dict,这个会执行失败
#assertEqual
def test_QQLogin(self):
self.driver.get('https://mail.qq.com/cgi-bin/loginpage')
self.assertEqual(self.driver.title,'登录QQ邮箱','页面跳转失败,请重新检查;')
def test_05(self):
flag="111"
flag1="2222"
self.assertEqual(flag,flag1)
#assertIn
def test_06(self):
flag="adfadfadfadfadsfaqeewr"
s="fads"
self.assertIn(s,flag,msg="不等于true")
print("06")
def test_01(self):
res =requests.get(url=url,params=data).json()
#data1和data的值不一样
data1={ "user":"11111"
}
self.assertDictEqual(res,data1)
'''#可以在断言处增加msg,打印'''
def test_07(self):
data1={
"username":"111111",
"password":"22222"
}
self.assertDictEqual(data,data1,msg="这2个方法不相同")
def test_03(self):
flag=True
self.assertFalse(flag)
def test_04(self):
flag=False
self.assertTrue(flag)
if __name__=="__main__":
unittest.main()
注意:
(1)assertIn()判断方式与断言执行效果一样的,如:
if "username"and "password"in res:
print(“执行成功")
(2)注意断点方法根据变量的类型设置,断言方法包括:看上方例子
(3)断言方法处可以使用msg,可以打印对应文字
7、unittest执行测试用例的顺序
默认根据ASCII码的顺序加载测试用例,数字与字母的顺序为:0-9,A-Z,a-z。对于测试目录与测试文件也是一样的加载顺序,只能通过测试用例的命名来提高被执行的优先级。
如:用例执行顺序。在代码中test sub方法写在test add前,但实际,test add比test sub 先运行。为什么呢? unittest执行 测试用例,默认是根据ASCII码的顺序加载测试用例, 数字与字母的顺序为: 0-9,A-Z,a-Z。
8、跳过测试和预期失败
若想让某个测试用例不执行,有没有办法呢?当然是有的,可以使用skip装饰器。
unittest提供了如下装饰器:
unittest.skip(reason):无条件的跳过装饰的测试,说明跳过测试的原因
unittest.skipIf(condition, reason):如果条件(condition)为真,跳过装饰的测试
unittest.skipUnless(condition, reason):如果条件(condition)为假,跳过装饰的测试
unittest.expectedFailure():测试标记为失败。不管执行结果是否失败,统一标记为失败
# eg:test.py
import unittest
class MyTest(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
pass
@unittest.skip("直接跳过测试")
def test_skip(self):
print("test_aaa")
@unittest.skipIf(3>2, "当条件为True时跳过测试")
def test_skip_if(self):
print("test_bbb")
@unittest.skipUnless(3>2, "除非条件为真,否则跳过测试")
def test_skip_unless(self):
print("test_ccc")
@unittest.expectedFailure
def test_expected_failure(self):
assertEqual(2, 3)
if __name__ == '__main__':
unittest.main()
# 注:这些装饰器同样可以作用于测试类,只需将其定义在测试类上面即可。
import unittest
@unittest.skip("直接跳过测试该测试类")
class Mytest(unittest.TestCase):
......
9、数据驱动DDT+Excel+yaml
- 数据驱动和关键驱动的区别:
- Data-Driven Tests(DDT)即数据驱动测试,可以实现不同数据运行同一个测试用例。ddt本质其实就是装饰器,一组数据一个场景。
- 关键字驱动(核心:把业务逻相封装成关键字login,只需要调用login。)
- 混合驱动模式(关键字驱动+数据驱动)
- 在进行数据驱动测试实战中,需要在测试类上使用@ddt.ddt装饰器,在测试用例上使用@ddt.data装饰器。@ddt.data装饰器可以把参数作为测试数据,参数可以是单个值、列表、元组或字典。对于列表和元组,需要使用@ddt.unpack装饰器把元组和列表解析成多个参数。
在python里面装饰器是以@开头,并且装饰有两种:类装饰器,函数装饰器。
ddt里面有哪些装饰器:
-
- @ddt(类装饰器,申明当前类使用ddt框架)
- @data(函数装饰器,用于给测试用例传递数据),支持传python所有数据类型:数字(int,long,float,compix),字符串,列表1ist,元祖tuple,集合
-
-
- 当传单个值时,所有的类型都可以传,并且用例执行一次。
- 当传入多个值的时候,那么传几个值,那么用例执行几次。
-
-
- @unpack(函致装饰器,将传输的数据包解包),一般作用于元祖tuple和列表list.
-
-
- 如果是数字或者字符串:那么不需要@unpack
- 如果是元祖和列表的话,那么可以通过@unpack,但是参数的个数必须和解完包后的值的个数一样
- 如果是集合无法解包:参考上方例子(需要多个值来传)
- 如果是字典,那么可以通过@unpack解包,但是参数的名字和个数必须和字典的键保持一致
-
-
- @fle_data(函数装饰器,可直接读取yaml/json文件)
- @data([a,d],[c,d])
- 如果没有@unpack,那么[a,b]当成一个参数传入用例运行
- 如果有@unpack,那么[a,b]被分解开,按照用例中的两个参数传递
##2.12.2 DDT在自动化测试中的应用(传列表)
import ddt
import unittest
# 给4条测试数据
Testdata = [
{"username": "admin", "password": "123456", "excepted": {'code': '200', 'msg': '登录成功'}},
{"username": None, "password": "1234567", "excepted": {'code': '400', 'msg': '用户名或密码不能为空'}},
{"username": "admin", "password": None, "excepted": {'code': '400', 'msg': '用户名或密码不能为空'}},
{"username": "admin", "password": "123456789", "excepted": {'code': '404', 'msg': '用户名或密码错误'}},
]
@ddt.ddt
class TestModules(unittest.TestCase):
def setUp(self):
print('testcase beaning....')
def tearDown(self):
print('testcase ending.....')
@ddt.data(*Data)
def test_DataDriver(self,Data):
#print('DDT数据驱动实战演示:',Testdata)
res = login.login_check(Testdata['username'], Testdata['password'])
self.assertEqual(res, Testdata['excepted'])
if __name__ == '__main__':
unittest.main()
-----------------------------
import ddt
import unittest
# 给4条测试数据
def readData():
Testdata = [
{"username": "admin", "password": "123456", "excepted": {'code': '200', 'msg': '登录成功'}},
{"username": None, "password": "1234567", "excepted": {'code': '400', 'msg': '用户名或密码不能为空'}},
{"username": "admin", "password": None, "excepted": {'code': '400', 'msg': '用户名或密码不能为空'}},
{"username": "admin", "password": "123456789", "excepted": {'code': '404', 'msg': '用户名或密码错误'}},
]
return TestData
@ddt.ddt
class TestModules(unittest.TestCase):
def setUp(self):
print('testcase beaning....')
def tearDown(self):
print('testcase ending.....')
@ddt.data(*readData())
def test_DataDriver(self,Data):
#print('DDT数据驱动实战演示:',Testdata)
res = login.login_check(Testdata['username'], Testdata['password'])
self.assertEqual(res, Testdata['excepted'])
if __name__ == '__main__':
unittest.main()
DDT+Excel表格自动化使用
- 使用xlsx测试思路
首先安装xlrd,pip install xlrd
-
- 创建一个xlsx文件,data.xlsx
- 导入os、unittest、HTMLTestRunner、ddt、selenium
- 使用@ddt在类名上
- 编写登录用例def test_jenkins login(self):
- 编写read_excel()函数读取excel
- 使用@data(*read excel())传值,获取到excel的内容
- 使用@unpack解包,获取各种值
注意:
@data(read excel())打印的结果是[[1,‘admin',123456],[2,'admin",'admin],[3,123456,123456]]会带多一个[],@data(*read excel())带个*号则解决[]问题,能执行三次
###2.12.4 Excel整合DDT自动化测试实战(这种方式是遍历表格打印)###2.12.3 Excel自动化测试实战(这种方式是一个个行读取) import xlrd import ddt,unittest from time import sleep from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait #导入WebDriverWait类 from selenium.webdriver.support import expected_conditions as EC # 导入EC模块 def readUserName(row): '''读取用户名''' book = xlrd.open_workbook('datainfo.xlsx','r') table = book.sheet_by_index(0) return table.row_values(row)[0] def readPasswd(row): '''读取用户名''' book = xlrd.open_workbook('datainfo.xlsx','r') table = book.sheet_by_index(0) return table.row_values(row)[1] def readAssertText(row): '''读取预期结果''' book = xlrd.open_workbook('datainfo.xlsx','r') table = book.sheet_by_index(0) return table.row_values(row)[2] class TestSouHuLogin(unittest.TestCase): def setUp(self): self.driver = webdriver.Chrome() self.testUrl = "https://mail.sohu.com/fe/#/login" def tearDown(self): self.driver.quit() def by_css(self,usernameloc): '''重写css定位''' return self.driver.find_element_by_css_selector(usernameloc) def getassertText(self): '''获取验证信息''' try: sleep(2) loctor = (By.CSS_SELECTOR,'.tipHolder.ng-binding') WebDriverWait(self.driver, 5, 0.5).until(EC.presence_of_element_located((loctor))) return self.by_css('.tipHolder.ng-binding').text except Exception as message: print('元素定位报错!报错原因是:{}'.format(message)) def souhuLogin(self,user,passwd): '''封装登录功能''' self.by_css('.addFocus.ipt-account.ng-pristine.ng-valid').send_keys(user) self.by_css('.addFocus.ng-pristine.ng-valid').send_keys(passwd) self.by_css('.btn-login.fontFamily').click() def test_souHuLogin_001(self): '''账号和密码为空:登录失败''' self.driver.get(self.testUrl) sleep(3) self.souhuLogin(readUserName(1),readPasswd(1)) self.assertEqual(self.getassertText(), readAssertText(1)) def test_souHuLogin_002(self): '''账号正确和密码为空:登录失败''' self.driver.get(self.testUrl) self.souhuLogin(readUserName(2), readPasswd(2)) self.assertEqual(self.getassertText(), readAssertText(2)) def test_souHuLogin_003(self): '''账号错误和密码为空:登录失败''' self.driver.get(self.testUrl) self.souhuLogin(readUserName(3), readPasswd(3)) self.assertEqual(self.getassertText(), readAssertText(3)) def test_souHuLogin_004(self): '''账号为空和密码正确:登录失败''' self.driver.get(self.testUrl) self.souhuLogin(readUserName(4), readPasswd(4)) self.assertEqual(self.getassertText(), readAssertText(4)) if __name__ == '__main__': unittest.main()
'''方法一思路: 引入ddt模块,在测试类上增加@ddt.ddt装饰器,在测试用例上增加@ddt.data、@ddt.unpack方法。其中@ddt.data 方法用于把newRows 参数作为测试数据,newRows 参数实际是一个嵌套的列表;@ddt.unpack方法用于把测试数据分解为多个值并且作为实际参数传入测试用例中的 user、passwd和 text中。getassertText()方法用来获取页面实际文本信息,然后与text预期文本进行比较,验证信息一致则通过。 ''' import xlrd import ddt,unittest from time import sleep from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait #导入WebDriverWait类 from selenium.webdriver.support import expected_conditions as EC #导入EC模块 ''' readData()方法用于读取datainfo.xlsx表中每一行的用户名、密码和文本信息,然后将读取结果分别依次追加到newRows。 ''' def readData(): book = xlrd.open_workbook('datainfo.xlsx','r') #读取datainfo.xlsx表 table = book.sheet_by_index(0) #获取第一个sheet newRows=[] #传所有的行和列,则所有数据会打印成一行 for rowValue in range(1,table.nrows): newRows.append(table.row_values(rowValue,0,table.ncols)) return newRows #返回新的newRows @ddt.ddt class TestLogin(unittest.TestCase): def setUp(self): self.driver = webdriver.Chrome() self.testUrl = "https://mail.sohu.com/fe/#/login" def tearDown(self): self.driver.quit() def by_css(self,usernameloc): '''重写css定位''' return self.driver.find_element_by_css_selector(usernameloc) def getassertText(self): '''获取验证信息''' try: sleep(2) loctor = (By.CSS_SELECTOR,'.tipHolder.ng-binding') WebDriverWait(self.driver, 5, 0.5).until(EC.presence_of_element_located((loctor))) return self.by_css('.tipHolder.ng-binding').text except Exception as message: print('元素定位报错!报错原因是:{}'.format(message)) @ddt.data(*readData()) @ddt.unpack def test_souhuLogin(self,user,passwd,text): self.driver.get(self.testUrl) sleep(3) self.by_css('.addFocus.ipt-account.ng-pristine.ng-valid').send_keys(user) self.by_css('.addFocus.ng-pristine.ng-valid').send_keys(passwd) self.by_css('.btn-login.fontFamily').click() self.assertEqual(self.getassertText(),text) if __name__ == '__main__': unittest.main() 方式二:这种方式读取表格时使用了读取所有行和列,验证2种方式其中哪种适合 import os import unittest from HTMLTestRunner import HTMLTestRunner from ddt import ddt,data,unpack from selenium import webdriver #读取exce1 #@data([”百里",“微微“],[“18",“21]) #x1rd,x1wt,openpyx1 def read_excel(): workbook=openpyxl.load_workbook("data.x1sx") sheet=workbook["login"] #print(sheet.max_row,sheet.max_column) allList=[]#传所有的行和列,则所有数据会打印成一行 for row in range(2,sheet.max row+1):#行数 ,row+1才能获取下一行 tempList=[] #传所有的行和列,但是数据会分行打印 for col in range(1,sheet.max column+1): tempList.append(sheet.ce11(row,col).value) #print(tempList) allList.append(tempList) #print(allList) return allList @ddt class Test(unittest.Testcase): @data(*read_excel()) @unpack def test_jenkins login(self,order,username,password): #print(args) driver=webdriver.Chrome() driver.get("htto://1ocalhost:8080/1ogin?from=82E") driver.find element by id("j username").send keys(username) driver.find element by name("j password").send keys(password) driver.find_element _by_name("Submit").click() #此处是只是传具体值 #@data(*read excel()) #def test_jenkins login(self,args): # #print(args) # driver=webdriver.Chrome() #driver.get("htto://1ocalhost:8080/1ogin?from=82E") #driver.find element by id("j username").send keys("admin") #driver.find element by name("j password").send keys("123456") #driver.find_element _by_name("Submit").click() #if __name__ =='__main__': # unittest.main() #使用html方式打印报告 if __name__ =='__main__': testcases=unittest.defaultrestLoader.discover (os.getcwd(),"*.py") filename=open(os.getcwd()+"/report.html","wb") runner=HTMLTestRunner(stream=filename,verbosity=2,title="自动化测试报告 ",description="报告详细如下:") runner.run(testcases)
DDT+Yaml自动化使用
import yaml import unittest from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from ddt import ddt, data, unpack, file_data from HTMLTestRunner import HTMLTestRunner import os import yaml import time @ddt class TestCase(unittest.TestCase): # 测试用例 @file_data("./data/test.yaml") def test_search(self,txt): self.driver = webdriver.Chrome() self.driver.get('https://www.baidu.com/') self.driver.find_element(By.ID,'kw').send_keys(txt) self.driver.find_element(By.ID, 'su').click() if __name__=="__main__": # 实例化测试套件 suite = unittest.TestSuite() # 加载测试用例 #testcases=unittest.defaultTestLoader.discover(os.getcwd(),"*.py") testcases=[TestCase("test_search")] suite.addTests(testcases) # 获取当前时间 now = time.strftime("%Y-%m-%d %H_%M_%S") # 定义报告存放路径 filename = now+open(r'D:\workspace\seleniumtest'+ "/report.html", "wb") # 定义测试报告 runner = HTMLTestRunner(stream=filename, verbosity=2, title="自动化测试报告 ",description = "报告详细如下:") runner.run(suite) filename.close()# 关闭报告文件
Yaml自动化使用
YAML是一种直观的能够被计算机识别的数据序列化格式,容易阅读,并且容易和脚本语言交互。YAML类似于XML,但是语法比XML简单得多:而对于JSON,YAML可以写成规范化的配置文件。此外,不管是做Web自动化测试还是接口自动化测试,都可以使用YAML来管理测试数据,这种方法也比较简单高效。
在线写yaml文件:YAML、YML在线编辑器(格式化校验)-BeJSON.com
使用基本规则
1. 大小写敏感
2. 使用缩进表示层级关系
3. 缩进时不允许使用Tab,只允许使用空格
4. 缩进的空格数目不重要,只要相同层级的元素左对齐即可
5. # 表示注释,从它开始到行尾都被忽略
实现思路:
1、YAML的安装非常简单,可以直接在线使用pip命令进行安装。打开cmd命令提示符界面,输入“pip install pyyaml”进行在线安装
2、YAML在自动化测试中使用data.yaml文件存储的数据名必须是以.yaml结尾。
3、导入YAML模块import yaml,readYaml()方法下的open()方法用于打开data.yaml文件,为了防止乱码可使用encoding="utf-8"来声明。yaml.load()方法用于读取data.yaml文件下的所有数据。close()方法用于关闭整个yaml文件,如果不关闭会产生ResourceWarning提示
注意:
- 通过yaml自身的函数形态来读取单个内容:yaml.load(stream=file,Loader=yaml.FullLoader)
- 通过yaml自身的函数形态来读取多个内容:yaml.loadAll(stream=file,Loader=yaml.FullLoader)
data.yaml
userNull: username:"" password:"" assertText:"请输入账号密码" passwordNull: username:"admin" password:"" assertText:"请输入账号密码"
####2.12.5 YAML自动化测试实战 import unittest,yaml from time import sleep from selenium import webdriver def readYaml(): '''获取所有yaml所有数据''' f = open('./data/data.yaml', 'r', encoding='utf-8') data = yaml.load(f) #print(data) f.close() return data class TestLogin(unittest.TestCase): def setUp(self): self.driver = webdriver.Chrome() self.testUrl = "https://mail.sohu.com/fe/#/login" def tearDown(self): self.driver.quit() def by_css(self,usernameloc): '''重写css定位''' return self.driver.find_element_by_css_selector(usernameloc) def getassertText(self): '''获取验证信息''' try: return self.by_css('.tipHolder.ng-binding').text except Exception as message: print('元素定位报错!报错原因是:{}'.format(message)) def souhuLogin(self,user,passwd): '''封装登录功能''' self.by_css('.addFocus.ipt-account.ng-pristine.ng-valid').send_keys(user) self.by_css('.addFocus.ng-pristine.ng-valid').send_keys(passwd) self.by_css('.btn-login.fontFamily').click() def test_souHuLogin_001(self): '''账号正确和密码为空:登录失败''' self.driver.get(self.testUrl) sleep(3) self.souhuLogin(readYaml()['userNull']['username'],readYaml()['userNull']['password']) self.assertEqual(self.getassertText(), readYaml()['userNull']['assertText']) def test_souHuLogin_002(self): '''账号错误和密码为空:登录失败''' self.driver.get(self.testUrl) sleep(3) self.souhuLogin(readYaml()['passNull']['username1'],readYaml()['passNull']['password1']) self.assertEqual(self.getassertText(), readYaml()['passNull']['assertText1']) if __name__ == '__main__': unittest.main()
https://www.yuque.com/mengxiaoqi-lrqrz/ylw9z8/hft8wa
10、生成HTML测试报告
1、下载文件:
下载地址:HTMLTestRunner - tungwaiyip's software ,右键HTMLTestRunner.py文件另存为即可。
下载后放到python安装目录/Lib下,如我的路径为:C:\Users\XXX\AppData\Local\Programs\Python\Python36\Lib
2、修改的内容如下
第94行, 将import StringIO修改成import io 第539行,将self.outputBuffer = StringIO.StringIO()修改成self.outputBuffer = io.StringIO() 第642行,将if not rmap.has_key(cls):修改成if not cls in rmap: 第631行,将print >> sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime)修改成print(sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime)) 第766行,将uo = o.decode('latin-1')修改成uo = e 第775行,将ue = e.decode('latin-1')修改成ue = e
3、使用HTMLTestRunner
方法一、使用TestLoader加载所有用例HTMLTestRunner(使用html报告运行所有测试用例)
- 导入os、unittest、HTMLTestRunner
- 创建Testsuite获取testcase下面所有测试用例,discover()方法会自动根据测
- 创建一个filename变量,使用open()方法定义测试报告文件存放路径,创建并打开测试报告文件
- 定义一个runner变量定义测试报告,并用runner()方法执行集中的用例,保存到html报告
HTMLTestRunner()参数:
-
- stream:默认值是sys.stderr,表示默认将结果输出到控制台,可以配置报告路径(但是要先用open()方法打开文件,是以一种文件流的方式),输出到指定位置;
- verbosity是一个选项,表示测试结果的信息复杂度,有0、1、2三个值。verbosity=0 : 你只能获得测试用例数总的结果;verbosity=1(默认模式):在每个成功的用例前面有 个“.”每个失败的用例前面有个“F”; verbosity=2 (详细模式):测试结果会显示每个测试用例的所有相关的信息。
- title:标题
- descriptions:默认值为True,描述内容
不常用参数
-
- output:是输出的文件夹,如果没有定义日志存放位置,日志报告会以html为后缀自动放在“工程目录/reports/file_d”这个路径的文件夹下,并以当前时间命名
- combine_reports:默认值为False,将值设置为True可将测报告合并
- report_name:报告名称,report_name会自动加上时间后缀,时间格式为%Y-%m-%d-%H-%M-%S
解析:fp=open(获取当前路径,'存放报告地址,当前时间+ 'report.html'),'wb')
方式一:定义一个方法【常用】封装 '''getAIlCasesO方法下的 discoverO函数用于读取TestCases目录下以 test 开头的·py文件,并这 回所有测试模块下的测试用例。RunMain()方法用于生成测试报告,并将测试结果写入测试报告中。 wb模式用于读取二进制文件。time.strfimeO方法用于获取系统的当前时间,以便区分生成的不同 测试报告名。 ''' import os,time,unittest from HTMLTestRunner import HTMLTestRunner def getAllCases(): '''获取tesTcase下面的所有测试模块''' start_dir=os.path.join(os.path.dirname(__file__) Testsuite = unittest.defaultTestLoader.discover(start_dir,'TestCases'), pattern='test*.py') return Testsuite def RunMain(): '''生成测试报告写入指定Reports目录''' fp=open(os.path.join(os.path.dirname(__file__),'report',time.strftime("%Y_%m_%d_%H_%M_%S")+ 'report.html'),'wb') HTMLTestRunner(stream=fp,title='Python+Selenium自动化测试实战', description='基于python语言PO自动化测试').run(getAllCases()) if __name__ == '__main__': RunMain() 方式二: import unittest from HTMLTestRunner import HTMLTestRunner import time if __name__ == '__main__': #指定批量执行的模块 test_module = './' discover = unittest.defaultTestLoader.discover(test_module, pattern="test*.py") #报告存放的文件夹 dir_path='./' #报告命名加上时间格式化 now=time.strftime('%Y-%m-%d %H_%M_%S') #报告绝对路径 report_path=dir_path +now +' result.html' #打开文件,写入测试结果 with open(report_path,'wb') as f: runner=HTMLTestRunner(stream=f,verbosity=2,title='Math测试报告',description='用例执行详细信息') runner.run(discover) f.close()
方法二、添加测试集使用HTMLTestRunner(使用html报告运行所有测试用例)
- 导入os、unittest、HTMLTestRunner
- 创建测试集,向测试集组装测试用例:
-
- 在【if _name_主模块】下先创建一个测试集suite(记得引用unittest+测试类模块)
- 创建一个数组【把所有用例按照顺序添加到数组】,【添加方式:测试类类名(测试方法名)】(最后执行,按照添加的顺序执行)
- 使用addTests()添加数组到测试集
- 创建一个filename变量,使用open()方法定义测试报告文件存放路径,创建并打开测试报告文件
- 定义一个runner变量定义测试报告,并用runner()方法执行集中的用例,保存到html报告
HTMLTestRunner()参数:
-
- stream:默认值是sys.stderr,表示默认将结果输出到控制台,可以配置报告路径(但是要先用open()方法打开文件,是以一种文件流的方式),输出到指定位置;
- verbosity:=1时,默认值为1,不限制完整结果,即单个用例成功输出’.’,失败输出’F’,错误输出’E’;=0的时候,不输出信息;=2的时候,需要打印详细的返回信息;
- title:标题
- descriptions:默认值为True,描述内容
不常用参数
-
- output:是输出的文件夹,如果没有定义日志存放位置,日志报告会以html为后缀自动放在“工程目录/reports/file_d”这个路径的文件夹下,并以当前时间命名
- combine_reports:默认值为False,将值设置为True可将测报告合并
- report_name:报告名称,report_name会自动加上时间后缀,时间格式为%Y-%m-%d-%H-%M-%S
使每次生成的测试报告名称都不重复且有意义,最好的办法是在报告名称中加入当前时间,这样生成的报告既不会重叠,也能更清晰的知道报告的生成时间。
import time :导入time模块
- time.time():获取当前时间戳
- time.ctime():当前时间的字符串形式
- time.localtime():当前时间的struct_time形式
- time.strftime:用来获得当前时间,可将时间格式化为字符串。
import os import unittest from HTMLTestRunner import HTMLTestRunner class Test(unittest.Testcase): def test01_baili(self): print(“测试百里") if __name__=="__main__": #1.创建用例集 suite = unittest.TestSuite() #testcases=unittest.defaultTestLoader.discover(os.getcwd(),"*.py") #2.定义数组变量 testcases=[RunCase("test_01_login"),RunCase("test_02_login")] #3.使用addTests添加数组变量 suite.addTests(testcases) #报告存放的文件夹 dir_path=r'D:\workspace\seleniumtest' #dir_path=os.path.join(os.path.dirname(__file__) #报告命名加上时间格式化 nowtime=time.strftime("%Y-%m-%d %H-%M-%S",time.localtime()) #报告绝对路径,打开文件,写入测试结果 filename = open(dir_path+ nowtime+"/report.html", "wb") runner = HTMLTestRunner(stream=filename, verbosity=2, title="自动化测试报告 ",description = "报告详细如下:") runner.run(suite) #写入文件完成后,关闭文件 filename.close()
生成的测试报告如下:
9-Unittest+HTMLTestRunner不能生成报告解决方法
https://www.cnblogs.com/santiandayu/p/10004179.html
参考:
unittest + HTMLTestRunner_unittest testrunner_你们的好朋友大强的博客-CSDN博客
11、发送邮件实战
1、SMTP
###2.13.1 纯文本的邮件实战 import smtplib #调用smtp发件服务 from email.mime.text import MIMEText #导入做纯文本的邮件模板类 smtpsever='smtp.qq.com' #QQ邮箱服务器 sender='qq邮箱账号@qq.com' #发送者邮箱 psw="hcygozfxeassddhhb" #配置邮箱客户端生成的QQ邮箱授权码 receiver='126邮箱账号@126.com' #接收者邮箱 port=465 #QQ邮箱服务器默认端口号 msg=MIMEText(body,'html','utf-8') #邮件正文内容 msg['from']=qq邮箱账号@qq.com' #发送者账号 msg['to']='126邮箱账号@qq.com' #接收者账号 msg['subject'] = "这个是纯文本发送的邮件示例" smtp = smtplib.SMTP_SSL(smtpsever,port) #调用发件服务 smtp.login(sender,psw) #通过发送者的邮箱账号和授权码登录邮箱 smtp.sendmail(sender,receiver,msg.as_string()) #发送邮件,信息以字符串方式保存 smtp.quit() #关闭邮件服务 ###2.13.2 带附件的邮件实战 import smtplib from email.mime.text import MIMEText #导入做纯文本的邮件模板类 from email.mime.multipart import MIMEMultipart #导入MIMEMultipart类 #发邮件相关参数 smtpsever='smtp.qq.com' #QQ邮箱服务器 sender='239xxxxx@qq.com' #发送者邮箱 psw="xxxxxxxxxxxxxxxx" #qq邮箱授权码 receiver='xxxxx@126.com' #接收者邮箱账号 port=465 #QQ邮箱服务器默认端口号 filepath=r"./readme.txt" #编辑邮件的内容 with open(filepath,'rb') as fp: #读文件 mail_body=fp.read() #主题 msg=MIMEMultipart() msg["from"]=sender msg["to"]=receiver msg["subject"]="带附件的邮件发送模版主题" body=MIMEText(mail_body,"html","utf-8") msg.attach(body) att = MIMEText(mail_body,"base64","utf-8") att["Content-Type"] = "application/octet-stream" att["Content-Disposition"] = 'attachment; filename="test_report.html"' msg.attach(att) try: smtp=smtplib.SMTP() smtp.connect(smtpsever) #连接QQ邮箱服务器 smtp.login(sender,psw) #调用发件服务 except: smtp=smtplib.SMTP_SSL(smtpsever,port) smtp.login(sender,psw) #登录邮箱 smtp.sendmail(sender,receiver,msg.as_string()) #发送邮件 smtp.quit()
12、持续集成
- 安装jenkins
- 构建一个自由风格的任务
- 点击构建-基于window环境-选择Execute Windows batch command+填写命令:
D:
cd D:\workspace
C:\python\python.exe allTests.py;
保存,配置完成;
- 点击项目-Build now-构建成功,查看终端日志
- 使用HTML Publisher plugin输出报告或者Allure输出报告(参考步骤8)
13、框架:测试框架封装和脚本的分层设计
实现思路:
- 使用POM模式封装测试框架:封装页面对象、基础层,测试用例,定义common封装公共方法包括截图、读取xlxs表格、日志,然后在写测试用例的时候可以调用截图、日志、用例等,最后runAllCases.py使用TestLoader类获取所有用例和使用HTMLTestRunner执行用例生成报告。
- 上传代码到GIT:git add * , git commit -m '提交', git push origin test
- 持续集成jenkins执行用例:配置源码管理git、window执行python runAllCases.py【执行方式:每日定时执行、发版前执行、冒烟测试执行(提测前执行)】,节点电脑接上手机