unitttest单元测试框架对于大部分Python爱好者来说并不陌生。unittest单元测试框架不管在Web自动化测试还是接口自动化测试中,应用都是非常广泛的。它不仅可以组织、运行数量级的自动化测试用例,还提供丰富的断言方法、跳过测试及生成测试报告等功能。本节进行unittest单元测试框架的实战。
unittest是Python自带的一个单元测试框架。它不仅可以做单元测试,还适用于Web自动化测试用例的开发和执行。通过unittest官方文档可以了解到以下信息。
Python单元测试框架有时称为PyUnit,是JUnit的Python语言版本,由Kent Beck和Erich Gamma编写。JUnit则是Kent的Smalltalk测试框架的Java版本。每一个都是它各自语言的事实上的标准单元测试框架。
unittest的设计灵感最初来源于JUnit及其他语言中具有共同特征的单元框架。它支持自动化测试,在测试中使用setup()(初始化)和tearDown()(关闭销毁)操作,组织测试用例为套件(批量运行),以及把测试和报告独立开来。
下面来看一个unittest单元测试框架的基本案例,示例如下:
import unittest class TestStrSample(unittest.TestCase): def test_strendswich(self): self.assertEqual('foo'.endswith('o'),False) def test_split(self): s = 'my name is Fighter' self.assertEqual(s.split(),['my','name','is','Fighter']) if __name__ == '__main__': unittest.main()
首先导入unittest单元测试框架,unittest单元测试框架下的测试类都默认继承unittest.TestCase子类。测试类下的测试方法都必须以test开头,否则不会被执行。每一个测试方法中最后都使用了assertEqual()方法来判断实际结果是否等于预期结果。unittest.main()方法提供了一个命令行接口,自动执行测试类下以test开头的测试方法。当执行完所有测试用例后,测试结果都会被记录并且写到测试报告中去。一般在测试执行结果中会有以下3种状态。
(1). (success):表示测试用例运行通过。
... ---------------------------------------------------------------------- Ran 3 tests in 0.000s OK
(2)F(failed):表示测试用例运行失败。
..F ====================================================================== FAIL: test_strendswich (__main__.TestStringMethods) ---------------------------------------------------------------------- Traceback (most recent call last): File "D:/project/One/webUI/webdriver_html/Pybase" self.assertEqual('foo'.endswith('o'),False) AssertionError: True != False
(3)E(error):表示测试脚本中存在错误。
..E ====================================================================== ERROR: test_strendswich (__main__.TestStringMethods) ---------------------------------------------------------------------- Traceback (most recent call last): File "D:/project/One/webUI/webdriver_html/Pybase.py",line 9,in test_strendswich self.assertEqual('foo'.endswth('o'),False) AttributeError: 'str' object has no attribute 'endswth'
setUp()和tearDown()方法可以在测试运行前后做一些操作。例如,Web自动化测试过程中经常会把实例化浏览器、获取URL、设置等待等操作存放在setUp()中,而tearDown()是测试完成后的清除工作,如关闭浏览器、数据库的还原等。
注意:setUp()和tearDown()属于非必要条件,如果什么都不做,可以用pass来表示。
下面来看一个setUp()和tearDown()的案例,示例如下:
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()
增加setUp()和tearDown()两部分测试固件后,运行结果如下:
.. 每条测试用例开始执行前做的操作..... ---------------------------------------------------------------------- 第一条测试用例 Ran 2 tests in 0.000s 每条测试用例执行完毕后做的操作..... 每条测试用例开始执行前做的操作..... OK 第二条测试用例 每条测试用例执行完毕后做的操作..... Process finished with exit code 0
测试用例执行顺序:setUp()→第一条测试用例→tearDown()→setUp()→第二条测试用例→tearDown()。也就是说,每执行一条测试用例,都要进行一次初始化操作和收尾工作。
断言即在测试用例执行过程中,通过判断测试得到实际结果和预期结果是否相等。unittest.TestCase类属性提供了多种断言方法,示例如下。
(1)assertEqual(a,b,[msg='测试失败时输出的信息']):断言a和b是否相等,相等则测试用例通过。
(2)assertNotEqual(a,b,[msg='测试失败时输出的信息']):断言a和b是否相等,不相等则测试用例通过。
(3)assertTrue(x,[msg='测试失败时输出的信息']):断言x是否为True,是True则测试用例通过。
(4)assertFalse(x,[msg='测试失败时输出的信息']):断言x是否为False,是False则测试用例通过。
(5)assertIs(a,b,[msg='测试失败时输出的信息']):断言a是否是b,是则测试用例通过。
(6)assertNotIs(a,b,[msg='测试失败时输出的信息']):断言a是否是b,不是则测试用例通过。
(7)assertIsNone(x,[msg='测试失败时输出的信息']):断言x是否为None,是None则测试用例通过。
(8)assertIsNotNone(x,[msg='测试失败时输出的信息']):断言x是否为None,不是None则测试用例通过。
(9)assertIn(a,b,[msg='测试失败时输出的信息']):断言a是否在b中,在b中则测试用例通过。
(10)assertNotIn(a,b,[msg='测试失败时输出的信息']):断言a是否在b中,不在b中则测试用例通过。
(11)assertIsInstance(a,b,[msg='测试失败时输出的信息']):断言a是b的一个实例,是则测试用例通过。
比较常用的断言方法有assertEqual()、assertTrue()、assertIn()、assertIs()等,其他方法可以作为备选。
测试固件整合Web自动化测试的示例如下:
import unittest from selenium import webdriver from time import sleep class TestWebUI(unittest.TestCase): def setUp(self): self.driver = webdriver.Chrome() def tearDown(self): self.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()
实例化浏览器、关闭浏览器分别定义在setUp()、tearDown()测试固件中。在TestWebUI()测试类下新增test_QQLogin()和test_MaoyanMovie()两条测试用例,每条测试用例用title获取验证信息,使用assertEqual()方法来判断实际结果和预期结果是否相等。如果不相等,就给出“页面跳转失败,请重新检查!”的提示信息。通过测试结果,可以看出运行两条测试用例消耗的时间为17.197s。
运行结果如下:
.. ---------------------------------------------------------------------- Ran 2 tests in 17.197s OK
使用setUp()和tearDown()方法在每次执行测试用例前都会先执行一次setUp()操作,然后执行测试用例,最后执行tearDown()操作。假设有100条测试用例,如果把实例化浏览器定义在setUp()操作中,那么执行这100条用例就需要开启100次浏览器,这样会导致测试效率大大降低。
引入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()
案例中,@classmethod装饰器结合setUpClass()和tearDownUpClass()方法完成启动一次浏览器和关闭一次浏览器动作。两条测试用例运行消耗的总时间长为9.707s,从时间上看要比setUp()和tearDown()测试固件节省很多时间。
编写Web自动化测试用例时会写很多个测试类,每个测试类下会有多个测试用例。但针对同一个测试项目而言,测试地址、实例化浏览器、关闭浏览器等操作都是固定的。可以把这部分测试固件单独分离出来,这样在测试类中的代码看起来会更加简洁、清晰,也提高了测试固件这部分代码的重用性。
针对2.11.4小节中的代码,新建...\Myunit.py文件并做如下改进,示例如下:
import unittest from selenium import webdriver class TestWebUI(unittest.TestCase): @classmethod def setUpClass(cls): cls.driver = webdriver.Chrome() @classmethod def tearDownClass(cls): cls.driver.quit()
定义TestWebUI()测试类,把测试固件setUpclass()和tearDownClass()方法单独分离出来。修改...\testUnit.py文件,示例如下:
from Myunit import * class TestModle(TestWebUI): 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,'猫眼电影 - 一网打尽好电影')
导入Myunit.py模块下的测试类和方法(*表示所有类和方法),其中TestModle()测试类继承TestWebUI()类,从而实例化浏览器。
unittest单元测试框架可以将测试结果写到测试报告中,通过测试报告可以清晰地查看自动化测试用例的总数、通过数、失败数及失败原因等信息。
1. 配置HTMLTestRunner.py文件
将HTMLTestRunner.py文件复制到Python安装目录下的Lib目录,如C:\Python36\Lib HTMLTestRunner.py。
2. 编写allTest.py文件,运行所有测试脚本
目录架构如下。
(1)...\TestCases:存放测试用例。
(2)...\TestCases\__init__:声明TestCases包文件。
(3)...\TestCases\testUnit.py:自动化测试用例。
(4)...\TestCases\Myunit.py:存放测试固件。
(5)...\Reports:存放HTML测试报告。
(6)...\allTest.py:运行所有测试用例主程序文件。
编写主程序...\allTest.py文件,代码如下:
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目录''' fp = open(os.path.join(os.path.dirname(__file__),'Reports', time.strftime("%Y_%m_%d_%H_%M_%S")+ 'report.html'),'wb') HTMLTestRunner(stream=fp,title='Python+Selenium自动化测试实战', description='基于python语言UI自动化测试').run(getAllCases()) if __name__ == '__main__': RunMain()
getAllCases()方法下的discover()函数用于读取TestCases目录下以test开头的.py文件,并返回所有测试模块下的测试用例。RunMain()方法用于生成测试报告,并将测试结果写入测试报告中。wb模式用于读取二进制文件。time.strftime()方法用于获取系统的当前时间,以便区分生成的不同测试报告名。
生成的HTML测试报告如图2.36所示。
图2.36 生成的HTML测试报告