单元测试框架
一.使用的库
在Python语?中应?最?泛的单元测试框架是unittest和pytest,unittest属于标准库,只要安装了Python解释器后就可以直接导?使?了,pytest是第三?的库,需要单独的安装。二.unittest标准库的使用
-
组件的介绍
-
-
测试用例(testCase):编写的每个具体的测试场景
-
每个测试用例必须是test开头,建议为test_
-
每个测试用例都应该有标题
-
每个测试用例建议只写一个测试场景
-
每个测试用例必须要有期望结果,也就是断言,否则就没有意义
-
每个测试用例都是独立的,不会因为业务的依赖关系而关系
-
-
测试固件(勾子方法):初始化和清理
-
测试套件(testSuite):测试套件是testCase的集合
-
测试执行(testRunner):具体执行被编写的测试套件或者是测试用例
-
测试报告(testReport):测试报告反馈实际的测试结果
-
-
自动化测试用例要素跟顺序:
-
前提条件/初始化:setUp
-
测试步骤
-
验证结果(断言)
-
清理:tearDown()
-
-
unittest属性
-
属性的查看
import unittest print(dir(unittest))
输出结果:
['BaseTestSuite', 'FunctionTestCase', 'IsolatedAsyncioTestCase', 'SkipTest', 'TestCase', 'TestLoader', 'TestProgram', 'TestResult', 'TestSuite', 'TextTestResult', 'TextTestRunner', '_TextTestResult', '__all__', '__builtins__', '__cached__', '__dir__', '__doc__', '__file__', '__getattr__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__unittest', 'addModuleCleanup', 'case', 'defaultTestLoader', 'expectedFailure', 'findTestCases', 'getTestCaseNames', 'installHandler', 'load_tests', 'loader', 'main', 'makeSuite', 'registerResult', 'removeHandler', 'removeResult', 'result', 'runner', 'signals', 'skip', 'skipIf', 'skipUnless', 'suite', 'util']
-
-
-
unittest.TestCase
import unittest class ApiTest(unittest.TestCase): def test_001(self): pass
TestCase类,所有测试用例类继承的基本类。
-
unittest.main()
if __name__ == '__main__': unittest.main()
可以方便的将一个单元测试模块变为可直接运行的测试脚本,main()方法使用TestLoader类来搜索所有包含在该模块中以“test”命名开头的测试方法,并自动执行他们。
在unittest的main中verbosity有三个参数,分别是0,1,2,代表的意思具体如下: 0(静默模式):仅仅获取总的测试?例数以及总的结果 1(默认模式) 2(详细模式):测试结果会显示每个测试?例的所有相关信息
-
setUp()
def setUp(self) -> None: self.driver=webdriver.Chrome() self.driver.maximize_window() self.driver.get('http://www.baidu.com') self.driver.implicitly_wait(10)
用于测试用例执行前的初始化工作,如测试用例需要登录web,可以先初始化浏览器。
-
tearDown()
def tearDown(self) -> None: self.driver.quit()
用于测试用例执行之后的善后工作,如关闭浏览器等
-
testLoder()
if __name__ == '__main__': suite = unittest.TestLoader().loadTestsFromModule('test_baidu2.py') unittest.TextTestRunner().run(suite)
用来加载 TestCase到TestSuite中,方法为:
def loadTestsFromTestCase(self, testCaseClass) def loadTestsFromModule(self, module, *args, pattern=None, **kws) def loadTestsFromName(self, name, module=None) def loadTestsFromNames(self, names, module=None) def getTestCaseNames(self, testCaseClass) def discover(self, start_dir, pattern='test*.py', top_level_dir=None) def _get_directory_containing_module(self, module_name) def _get_name_from_path(self, path) def _get_module_from_name(self, name) def _match_path(self, path, full_path, pattern) def _find_tests(self, start_dir, pattern, namespace=False) def _find_test_path(self, full_path, pattern, namespace=False)
-
TextTestRunner
unittest.TextTestRunner().run(suite)
按模块/类执行测试代码时需要使用到TextTestRunner 中的run 的方法去执行。
-
assert *():断言
assert:python解释器自带的断言,unittest中使用断言需要封装
assertEqual(a,b,[msg='测试失败时打印的信息']):断言a和b是否相等,相等则测试用例通过。 assertNotEqual(a,b,[msg='测试失败时打印的信息']):断言a和b是否相等,不相等则测试用例通过。 assertTrue(x,[msg='测试失败时打印的信息']):断言x是否True,是True则测试用例通过。 assertFalse(x,[msg='测试失败时打印的信息']):断言x是否False,是False则测试用例通过。 assertIs(a,b,[msg='测试失败时打印的信息']):断言a是否是b,是则测试用例通过。 assertNotIs(a,b,[msg='测试失败时打印的信息']):断言a是否是b,不是则测试用例通过。 assertIsNone(x,[msg='测试失败时打印的信息']):断言x是否None,是None则测试用例通过。 assertIsNotNone(x,[msg='测试失败时打印的信息']):断言x是否None,不是None则测试用例通过。 assertIn(a,b,[msg='测试失败时打印的信息']):断言a是否在b中,在b中则测试用例通过。 assertNotIn(a,b,[msg='测试失败时打印的信息']):断言a是否在b中,不在b中则测试用例通过。 assertIsInstance(a,b,[msg='测试失败时打印的信息']):断言a是是b的一个实例,是则测试用例通过。 assertNotIsInstance(a,b,[msg='测试失败时打印的信息']):断言a是是b的一个实例,不是则测试用例通过。
-
-
测试固件
-
只执行一次(类方法)
#! /usr/bin/env python # -*- coding:utf-8 -*- # author:特昂糖 from selenium import webdriver import unittest class BaiduTest(unittest.TestCase): #测试固件 @classmethod def setUpClass(cls) -> None: cls.driver=webdriver.Chrome() cls.driver.maximize_window() cls.driver.get('http://www.baidu.com') cls.driver.implicitly_wait(10) @classmethod def tearDownClass(cls) -> None: cls.driver.quit()
-
每次都执行
#! /usr/bin/env python # -*- coding:utf-8 -*- # author:特昂糖 from selenium import webdriver import unittest class BaiduTest(unittest.TestCase): def setUp(self) -> None: self.driver=webdriver.Chrome() self.driver.maximize_window() self.driver.get('http://www.baidu.com') self.driver.implicitly_wait(20) def tearDown(self) -> None: self.driver.quit()
代码执行顺序为:测试固件--->测试套件,测试固件--->测试套件
-
-
测试套件
-
测试套件的执行
-
按测试用例执行
-
按测试类执行
#! /usr/bin/env python # -*- coding:utf-8 -*- # author:特昂糖 from selenium import webdriver import unittest class BaiduTest(unittest.TestCase): def setUp(self) -> None: self.driver=webdriver.Chrome() self.driver.maximize_window() self.driver.get('http://www.baidu.com') self.driver.implicitly_wait(20) def tearDown(self) -> None: self.driver.quit() def test_baidu_title(self): '''验证百度首页的title''' self.assertEqual(self.driver.title,'百度一下,你就知道') def test_baidu_url(self): '''验证百度首页的URL''' self.driver.current_url=='https://www.baidu.com/' def test_baidu_video(self): '''验证点击视频之后跳转的页面''' nowhandler = self.driver.current_window_handle self.driver.find_element_by_link_text('视频').click() allhandlers = self.driver.window_handles for handler in allhandlers: if handler != nowhandler: self.driver.switch_to.window(handler) self.assertEqual(self.driver.current_url, 'https://haokan.baidu.com/?sfrom=baidu-top') def test_baidu_map(self): '''验证点击地图后跳转的页面''' nowhandler=self.driver.current_window_handle self.driver.find_element_by_link_text('地图').click() allhandlers=self.driver.window_handles for handler in allhandlers: if handler!=nowhandler: self.driver.switch_to.window(handler) self.assertTrue(self.driver.current_url.startswith('https://map.baidu')) def test_baidu_hao123(self): '''验证点击hao123后跳转的页面''' nowhandler=self.driver.current_window_handle self.driver.find_element_by_link_text('hao123').click() allhandlers=self.driver.window_handles for handler in allhandlers: if handler!=nowhandler: self.driver.switch_to.window(handler) self.assertEqual(self.driver.current_url,'https://www.hao123.com/') if __name__ == '__main__': suite=unittest.TestLoader().loadTestsFromTestCase(BaiduTest) unittest.TextTestRunner().run(suite)
-
按测试模块执行
import unittest import os def getTests(): '''加载路径下所有的测试模块''' suite=unittest.TestLoader().discover(start_dir=os.path.dirname(__file__),pattern='test_*.py') return suite def runSuite(): unittest.TextTestRunner().run(getTests()) if __name__ == '__main__': runSuite()
-
-
-
测试套件的优化
-
测试套件的分离
from selenium import webdriver import unittest class Init(unittest.TestCase): @classmethod def setUpClass(cls) -> None: cls.driver = webdriver.Chrome() cls.driver.maximize_window() cls.driver.get('http://www.baidu.com') cls.driver.implicitly_wait(10) @classmethod def tearDownClass(cls) -> None: cls.driver.quit()
把测试套件分离为一个方法,用的时候直接调用继承就可以了
-
源代码的继承
#! /usr/bin/env python # -*- coding:utf-8 -*- # author:特昂糖 from test.init import Init class BaiduTest(Init): def test_baidu_title(self): '''验证百度首页的title''' self.assertEqual(self.driver.title,'百度一下,你就知道') def test_baidu_url(self): '''验证百度首页的URL''' self.driver.current_url=='https://www.baidu.com/'
使用的方法是类继承,先导入方法,再继承
-
-
unittest的参数化
-
使用的库
在unittest测试框架中,参数化使用的库为:parameterized 安装方式为:pip3 install parameterized
-
解决的问题
参数化: 我们把相同的测试步骤,不同的测试场景,可以使用参数化解决的问题是可以使用少量的测试代码,来覆盖更多的测试场景
-
实战案例
以新浪邮箱的登录页面为例,我们需要测试不同的测试场景,如账号密码为空提示、邮箱号格式错误的提示、账号密码不符的错误提示,使用之前的方法,我们需要写三个测试用例,代码如下:
#! /usr/bin/env python # -*- coding:utf-8 -*- # author:特昂糖 import time from selenium import webdriver import unittest class SinaLogin(unittest.TestCase): def setUp(self) -> None: self.driver = webdriver.Chrome() self.driver.maximize_window() self.driver.get("https://mail.sina.com.cn/") self.driver.implicitly_wait(20) def tearDown(self) -> None: self.driver.quit() def test_sina_null(self): '''sina邮箱验证,登录账号秘密为空''' self.driver.find_element_by_class_name('loginBtn').click() 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]') self.assertEqual(divText.text,'请输入邮箱名') def test_sina_email_format(self): '''sina邮箱验证,登录邮箱格式不正确''' self.driver.find_element_by_id('freename').send_keys('jasbahc12') self.driver.find_element_by_class_name('loginBtn').click() 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]') self.assertEqual(divText.text,'您输入的邮箱名格式不正确') def test_sina_username_error(self): '''sina邮箱验证,账号密码不匹配''' self.driver.find_element_by_id('freename').send_keys('hc12@sina.cn') self.driver.find_element_by_id('freepassword').send_keys('jasbahc12') self.driver.find_element_by_class_name('loginBtn').click() time.sleep(1) 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]') self.assertEqual(divText.text,'登录名或密码错误') if __name__ == '__main__': unittest.main(verbosity=2)
针对以上的代码,我们可以使用参数化的方式把代码改良,只需要写一个测试用例就可以
import time from selenium import webdriver import unittest from parameterized import parameterized,param class SinaLogin(unittest.TestCase): def setUp(self) -> None: self.driver = webdriver.Chrome() self.driver.maximize_window() self.driver.get("https://mail.sina.com.cn/") self.driver.implicitly_wait(20) def tearDown(self) -> None: self.driver.quit() @parameterized.expand([ param('','','请输入邮箱名'), param('asqs121','','您输入的邮箱名格式不正确'), param('wxni121x@sina.cn','as12','登录名或密码错误')]) #参数化 def test_sina_Login(self,username,password,result): '''sina邮箱登录验证''' 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() time.sleep(1) 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]') self.assertEqual(divText.text,result) if __name__ == '__main__': unittest.main(verbosity=2)
-
-
unittest测试报告
- 运行test.py之后得到的测试结果无法直接提交,我们需要借助第三方的库生成HTML格式的测试报告,这里我们用到的是HTMLTestRunner
- 下载地址为:https://github.com/tungwaiyip/HTMLTestRunner
- 下载之后我们需要把这个文件放到Python安装路径的Lib文件夹下
- 测试报告运行代码如下:
#! /usr/bin/env python # -*- coding:utf-8 -*- # author:特昂糖 import os import time import unittest import HTMLTestRunner def allTests(): #查找路径下所有的测试文件 suite=unittest.TestLoader().discover( start_dir=os.path.dirname(__file__), pattern='test_*.py', ) return suite def base_dir(): #查看文件路径的上一级 return os.path.dirname(os.path.dirname(__file__)) def run(): fp=open(os.path.join(base_dir(),'report','report.html'),'wb') #路径拼接和文件二级制的写入 runner=HTMLTestRunner.HTMLTestRunner( stream=fp, title='UI自动化测试报告', description='UI自动化测试报告详细信息' ) runner.run(allTests()) if __name__ == '__main__': getNowTime()
测试报告生成后会写入report.html的文件中,我们使用浏览器打开就可以查看:
我们发现上面的代码生成的测试报告每次都会覆盖,这样我们想查看之前的测试报告就没了,我们可以在生成的测试报告名称中加入时间,代码如下
import os import time import unittest import HTMLTestRunner def allTests(): #查找路径下所有的测试文件 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 base_dir(): #查看文件路径的上一级 return os.path.dirname(os.path.dirname(__file__)) def run(): fp=open(os.path.join(base_dir(),'report',getNowTime()+'report.html'),'wb') #路径拼接和文件的 runner=HTMLTestRunner.HTMLTestRunner( stream=fp, title='UI自动化测试报告', description='UI自动化测试报告详细信息' ) runner.run(allTests()) if __name__ == '__main__': run()