软件测试专栏
实战相关知识
1.unittest框架
unittest框架是Python语言内置的单元测试框架,Python编写的Web UI自动化测试脚本可以借助该框架来组织和执行。
(1)UnitTest组成部分
- TestFixture(测试固件):测试用例的准备和销毁。
- TestCase(测试用例):一个TestCase的实例就是一个测试用例。
- TestSuite(测试套件):将多个测试用例集合在一起就是一个TestSuite。
- TestRunner(测试运行器):使用TextTestRunner提供的 run( )方法执行测试用例。
(2)使用UnitTest测试框架注意:
- 首先需要导入unittest包:import unittest。
- 导入包的语句和定义测试类中间要隔两个空行。
- 新建测试类的名称,建议每个单词的首字母大写,编写顺序建议保持团队一致。
- 测试类必须继承unittest.TestCase
- 接下来可以编写setUp,setUp方法不是必须需要的。
- 接下来可以编写测试用例,测试用例名称以”test_”开头。
self.assertXXX是UnitTest提供的断言方法 - 用例写完,就可以编写tearDown,tearDown也不是必须要有。
tearDown写完后空两行,就可以使用unittest.main()进行测试了。
(3)用HTMLTestRunner模块生成可视化测试报告。
2.Pytest测试框架
Pytest测试框架在当今自动化测试中更受欢迎,是一个非常流行且成熟的全功能的Python测试框架,适用于单元测试、UI测试、接口测试。
(1)Pytest规则
- 文件命名:默认以“test_”开头或者以”_test”结尾。
- 测试类(class)命名:默认以“Test”开头
- 测试方法(函数)命名:默认以“test_”开头
- 断言:直接使用Python语言的断言assert
(2)Pytest测试固件
- 如果测试文件中没有定义class,而是直接定义的函数,那么使用setup_module、teardown_module 和 setup_function、teardown_function
- 如果测试文件中定义了class,就使用setup_class、teardown_class 和 setup_method、teardown_method
- 不管是否定义class,都可以使用setup、teardown来实现在每个方法(或函数)的前后执行。
- 建议在一个项目中约定好是定义class来组织测试用例,还是直接定义函数来组织用例。
(3)测试用例
Pytest和unittest的框架风格基本一致,但有几点要注意:
- 注意函数或方法名以“test_”开头
- 直接通过函数定义测试用例的话,def后面的括号中没有self
- 通过class中的方法定义测试用例的话,def后面的括号中有self。
- 断言用的是Python的断言方式。
(4)用第三方插件完成测试报告的生成。
实战内容
1.“百度翻译”的页面UI测试
将“百度翻译”的页面UI测试(至少包含翻译功能这个测试用例)的自动化测试线性脚本,分别用unittest和Pytest框架完成,并生成测试报告。
(1)利用unittest框架完成
首先需要导入包
新建测试类的名称,测试类必须继承unittest.TestCase
编写setUp
编写测试用例,测试用例名称以”test_”开头。
- 用例1:输入的文字与语言不一致
使用assertTRUE断言方法,断言网页会输出与输入文字匹配的语言,如下图
- 用例2:输入的文字与语言一致
使用assertEqual断言方法,断言输出文本框结果与预期结果一致,如下图
用例写完,编写tearDown
tearDown写完后空两行,使用unittest.main()进行测试
用第三方插件完成测试报告的生成
将HTMLTestRunner.py和run.py以及__init__.py文件放置与脚本文件同级目录下,如下图:
编写run.py文件
运行run.py文件
运行结果如下
出现如上结果,说明两个测试用例通过
测试报告如下
(2)利用Pytest框架完成
首先需要导入包
新建测试类的名称,默认以“Test”开头
编写setUp
编写测试用例,测试用例名称以”test_”开头。
- 用例1:输入的文字与语言不一致
使用Python语言的断言assert,断言网页会输出与输入文字匹配的语言 - 用例2:输入的文字与语言一致
使用Python语言的断言assert,断言输出文本框结果与预期结果一致 - 用例写完,编写teardown
全局设置
创建一个配置文件:pytest.ini
该文件要和需要执行的测试文件所在的目录文件在同一级
如下图所示:
pytest.ini内容如下:
通过addopts来设置命令行参数;-v 监控、失败重试的次数、重试的时间间隔、按标签来执行、生成测试报告,多个参数之间用空格分隔 - 接着在py文件teardown后两行,使用pytest.main()进行测试
运行结果如下
出现passed说明两个测试用例通过
测试报告如下
2.“新浪微博”的两个页面UI测试
将“新浪微博”的两个页面UI测试(至少包含登录账号、发文字微博两个测试用例)的自动化测试线性脚本,分别用unittest和Pytest框架完成,并生成测试报告。
(1)利用unittest框架完成
首先需要导入包
新建测试类的名称,测试类必须继承unittest.TestCase
编写setUp,因为用例二需要登录才能进行测试,所以登录需要写进setUp
编写测试用例,测试用例名称以”test_”开头。
- 用例1:登录账号,这里仅测试是否通过
使用assertIn断言方法,断言用户名‘111Nuyoah111’会出现在网页中
- 用例2:发布微博文字
使用assertIn断言方法,断言发布内容成功,如下图
- 用例写完,编写tearDown
- tearDown写完后空两行,使用unittest.main()进行测试
用第三方插件完成测试报告的生成
将HTMLTestRunner.py和run.py以及__init__.py文件放置与脚本文件同级目录下,如下图:
- 编写run.py文件
运行run.py文件
运行结果如下
出现如上结果,说明两个测试用例通过
测试报告如下
(2)利用Pytest框架完成
首先需要导入包
新建测试类的名称,默认以“Test”开头
编写setUp
编写测试用例,测试用例名称以”test_”开头。
- 用例1:登录账号,这里仅测试是否通过
使用assertIn断言方法,断言用户名‘xxx’会出现在网页中
- 用例2:发布微博文字
使用assertIn断言方法,断言发布内容成功 - 用例写完,编写teardown
全局设置
创建一个配置文件:pytest.ini
该文件要和需要执行的测试文件所在的目录文件在同一级
如下图所示:
pytest.ini内容如下:
通过addopts来设置命令行参数;-v 监控、失败重试的次数、重试的时间间隔、按标签来执行,多个参数之间用空格分隔
运行生成测试报告,在py文件teardown后两行,使用pytest.main()进行测试
- 运行结果如下
出现passed说明两个测试用例通过
测试报告如下
操作异常问题与解决方案
- 问题1:pytest框架脚本运行失败
- 解决方法:通过查询,发现是pytest环境问题,所以将pytest及相关插件卸载后在全局目录下重新下载
- 问题2:allure在成功使用后,再次使用会自动跳过测试用例
- 解决方法:allure环境不稳定,更换测试报告方法,使用pytest-html完成测试报告
- 问题3:微博无法使用账号密码登录,测试用例无法完成登录的试错
- 解决方法:修改代码,直接在setup固件进行手动登录,最后加入一个登录的测试用例进行断言
总结
Unittest和Pytest是Python中常用的两个测试框架,用于编写和执行单元测试。
Unittest是Python的内置测试框架之一,可以通过导入unittest模块来使用。Unittest提供了一组用于编写测试用例的类和方法,测试用例是通过继承unittest.TestCase类来创建的。测试方法以test_开头,并且可以使用断言方法(如assertEqual()、assertTrue()等)来验证预期行为。Unittest提供了丰富的功能和工具,如测试套件、测试装置(setUp()和tearDown()方法)、测试发现等。可以使用命令行工具或集成开发环境(IDE)来运行Unittest测试。
Pytest是一个第三方的Python测试框架,可以通过安装pytest库来使用。Pytest提供了更简洁、灵活和可扩展的方式来编写测试用例。不需要继承特定的基类,可以使用普通的函数定义测试用例,用assert语句来断言结果。Pytest具有丰富的插件生态系统和许多附加功能,例如自动发现测试文件、参数化测试、夹具(fixtures)等。Pytest支持使用命令行工具来运行测试,并提供了丰富的输出和报告选项。
unitest和pytest的区别是:语法风格:Unittest使用类和方法的方式来组织测试用例,而Pytest使用函数定义测试用例。灵活性:Pytest提供了更灵活和简洁的语法,没有像Unittest那样的约束。Pytest的编写方式更为简单,减少了样板代码的编写。插件生态系统:Pytest具有丰富的插件生态系统,提供了许多附加功能和扩展选项,而Unittest相对较少。自动发现测试:Pytest具有自动发现测试文件和用例的功能,而Unittest需要手动设置测试套件和加载用例。夹具支持:Pytest提供了强大的夹具(fixtures)机制,用于管理测试数据和环境设置。Unittest也支持夹具,但Pytest的夹具功能更为灵活和强大。
总体而言,Pytest相对于Unittest提供了更简洁、灵活且功能更丰富的测试框架,能够简化测试代码的编写和维护,并提供更好的测试发现和报告功能。当然,选择使用哪个框架取决于个人偏好和项目需求。
附录
部分源码(与上面截图有所不同,仅供参考)
Baidu unittest
from selenium import webdriver import unittest from time import sleep from selenium.webdriver.common.by import By class TestBaidu(unittest.TestCase): def setUp(self): self.driver = webdriver.Chrome() self.driver.maximize_window() self.driver.implicitly_wait(20) # 访问登录页 self.driver.get('https://fanyi.baidu.com/') # 移除广告弹窗 self.driver.find_element(By.CLASS_NAME,'app-guide-close').click() def test_001(self): # 用例一:输入的文字与选择的语言不一致 # 选择语言阿拉伯语 language = self.driver.find_element(By.CLASS_NAME,'select-from-language') language.click() language_text=self.driver.find_element(By.XPATH,'//*[@id="lang-panel-container"]' '/div/div[5]/div[1]/div[1]/div/span[1]') language_text.click() #输入翻译文本英语 intext =self.driver.find_element(By.CLASS_NAME,'textarea') intext.send_keys('love') sleep(2) # 输入不匹配文字后的提示信息 self.assertTrue(self.driver.find_element(By.LINK_TEXT,'英语')) def test_002(self): # # 用例二:输入的文字与选择的语言一致 # 选择语言英语 language = self.driver.find_element(By.CLASS_NAME, 'select-from-language') language.click() language_text = self.driver.find_element(By.XPATH,'//*[@id="lang-panel-container"]' '/div/div[5]/div[1]/div[21]/div/span[1]') language_text.click() # 输入翻译文本英语 intext = self.driver.find_element(By.CLASS_NAME, 'textarea') intext.send_keys('love') sleep(2) # 输入不匹配文字后的提示信息 outtext = self.driver.find_element(By.CLASS_NAME, 'output-bd').text self.assertEqual(outtext,'爱') def tearDown(self): self.driver.quit() if __name__ == '__main__': unittest.main()
Baidu pytest
from time import sleep import pytest from selenium import webdriver from selenium.webdriver.common.by import By class TestBaidu(): def setup(self): self.driver = webdriver.Chrome() self.driver.maximize_window() self.driver.implicitly_wait(20) # 访问登录页 self.driver.get('https://fanyi.baidu.com/') # 移除广告弹窗 self.driver.find_element(By.CLASS_NAME,'app-guide-close').click() @pytest.mark.L1 def test_001(self): # 用例一:输入的文字与选择的语言不一致 # 选择语言阿拉伯语 language = self.driver.find_element(By.CLASS_NAME,'select-from-language') language.click() language_text=self.driver.find_element(By.XPATH,'//*[@id="lang-panel-container"]' '/div/div[5]/div[1]/div[1]/div/span[1]') language_text.click() #输入翻译文本英语 intext =self.driver.find_element(By.CLASS_NAME,'textarea') intext.send_keys('love') sleep(2) # 输入不匹配文字后的提示信息 assert '英语' == self.driver.find_element(By.LINK_TEXT,'英语').text @pytest.mark.L2 def test_002(self): # # 用例二:输入的文字与选择的语言一致 # 选择语言英语 language = self.driver.find_element(By.CLASS_NAME, 'select-from-language') language.click() language_text = self.driver.find_element(By.XPATH,'//*[@id="lang-panel-container"]' '/div/div[5]/div[1]/div[21]/div/span[1]') language_text.click() # 输入翻译文本英语 intext = self.driver.find_element(By.CLASS_NAME, 'textarea') intext.send_keys('love') sleep(2) # 输入不匹配文字后的提示信息 outtext = self.driver.find_element(By.CLASS_NAME, 'output-bd').text assert outtext=='爱' def teardown(self): self.driver.quit() if __name__ == '__main__': pytest.main(["-s", "./test_baidu.py"])
Weibo unittest
from selenium import webdriver import time,unittest # 通过时间戳,构造唯一project name from selenium.webdriver.common.by import By project_name = 'project_{}'.format(time.time()) class TestNewProject(unittest.TestCase): def setUp(self): self.driver = webdriver.Chrome() self.driver.maximize_window() self.driver.implicitly_wait(20) # 登录 self.driver.get('https://weibo.com') self.driver.find_element(By.CLASS_NAME, 'LoginCard_btn_Jp_u1').click() time.sleep(8) # 手动扫码登录 def test_new_project(self): # 测试搜索框输入框 self.driver.find_element(By.CLASS_NAME,'Form_input_2gtXx').send_keys('123') time.sleep(1) self.driver.find_element(By.CLASS_NAME,'Visible_angle_MP2Km').click() time.sleep(1) self.driver.find_element(By.XPATH,'//*[@id="homeWrap"]/div[1]/div/div[4]/div/div[3]/div/div/div[4]').click() # 测试发布 self.driver.find_element(By.CLASS_NAME,'Tool_btn_2Eane').click() time.sleep(1) self.assertIn('123', self.driver.page_source) def tearDown(self): self.driver.quit() if __name__ == '__main__': unittest.main()
Weibo pytest
import time import pytest from selenium import webdriver from selenium.webdriver.common.by import By class TestWeibo (): def setup(self): self.driver = webdriver.Chrome() self.driver.maximize_window() self.driver.implicitly_wait(20) # 登录 self.driver.get('https://weibo.com') self.driver.find_element(By.CLASS_NAME, 'LoginCard_btn_Jp_u1').click() time.sleep(8) # 手动扫码登录 @pytest.mark.L1 def test_new_project(self): assert 'xxx' in self.driver.page_source#此处自行修改“xxx” # 测试搜索框输入框 self.driver.find_element(By.CLASS_NAME,'Form_input_2gtXx').send_keys('123') time.sleep(1) self.driver.find_element(By.CLASS_NAME,'Visible_angle_MP2Km').click() time.sleep(1) self.driver.find_element(By.XPATH,'//*[@id="homeWrap"]/div[1]/div/div[4]/div/div[3]/div/div/div[4]').click() # 测试发布 self.driver.find_element(By.CLASS_NAME,'Tool_btn_2Eane').click() time.sleep(1) assert '123' in self.driver.page_source def teardown(self): self.driver.quit() if __name__ == '__main__': pytest.main(["-s", "test_weibo.py", "--html=./report.html"])