UnitTest单元测试框架/数据驱动实战
一、简介:
在Python中,单元测试框架主要是unittest,单元测试是最基本也是最底层的测试类型,单元测试应用于最基本的软件代码,如类,函数,方法等。
维度:1、表单的错误提示信息验证
2、产品的业务逻辑
3、页面的各种交互
完整的自动化测试用例步骤:
1、初始化
2、测试步骤
3、断言
4、清理
二、UnitTest测试框架
3)测试套件:TestSuite在一个测试套件里面可以包含很多的测试用例,可以把它理解为一个容器
4)测试执行:TestRunner:测试执行指的是针对测试套件或者是测试用例进行执行的过程
5)测试报告:TestReport:所有的测试测试用例执行完成后输出的汇总结果报告信息
注意:在测试类里面编写的测试方法必须是test开头的
在unittest中测试固件依据方法可以分为两种执行方式:
第一种是测试固件只执行一次,它的缺点是:执行完一个测试用例后,需要回归到页面的初始化,一般不建议使用。
第二种是测试固件每次都执行,下面依据具体的案例来讲解二者。
这里以登录新浪邮箱为例:测试固件每次都执行
import unittest from selenium import webdriver import time as t class SinaTest(unittest.TestCase): def setUp(self)->None: self.driver=webdriver.Chrome() self.driver.maximize_window() self.driver.get('http://mail.sina.com.cn/') self.driver.implicitly_wait(30) def tearDown(self)->None: self.driver.quit() def test_login_null(self): """登录:验证账户密码为空的错误提示信息""" self.driver.find_element_by_id('freename').send_keys('') self.driver.find_element_by_id('freepassword').send_keys('') self.driver.find_element_by_class_name('loginBtn').click() t.sleep(3) divText=self.driver.find_element_by_xpath('/html/body/div[3]/div/div[2]/div/div/div[4]/div[1]/div[1]/div[1]/span[1]') #assertEqua比较两个对象是否相等, self.assertEqual(divText.text,'请输入邮箱名') def test_login_email_format(self): """登录:验证账户格式不规范的错误提示信息""" self.driver.find_element_by_id('freename').send_keys('hdsuch') self.driver.find_element_by_id('freepassword').send_keys('naluhc') self.driver.find_element_by_class_name('loginBtn').click() t.sleep(3) divText=self.driver.find_element_by_xpath('/html/body/div[3]/div/div[2]/div/div/div[4]/div[1]/div[1]/div[1]/span[1]') #assertEqua比较两个对象是否相等, self.assertEqual(divText.text,'您输入的邮箱名格式不正确') if __name__=='__main__': unittest.main()
2.2.1分离测试固件
针对测试固件进行分离,在page包下创建init.py文件,来具体分离我们的测试固件
#分离固件 import unittest from selenium import webdriver class Init(unittest.TestCase): def setUp(self)->None: self.driver=webdriver.Chrome() self.driver.maximize_window() self.driver.get('http://mail.sina.com.cn/') self.driver.implicitly_wait(30) def tearDown(self)->None: self.driver.quit()
2.3 四个断言的方法:
1.assertTrue 断言是否为真
2.assert in 比较一个对象是否包含另外一个对象
4.assertEqual 比较两个对象是否相等
在unittest中,测试点的执行顺序是依据ascill码来执行的,也就是说根据ASCILL码的顺序加载,数字与字母的顺序为:0-9,A-Z,a-z,所以以A开头的测试用例方法会优先执行,以a开头会后执行。也就是根据数字的大小从小到 大执行的,切记数字的大小值的是不包含test,值的是test后面的测试点的数字大小。
1、在一个测试类里面,每一个测试方法都是以test开头的,test不能是中间或者尾部,必须是开头,建议test_
2、每一个测试用例方法都应该有注释信息,这样在测试报告就会显示具体的测试点的检查点
3、在自动化测试中,每个测试用例都必须得有断言,无断言的自动化测试用例是无效的
4、最好一个测试用例方法对应一个业务测试点,不要多个业务检查点写一个测试用例
5、如果涉及到业务逻辑的处理,最好把业务逻辑的处理方法放在断言前面,这样做的目的是不要因为业务逻辑执 行错误导致断言也是失败
6、测试用例名称最好规范,有约束
7、是否先写自动化测试的测试代码,在使用自动化测试方式写,本人觉得没必要,毕竟能够做自动化测试的都具 备了功能测试的基本水平,所以没必要把一个业务的检查点写多次,浪费时间和人力成本。
1)先梳理哪些模块可以做自动化测试
2)梳理完成后,和相关的人建议对下计划以及梳理的结果
3)编写代码实现梳理的测试模块
4)编写完成后相关的人进行评审代码(测试场景是否考虑周全,测试断言是否合理,代码编写的是否合理)
5)编写完成后,整合到JeKins的持续集成平台
6)下个迭代的时候,直接可以应用于产品的回归测试中
2.7.1按测试类执行
可以理解为在测试套件中,我们按测试类的方式来执行,测试类里面有多少的测试用例,我们都会执行
2.7.2按测试模块执行
按测试模块来执行,就是以模块为单位来进行执行,那么其实在一个模块里面可以编写很多的类
2.7.3 按具体的测试用例来执行
当然如果是仅仅执行某一个测试用例,执行的方式一种是鼠标放到具体的测试用例,然后右键执行就可以了
import unittest from selenium import webdriver import time as t class BaiduTest(unittest.TestCase): def setUp(self)->None: self.driver=webdriver.Chrome() self.driver.maximize_window() self.driver.get('http://mail.sina.com.cn/') self.driver.implicitly_wait(30) def tearDown(self)->None: self.driver.quit() def test_login_email_format(self): """登录:验证账户格式不规范的错误提示信息""" self.driver.find_element_by_id('freename').send_keys('hdsuch') self.driver.find_element_by_id('freepassword').send_keys('naluhc') self.driver.find_element_by_class_name('loginBtn').click() t.sleep(3) divText=self.driver.find_element_by_xpath('/html/body/div[3]/div/div[2]/div/div/div[4]/div[1]/div[1]/div[1]/span[1]') #assertEqua比较两个对象是否相等, self.assertEqual(divText.text,'您输入的邮箱名格式不正确') #assertIn比较一个对象是否包含另外一个对象,in self.assertIn(divText.text,'您输入的邮箱名格式不正确') def test_login_isButton(self): """登录:验证自动登录是否默认勾选""" divText=self.driver.find_element_by_id('store1') #针对bool的类型验证assertTrue self.assertTrue(divText.is_selected()) if __name__=='__main__': # 按测试模块来执行 #suite = unittest.TestLoader().loadTestsFromModule(module='test_sina.py')括号里写模块名 #按测试类来执行 suite=unittest.TestLoader().loadTestsFromTestCase(testCaseClass='SinaTest')括号里写类名 unittest.TextTestRunner().run(suite)
2.8加载所有的测试模块(重点)
import unittest import os if __name__=='__main__': #加载所有的测试模块来执行 #start_dir:测试模块的路径 #pattern:通过正则的方式加载所有的测试模块 suite=unittest.TestLoader().discover( start_dir=os.path.dirname(__file__), pattern='test_*.py') unittest.TextTestRunner().run(suite)
2.9unittest之参数化
首先需要安装一个第三方的库parameterized,安装的命令为:
pip3 install parameterized
本质思想:把测试的数据看成列表当中的一个元素, 那么针对列表进行循环的时候,把每个元素进行赋值
在UI自动化测试中,parameterized也是特别的有用,如针对一个登录案例的测试,针对登录就会有很多的测试案例,主要是用户名和密码的input表单的验证以及错误信息的验证,下面就结合具体的案例来看
以新浪邮箱登录为例:
class SinaTest(unittest.TestCase): def setUp(self)->None: self.driver=webdriver.Chrome() self.driver.maximize_window() self.driver.get('http://mail.sina.com.cn/') self.driver.implicitly_wait(30) def tearDown(self)->None: self.driver.quit() @parameterized.expand([ param('','','请输入邮箱名'), param('waertre','asewrae','您输入的邮箱名格式不正确'), param('1754788101@qq.com','123456','请输入正确的新浪邮箱帐号和密码') ]) def test_login(self,username,password,result): """登录:测试登录不同场景""" self.driver.find_element_by_id('freename').send_keys(username) self.driver.find_element_by_id('freepassword').send_keys(password) self.driver.find_element_by_class_name('loginBtn').click() t.sleep(3) divText=self.driver.find_element_by_xpath('/html/body/div[3]/div/div[2]/div/div/div[4]/div[1]/div[1]/div[1]/span[1]') #assertEqua比较两个对象是否相等, self.assertEqual(divText.text,result) if __name__ == '__main__': unittest.main()
unittest生成测试报告需要使用到第三方的库HTMLTestRunner,把该库放在Python安装目录下的lib目录下。
下面具体展示测试报告的生成,把测试报告存储到report的文件夹里面,思考到每次生成的测试报告名称一致,我们可以以当前时间作为区分
import unittest import HTMLTestRunner import os import time def getSuite(): #获取所有需要执行的测试模块 suite=unittest.TestLoader().discover( start_dir=os.path.dirname(__file__), pattern='test_*.py') return suite def getNowTime(): return time.strftime('%y-%m-%d %H_%M_%S',time.localtime(time.time())) def main(): filename=os.path.join(os.path.dirname(__file__),'report',getNowTime()+'reprot.html') fp=open(filename,'wb') runner=HTMLTestRunner.HTMLTestRunner( stream=fp, title='UI自动化测试报告', description='UI自动化测试报告' ) runner.run(getSuite()) if __name__=='__main__': main()
在report的文件夹下,生成的测试报告打开后显示如下:
三、UI自动化测试实战之数据驱动
在UI的自动化测试中,我们需要把测试使用到的数据分离到文件中,如果单纯的写在我们的测试模块里面,不是一个好的设计,所以不管是什么类型的自动化测试,都是需要把数据分离出来的。当然分离到具体的文件里面,文件的形式其实有很多的,这里主要说明JSON的文件和YAML的文件在UI自动化测试中的应用。
以下以JSON为例,注意,JSON里是{ },且都是双引号
完善后的测试脚本为
import unittest import time as t from selenium import webdriver import json from unittest测试框架.init import Init def readJson(): return json.load(open('sina.json',encoding='utf-8')) class SinaTest(Init): def test_login_null(self): """登录:验证账户密码为空的错误提示信息""" self.driver.find_element_by_id('freename').send_keys('') self.driver.find_element_by_id('freepassword').send_keys('') self.driver.find_element_by_class_name('loginBtn').click() t.sleep(3) divText=self.driver.find_element_by_xpath('/html/body/div[3]/div/div[2]/div/div/div[4]/div[1]/div[1]/div[1]/span[1]') #assertEqua比较两个对象是否相等, self.assertEqual(divText.text,readJson()['null']) def test_login_email_format(self): """登录:验证账户格式不规范的错误提示信息""" self.driver.find_element_by_id('freename').send_keys('hdsuch') self.driver.find_element_by_id('freepassword').send_keys('naluhc') self.driver.find_element_by_class_name('loginBtn').click() t.sleep(3) divText=self.driver.find_element_by_xpath('/html/body/div[3]/div/div[2]/div/div/div[4]/div[1]/div[1]/div[1]/span[1]') #assertEqua比较两个对象是否相等, self.assertEqual(divText.text,readJson()['format']) if __name__=='__main__': #按测试类来执行 suite=unittest.TestLoader().loadTestsFromTestCase(testCaseClass='SinaTest') unittest.TextTestRunner().run(suite)
3.2 YAML文件
下面我们演示把测试数据存储到YAML文件里面,注意:冒号后面要有空格,不需要加引号
分离出来的文件内容为:
import unittest import time as t from selenium import webdriver import json import yaml from unittest测试框架.init import Init def readYaml(): with open('sina.yaml',encoding='utf-8')as f: return yaml.safe_load(f) class SinaTest(Init): def test_login_null(self): """登录:验证账户密码为空的错误提示信息""" self.driver.find_element_by_id('freename').send_keys('') self.driver.find_element_by_id('freepassword').send_keys('') self.driver.find_element_by_class_name('loginBtn').click() t.sleep(3) divText=self.driver.find_element_by_xpath('/html/body/div[3]/div/div[2]/div/div/div[4]/div[1]/div[1]/div[1]/span[1]') #assertEqua比较两个对象是否相等, self.assertEqual(divText.text,readYaml()['login'][None]) def test_login_email_format(self): """登录:验证账户格式不规范的错误提示信息""" self.driver.find_element_by_id('freename').send_keys('hdsuch') self.driver.find_element_by_id('freepassword').send_keys('naluhc') self.driver.find_element_by_class_name('loginBtn').click() t.sleep(3) divText=self.driver.find_element_by_xpath('/html/body/div[3]/div/div[2]/div/div/div[4]/div[1]/div[1]/div[1]/span[1]') #assertEqua比较两个对象是否相等, self.assertEqual(divText.text,readYaml()['login']['format']) if __name__=='__main__': #按测试类来执行 suite=unittest.TestLoader().loadTestsFromTestCase(testCaseClass='SinaTest') unittest.TextTestRunner().run(suite)
实战:login_list.json
下面将测试用例使用参数化的方法
import unittest import time as t from selenium import webdriver from parameterized import parameterized,param import json from unittest测试框架.init import Init """参数化: 相同的测试步骤,不同的测试数据,那么这样的测试场景我们就可以使用 参数化的解决思路来解决。也就是说使用一个测试用例的代码,执行多个测试场景 参数化本质:针对测试数据进行循环,每次循环的时候对列表中元素的值一一赋值的过程""" def readJson(): return json.load(open('login_list.json',encoding='utf-8'))['login'] class SinaTest(Init): @parameterized.expand([ param(readJson()[0]['username'],readJson()[0]['password'],readJson()[0]['result']), param(readJson()[1]['username'],readJson()[1]['password'],readJson()[1]['result']), param(readJson()[2]['username'],readJson()[2]['password'],readJson()[2]['result']) ]) def test_login(self, username, password, result): # 登录:测试登录不同场景 self.driver.find_element_by_id('freename').send_keys(username) self.driver.find_element_by_id('freepassword').send_keys(password) self.driver.find_element_by_class_name('loginBtn').click() t.sleep(3) divText = self.driver.find_element_by_xpath( '/html/body/div[3]/div/div[2]/div/div/div[4]/div[1]/div[1]/div[1]/span[1]') # assertEqua比较两个对象是否相等, self.assertEqual(divText.text, result) if __name__ == '__main__': unittest.main()