1、UnitTest简介
unittest是Python语言的单元测试框架,在Python的官方文档中,对unittest单元测试框架进行了详细的介绍,感兴趣的朋友可以到 https://www.python.org/doc/网站了解。本章重点介绍unittest单元测试框架在自动化测试中的应用。
unittest 单元测试框架提供了创建测试用例、测试套件和批量执行测试用例的方案。在python 安装成功后,unittest 单元测试框架就可以直接导入使用,它属于标准库。作为单元测试的框架,unittest 单元测试框架也是对程序的最小模块进行的一种敏捷化测试。在自动化测试中,我们虽然不需要做白盒测试,但是必须知道所使用语言的单元测试框架,这是因为当我们把Selenium2的API全部学习完后,就会遇到用例的组织问题。虽然函数式编程和面向对象编程提供了对代码的重构,但是对于所编写的每个测试用例,不可能编写成一个函数(方法)来调用执行。
利用单元测试框架,可以创建一个类,该类继承 unittest 的TestCase,这样可以把每个TestCase 看成是一个最小的单元,由测试套件组织起来,运行时直接执行即可,同时可引入测试报告。unittest各个组件的关系如图所示。
2、测试固件
在untitest 单元测试框架中,测试固件用于处理初始化的操作,例如,在对百度的搜索进行测试之前,首先需要打开浏览器并且进入到百度首页;测试结束后,需要关闭浏览器。测试固件提供了两种执行形式,一种是每执行一个测试用例,测试固件都会被执行到;另外一种是不管有多少个测试用例,测试固件只执行一次。
1. 测试固件每次均执行
unittest单元测试框架提供了名为setUp和tearDown的测试固件。
下面,我们通过编写一个例子来看测试固件执行的方式,测试代码为:
#!/usr/bin/env python
#-*-coding:utf-8-*-
import unittest
class BaiduTest(unittest.TestCase):
def setUp(self):
print('start')
def tearDown(self):
print('end')
def test baidu_so(self):
print('测试用例执行')
if __name__ == '__main__':
unittest.main(verbosity=2)
它的执行顺序是先执行 setUp 方法,再执行具体的测试用例 test_baidu_so,最后执行tearDown方法。
执行后的结果如图所示:
下面以百度首页为例,编写两个测试点。执行的方式是 setUp 执行两次,tearDown也会执行两次,具体实现的代码如下:
#!/usr/bin/env python 、
#-*-coding:utf-8-*-
from selenium import webdriver import unittest
class BaiduTest(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Firefox()
self.driver.maximize_window()
self.driver.get('http://www.baidu.com')
self.driver.implicitly_wait(30)
def tearDown(self):
self.driver.quit()
def test_baidu_news(self):
'''验证∶测试百度首页点击新闻后的跳转'''
self.driver.find_element_by_link_text('新闻').click()
def test_baidu_map(self):
'''验证∶测试百度首页点击地图后的跳转'''
self.driver.find_element_by_link_text('地图').click()
if ___name___ == '__main__':
unittest.main(verbosity=2)
运行以上代码后,浏览器会被打开两次,也会关闭两次。如果是在一个测试类中有N个测试用例,那么也就意味着打开N次浏览器,关闭N次浏览器,而关闭和打开浏览器都会占用一定的资源和时间,很显然,这并不是一个理想的选择。
2. 测试固件只执行一次
钩子方法setUp和tearDown虽然经常使用,但是在UI自动化测试中,一个系统的测试用例一般多达五百多条,打开和关闭五百多次浏览器,会消耗大量的资源和时间。在unittest 单元测试框架中可以使用另外一个测试固件来解决这一问题,它就是 setUpClass和tearDownClass方法。该测试固件方法是类方法,需要在方法上面加装饰器@classmethod。使用该测试固件,不管有多少个测试用例,测试固件只执行一次,也就是说不管有多少个测试用例,执行的时候浏览器只会被打开一次和关闭一次,案例代码如下:
#!/usr/bin/env python
#-*-coding:utf-8-*-
import unittest
class UiTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
print('start')
@classmethod
def tearDownClass(cls):
print('end')
def test_001(self):
print('第一个测试用例')
def test_002(self):
print('第二个测试用例')
if __name__ == '__main__':
unittest.main(verbosity=2)
执行以上测试代码后,输出的结果如图所示。
注解:在以上代码中,虽然有两个测试方法,但是测试固件只执行了一次。
虽然使用类测试固件可以在执行多个测试用例时让测试固件只执行一次,但是实际使用类测试固件中,还需要解决另外一个问题。例如,以百度首页测试点击新闻页面和测试点击地图页面为例,意味着点击新闻页面后,需回到首页后才可以找得到地图页面的链接进行点击。
因为在新闻页面并没有地图的链接地址,从而导致地图页面的测试失败,反之亦然。
实例代码如下:
#!/usr/bin/env python
#-*-coding:utf-8-*-
from selenium import webdriver
import unittest
class BaiduTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.driver=webdriver.Firefox()
cls.driver.maximize_window()
cls.driver.get('http://www.baidu.com')
cls.driver.implicitly_wait(30)
@classmethod
def tearDownClass(cls):
cls.driver.quit()
def test baidu news(self):
'''验证∶测试百度首页点击新闻后的跳转'''
self.driver.find_element_by_link_text('新闻').click()
self.driver.get('http://www.baidu.com')
def test baidu map(self):
'''验证∶测试百度首页点击地图后的跳转'''
self.driver.find_element_by_link_text('地图').click()
self.driver.get('http://www.baidu.com')
if __name__ == '__main__':
unittest.main(verbosity=2)
运行以上代码后,所有的测试用例执行通过。在实际的自动化测试工作中,建议大家尽量使用测试固件setUp和tearDown,使得自动化测试用例之间没有关联性,避免一个测试用例执行失败是由上一个测试用例导致。
3、测试执行
在以上实例中,可以看到测试用例的执行是在主函数中,unittest 调用的是main,代码如下:
main = TestProgram
TestProgram还是一个类,再来看该类的构造函数,代码如下:
class TestProgram(object):
"""A command-line program that runs a set of tests; this is primarily
for making test modules conveniently executable.
"""
USAGE = USAGE_FROM_MODULE
# defaults for testing
failfast = catchbreak = buffer = progName = None
def __init__(self, module='_main_', defaultTest=None, argv=None, testRunner=None, testLoader=loader.defaultTestLoader, exit=True,verbosity=1,failfast=None, catchbreak=None, buffer=None):
if isinstance(module,basestring):
self.module = __import__(module)
for part in module.split('.')[1:]:
self.module = getattr(self.module, part)
else:
self.module = module
if argv is None:
argv = sys.argv
self.exit = exit
self.failfast = failfast
self.catchbreak = catchbreak
self.verbosity=verbosity
self.buffer = buffer
self.defaultTest = defaultTest
self.testRunner = testRunner
self.testLoader=testLoader
self.progName = os.path.basename(argv[0])
self.parseArgs(argv)
self.runTests()
在unittest模块中包含的main方法,可以方便地将测试模块转变为可以运行的测试脚本。main 使用 unittest.TestLoader类来自动查找和加载模块内的测试用例,TestProgram类中该部分的代码如下:
def createTests(self):
if self.testNames is None:
self.test = self.testLoader.loadTestsFromModule(self.module)
else:
self.test = self.testLoader.loadTestsFromNames(self.testNames,self.module)
在执行测试用例时,在main方法中加入了verbosity=2,代码如下:
unittest.main(verbosity=2)
下面详细地解释一下verbosity部分。在verbosity中默认是1,0代表执行的测试总数和全局结果,2代表显示详细的信息。
我们现在通过一个实例来说明执行结果的显示,实例代码如下:
#!/usr/bin/env python #coding:utf-8
import unittest
from selenium import webdriver
class Baidu(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Chrome()
self.driver.maximize window()
self.driver.implicitly_wait(30)
self.driver.get('http://www.baidu.com')
def tearDown(self):
self.driver.quit()
def test baidu_title(self):
'''验证百度首页的title'''
self.assertEqual(self.driver.title,'百度一下,你就知道')
def test baidu url(self):
'''验证百度首页的URL'''
self.assertEqual(self.driver.current_url,'https://ww.baidu.com')
if __name__ == '__main__':
unittest.main(verbosity=2)
执行后的结果如图所示:
注解:在以上的截图中可以看到,成功的测试用例会显示 OK,失败的测试用例会显示出详细的信息。
在一个测试类中,有很多的测试用例,如果想单独地执行某一个测试,用鼠标右键点击要执行的测试用例的名称,选择“Run”。如我们想单独执行test_baidu_news的TestCase,将鼠标移动到该TestCase名称上用右键点击,再在出现的菜单项中点击Run “Unittest test_baidu_news”,如图所示。
点击Run后,就会单独地执行test_baidu_news的测试用例。
4、构建测试套件
前面介绍了测试用例的执行,在一个测试类中会有很多个测试用例。如何来组织并使用这些测试用例呢?untitest 提供了“测试套件”方法,它由 unittest 模块中的TestSuite 类表示,测试套件可以根据所测试的特性把测试用例组合在一起。
1. 按顺序执行
在实际的工作中,由于业务场景需要测试用例按顺序执行,例如,先执行X测试用例再执行Y测试用例,在TestSuite类中提供了addTest方法可以实现,也就是说要执行的测试用例按自己期望的执行顺序添加到测试套件中。下面的案例实现对百度首页的测试,测试用例的执行顺序是先测试百度新闻,再测试百度地图,代码如下:
#!/usr/bin/env python
#-*-coding:utf-8-*-
from selenium import webdriver
import unittest
class BaiduTest(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Firefox()
self.driver.maximize_window()
self.driver.get('http://www.baidu.com')
self.driver.implicitly_wait(30)
def tearDown(self):
self.driver.quit()
def test baidu_news(self):
'''验证∶测试百度首页点击新闻后的跳转'''
self.driver.find_element_by_link_text('新闻').click()
url=self.driver.current_url
self.assertEqual(url,'http://news.baidu.com/')
def test baidu map(self):
'''验证∶测试百度首页点击地图后的跳转'''
self.driver.find_element_by_link_text('地图').click()
self.driver.get('http://news.baidu.com/')
if __name__ == '__main__':
suite=unittest.TestSuite()
suite.addTest(BaiduTest('test_baidu_news'))
suite.addTest(BaiduTest('test_baidu_map'))
unittest.TextTestRunner(verbosinyao).run(suite)
注解:在以上代码中,首先需要对 TestSuite 类进行实例化,使之成为一个对象suite,然后调用TestSuite类中addTest方法,把测试用例添加到测试套件中,最后执行测试套件,从而执行测试套件中的测试用例。
运行以上代码后,测试用例会按照添加到测试套件的顺序执行,也就是说先添加进去的先执行,后添加进去的后执行。
2. 按测试类执行
在自动化测试中,一般测试用例往往多达几百个,如果完全按顺序来执行,其一是不符合自动化测试用例的原则,因为在UI 自动化测试中,自动化测试用例最好独立执行,互相之间不影响并且没有依赖关系。其二是当一个测试类中有很多测试用例时,逐一地向套件中添加用例是一项很烦琐的工作,这时,可以使用makeSuite类按测试类来执行。makeSuite可以实现把测试用例类中所有的测试用例组成测试套件 TestSuite 这样可避免逐一向测试套件中添加测试用例。
修改后的测试类中的代码如下:
#!/usr/bin/env python
#-*-coding:utf-8-*-
from selenium import webdriver
import unittest
class BaiduTest(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Firefox()
self.driver.maximize_window()
self.driver.get('http://www.baidu.com')
self.driver.implicitly_wait(30)
def tearDown(self):
self.driver.quit()
def test_baidu_news(self):
'''验证∶测试百度首页点击新闻后的跳转'''
self.driver.find_element_by_link_text('新闻').click()
url=self.driver.current_url
self.assertEqual(url,'http://news.baidu.com/')
def test baidu map(self):
'''验证∶测试百度首页点击地图后的跳转'''
self.driver.find_element_by_link_text('地图').click()
self.driver.get('http://www.baidu.com')
if __name__ == '__main__':
suite=unittest.TestSuite(unittest.makeSuite(BaiduTest)
unittest.TextTestRunner(verbosity=2).run(suite)
注解:在以上代码中可以看到,在测试套件 TestSuite 类中,unittest 模块调用了makeSuite的方法,makeSuite方法的参数是 testCaseClass,也就是测试类,代码如下:
def makeSuite(testCaseClass, prefix='test', sortUsing=cmp, suiteClass=suite.TestSuite):
return makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase(testCaseClass)
3. 加载测试类
在unittest 模块中也可以使用 TestLoader 类来加载测试类,也就是说TestLoader加载测试类并将它们返回添加到TestSuite中,TestLoader类的应用代码如下:
#!/usr/bin/env python
#-*-coding:utf-8-*-
from selenium import webdriver
import unittest
class BaiduTest(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Firefox()
self.driver.maximize window()
self.driver.get('http://www.baidu.com')
self.driver.implicitly_wait(30)
def tearDown(self):
self.driver.quit()
def test baidu_news(self):
'''验证∶测试百度首页点击新闻后的跳转'''
self.driver.find_element_by_link_text('新闻').click()
url=self.driver.current_url
self.assertEqual(url,'http://news.baidu.com/')
def test baidu map(self):
'''验证∶测试百度首页点击地图后的跳转'''
self.driver.find_element_by_link_text('地图').click()
self.driver.get('http://www.baidu.com')
if __name__ == '__main__':
suite=unittest.TestLoader().loadTestsFromTestCase(BaiduTest)
unittest.TextTestRunner(verbosity=2).run(suite)
4. 按测试模块执行
在TestLoader类中也可以按模块来执行测试。在Python中,一个Python文件就是一个模块,一个模块中可以有 N个测试类,在一个测试类中可以有 N个测试用例。
按模块执行的代码如下:
#!/usr/bin/env python
#-*-coding:utf-8-*-
from selenium import webdriver
import unittest
class BaiduTest(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Firefox()
self.driver.implicitly_wait(30)
self.driver.get('http://www.baidu.com')
def test title(self):
'''验证∶测试百度浏览器的title'''
self.assertEqual(self.driver.title,'百度一下,你就知道')
def test_so(self):
'''验证∶测试百度搜索输入框是否可编辑'''
so=self.driver.find_element_by_id('kw')
self.assertTrue(so.is_enabled()
def test_002(self):
'''验证点击百度新闻'''
self.driver.find_element_by_link_text('新闻').click()
@unittest.skip('do not run')
def test_003(self):
'''验证∶点击百度地图'''
self.driver.find_element_by_link_text('地图').click()
def tearDown(self):
self.driver.quit()
class BaiduMap(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Firefox()
self.driver.maximize_window()
self.driver.get('http://www.baidu.com')
self.driver.implicitly_wait(30)
def tearDown(self):
self.driver.quit()
def test_baidu_map(self):
'''验证∶测试百度首页点击地图后的跳转'''
self.driver.find_element_by_link_text('地图').click()
self.driver.get('http://www.baidu.com')
if __name__ == '__main__':
suite=unittest.TestLoader().loadTestsFromModule('unittest1.py')
unittest.TextTestRunner(verbosity=2).run(suite)
注解:在以上代码中可以看到,测试类分别是BaiduMap和BaiduTest,模块名称为unittest1.py,TestLoader类直接调用loadTestsFromModule方法返回给指定模块中包含的所有测试用例套件。
5. 优化测试套件
#!/usr/bin/env python
#-*-coding:utf-8-*-
from selenium import webdriver \import unittest
class BaiduTest(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Firefox()
self.driver.maximize_window()
self.driver.get('http://www.baidu.com')
self.driver.implicitly_wait(30)
def tearDown(self):
self.driver.quit()
def test baidu_news(self):
'''验证∶测试百度首页点击新闻后的跳转'''
self.driver.find_element_by_link_text('新闻').click()
self.assertEqual(self.driver.current_url,'http://news.baidu.com/')
def test baidu map(self):
'''验证∶测试百度首页点击地图后的跳转'''
self.driver.find element_by_link_text('地图').click()
self.assertEqual(self.driver.current_url,'https://map.baidu.com/')
@staticmethod
def suite(testCaseClass):
suite=unittest.TestLoader().loadTestsFromTestCase(testCaseClass)
return suite
if __name__ == '__main__':
unittest.TextTestRunner(verbosity=2).run(BaiduTest.suite(BaiduTest))
5、分离测试固件
在UI 自动化测试中,不管编写哪个模块的测试用例,都需要首先在测试类中编写测试固件初始化WebDriver类及打开浏览器,测试用例执行完成后还需要关闭浏览器,这部分的代码如下:
def setUp(self):
self.driver=webdriver.Firefox()
self.driver.maximize window()
self.driver.get('http://www.baidu.com')self.driver.implicitly_wait(30)
def tearDown(self):
self.driver.quit()
在每一个测试类中都要编写以上代码,因此需要重复编写很多代码。是否可以把测试固件这部分代码分离出去,测试类直接继承分离出去的类呢?我们把测试固件分离到init.py模块中,类名称为InitTest,代码如下:
#!/usr/bin/env python
#-*-coding:utf-8-*-
import unittest
from selenium import webdriver
class InitTest(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Firefox()
self.driver.maximize_window()
self.driver.get('http://www.baidu.com')
self.driver.implicitly_wait(30)
def tearDown(self):
self.driver.quit()
测试类继承了InitTest,继承后,在测试类中直接编写要执行的测试用例,代码如下:
#!/usr/bin/env python
#-*-coding:utf-8-*-
import unittest
from init import InitTest
class BaiduTest(InitTest):
def test baidu news(self):
'''验证∶测试百度首页点击新闻后的跳转'''
self.driver.find_element_by_link_text('新闻').click()
self.assertEqual(self.driver.current url,'http://news.baidu.com/')
def test baidu map(self):
'''验证∶测试百度首页点击地图后的跳转'''
self.driver.find_element_by_link_text('地图').click()
self.assertEqual(self.driver.current_url,'https://map.baidu.com/')
if name == '__main__':
unittest.main(verbosity=2)
注解:首先需要导入 init 模块中的InitTest 类,测试类 BaiduTest 继承InitTest类。这样执行测试类后,会先执行setUp方法,再执行具体的测试用例,最后执行 tearDown 方法。python 的类继承的方式解决了在每个测试类中都需要编写测试固件的问题。把测试固件分离出去后,即使后期测试地址发生变化,只需要修改init模块中InitTest类中的url地址即可,而不需要在每个测试类修改测试地址,减少了编写重复性代码的开销。
分离了测试固件,运行以上代码,对应的测试用例执行通过。
6、测试断言
断言就是判断实际测试结果与预期结果是否一致,一致则测试通过,否则失败。因此,在自动化测试中,无断言的测试用例是无效的。这是因为当一个功能自动化已全部实现,在每次版本迭代中执行测试用例时,执行的结果必须是权威的,也就是说自动化测试用例执行结果应该无功能性或者逻辑性问题。在自动化测试中最忌讳的就是自动化测试的用例功能测试虽然是通过的,但被测功能本身却是存在问题的。
自动化测试用例经常应用在回归测试中,发现的问题不是特别多,如果测试结果存在功能上的问题,则投入了人力去做的自动化测试就没有多大的意义了。所以每一个测试用例必须要有断言。在测试的结果中只有两种可能,一种是执行通过,另外一种是执行失败,也就是功能存在问题。在TestCase类中提供了assert方法来检查和报告失败,常用的方法如图所示。
1. assertEqual
assertEqual 方法用于测试两个值是否相等,如果不相等则测试失败。在这里特别强调的是,两个值相等不仅仅指内容相同而且还要类型相同。例如,两个值虽然内容一致,但一个是bytes类型一个是str类型,则两个值仍为不相等,测试失败。下面通过一个实例来演示,代码如下:
#!/usr/bin/env python
#-*-coding:utf-8-*-
import unittest
from selenium import webdriver
class BaiduTest(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Firefox()
self.driver.implicitly_wait(30)
self.driver.get('http://www.baidu.com')
def tearDown(self):
self.driver.quit()
def test baidu_title(self):
'''验证∶测试百度首页的title'''
self.assertEqual(self.driver.title,'百度一下,你就知道'.encode('gbk')
if __name__ == '__main__':
unittest.main(verbosity=2)
以上代码会执行失败,这是因为 self.driver.title 获取的内容是“百度一下,你就知道”,它的类型是str类型;而“百度一下,你就知道”被转为bytes类型,内容一致、类型不一致,所以失败,执行后的结果如图所示。
我们把代码再次修改一下,让两个断言内容一致,类型也一致,修改后的代码如下:
import unittest
from selenium import webdriver
class BaiduTest(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Firefox()
self.driver.implicitly_wait(30)
self.driver.get('http://www.baidu.com')
def tearDown(self):
self.driver.quit()
def test baidu_title(self):
'''验证∶测试百度首页的title'''
self.assertEqual(self.driver.title,'百度一下,你就知道')
if __name__ == '__main__':
unittest.main(verbosity=2)
再次运行以上代码,测试用例执行成功,断言通过。
2. assertTrue
assertTrue 返回的是布尔类型,它主要对返回的测试结果执行布尔类型的校验。例如,验证百度搜索输入框是否可编辑,is_enabled 方法返回的结果是 True,实现的代码如下:
#!/usr/bin/env python
#-*-coding:utf-8-*-
import unittest
from init import InitTest
class BaiduTest(InitTest):
def test_baidu_news(self):
'''验证∶测试百度搜索输入框是否可编辑'''
so=self.driver.find_element_by_id('kw')
self.assertTrue(so.is_enabled()
if __name__ == '__main__':
unittest.main(verbosity=2)
3. assertFalse
assertFalse和assertTrue,都是对返回的布尔类型进行校验。不同的是,assertFalse要求返回的结果是 False,测试用例才会执行成功。以新浪邮箱登录页面为例,取消“自动登录”按钮后,方法 is_elected返回的是 False,实现的代码如下:
#!/usr/bin/env python
#-*-coding:utf-8-*-
import unittest
from init import InitTest
class BaiduTest(InitTest):
def test baidu news(self):
'''验证∶新浪邮箱登录页面取消自动登录'''
isautoLogin=self.driver.find_element_by_id('storel')
isautoLogin.click()
self.assertFalse(isautoLogin.is_selected()
if __name__ == '__main__':
unittest.main(verbosity=2)
4. assertIn
assertIn 指的是一个值是否包含在另外一个值的范围内。还是以百度首页的url为例,测试https://www.baidu.com/是否在https://www.baidu.com/的范围内,实现的代码如下:
#!/usr/bin/env python
#-*-coding:utf-8-*-
import unittest
from init import InitTest
class BaiduTest(InitTest):
def test baidu news(self):
'''验证∶新浪邮箱登录页面取消自动登录'''
self.assertIn(self.driver.current_url,'https://www.baidu.com/')
if __name__ == '__main__':
unittest.main(verbosity=2)
7、断言的注意事项
前面介绍了unittest 测试框架中断言的基本内容,之所以单独列出一节来讲断言的注意事项,这是因为在实际的工作过程中,以及在与其他的网友交流时,发现很多人的测试用例写得不够规范,导致即使产品的功能有问题,测试用例执行结果仍为正确。出现这种情况,多是因为在测试过程中,使用了不正确的if应用和异常应用。下面就从这两个维度来解答这个错误信息是如何发生的,以及怎么样来避免它。
1. 不正确的if应用
还是以新浪登录页面为例,来测试自动登录按钮是否选中,该代码中引入了判断的代码如下:
#!/usr/bin/env python
#-*-coding:utf-8-*-
import unittest
from selenium import webdriver
class BaiduTest(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Firefox()
self.driver.maximize_window()
self.driver.get('http://mail.sina.com.cn/')
self.driver.implicitly_wait(30)
def tearDown(self):
self.driver.quit()
def test sina login(self):
'''验证∶新浪登录页面取消自动登录'''
isAutoLogin=self.driver.find_element_by_id('storel')
if isAutoLogin.is_selected():
print 'succcess'
else:
print 'fail'
if __name__ == '__main__':
unittest.main(verbosity=2)
注解:一个测试用例只有两种结果,要么 Pass,要么Fail(代码错误也显示Fail)。以上代码不管复选框是不是自动选中的,测试执行结果都是 Pass,测试用例都是通过。因此,在自动化测试的测试用例中,切记不要使用if else这类判断代码来代替断言。
2. 不正确的异常应用
这里还是以新浪登录页面为例,先看以下实例代码:
#!/usr/bin/env python
#-*-coding:utf-8-*-
import unittest
from selenium import webdriver
class BaiduTest(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Firefox()
self.driver.maximize_window()
self.driver.get('http://mail.sina.com.cn/')
self.driver.implicitly_wait(30)
def tearDown(self):
self.driver.quit()
def test sina login(self):
'''验证∶新浪登录页面取消自动登录'''
isAutoLogin=self.driver.find_element_by_id('storel')
try:
self.assertTrue(isAutoLogin.is selected())
except:
print 'fail'
if __name__ == '__main__':
unittest.main(verbosity=2)
注解:以上代码和应用if else的结果一样,即使自动登录未被选中,测试用例的结果也显示Pass。另外,在自动化测试中尽量不要应用打印结果来判断测试用例的情况,用例如,果在代码错误或者功能有 Bug 的情况下就让用例报错或者失败,而不是结果显示Pass,只有功能正常的测试用例结果才是Pass的。
8、批量执行测试用例
在实际测试中,常常需要批量执行测试用例。例如,在testCase 包中有test_baidu.py和test_sina.py两个文件,下面批量执行这两个模块的测试用例。创建新文件allTests.py,在allTests.py文件中编写批量执行的代码,test_baidu.py模块的代码如下:
#!/usr/bin/env python
#-*-coding:utf-8-*-
import unittest
from selenium import webdriver
class BaiduTest(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Firefox()
self.driver.maximize window()
self.driver.get('http://www.baidu.com')
self.driver.implicitly_wait(30)
def tearDown(self):
self.driver.quit()
def test baidu_title(self):
'''验证∶测试百度首页的title是否正确'''
self.assertEqual(self.driver.title,'百度一下,你就知道')
if __name__ == '__main__':
unittest.main(verbosity=2)
test_sina.py模块代码如下:
#!/usr/bin/env python
#-*-coding:utf-8-*-
import unittest
from selenium import webdriver
class SinaTest(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Firefox()
self.driver.maximize_window()
self.driver.get('http://mail.sina.com.cn/')
self.driver.implicitly_wait(30)
def tearDown(self):
self.driver.quit()
def test_username_password_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_link_text('登录').click()
divError=self.driver.find_element_by_xpath('html/body/div[1]/div[2]/div/div/div[4]/div[1]/div[1]/div[1]/span[1]').text
self.assertEqual(divError,'请输入邮箱名')
if __name__ == '__main__':
unittest.main(verbosity=2)
allTests.py模块的代码如下:
#!/usr/bin/env python
#-*-coding:utf-8-*-
import unittest
import os
def allcases():
'''获取所有测试模块'''
suite=unittest.TestLoader().discover(start_dir= os.path.dirname(__file__), pattern='test_*.py', top_level_dir=None)
return suite
if __name__ =='__main__':
unittest.TextTestRunner(verbosity=2).run(allCases())
注解:在以上代码中,批量获取测试模块用到的方法是 discover。discover方法有三个参数,第一个参数 start_dir是测试模块的路径,存放在testCase包中;第二个参数pattern用来获取testCase包中所有以test开头的模块文件,会获取到test_baidu.py和test_sina.py;第三个参数 top_level_dir 在调用的时候直接给默认值None。
discover方法的代码如下:
def discover(self,start_dir,pattern='test*.py',top_level_dir=None):
运行以上allTests.py文件后,测试结果如图所示。
9、生成测试报告
运行 allTests.py 文件后得到的测试结果不够专业,无法直接提交,因此需要借助第三方库生成HTML格式的测试报告。这里用到的库是HTMLTestRunner.py,下载地址是:
GitHub - tungwaiyip/HTMLTestRunner: HTMLTestRunner is an extension to the Python standard library's unittest module. It generates easy to use HTML test reports.
下载 HTMLTestRunner.py文件后,把该文件放到 Python安装路径的Lib子文件夹中,例如,C:\Python36-32\Lib目录下。创建report文件夹,与testCase包放在同一个目录下,继续完善 allTests.py 文件,最终生成测试报告,最终的allTests.py代码如下:
#!/usr/bin/env python #-*-coding:utf-8-*-
import unittest import os
import HTMLTestRunner import time
def allTests():
'''获取所有要执行的测试用例'''
suite=unittest.defaultTestLoader.discover(start_dir=os.path.join(os.path.dirname(_file_),'testCase'), pattern='test_*.py', top_level_dir=None)
return suite
def getNowTime():
'''获取当前的时间'''
return time.strftime('%Y-%m-%d %H_%M_%S',time.localtime(time.time())
def run():
fileName=os.path.join(os.path.dirname(_file_),'report', getNowTime()+'report.html')
fp=open(fileName,'wb')
runner=HTMLTestRunner.HTMLTestRunner(stream=fp,title='UI自动化测试报告',description='UI自动化测试报告详细信息')
runner.run(allTests())
if __name__ == '__main__'
run()
注解:在以上完善后的allTests.py 文件中其中导入了sys、HTMLTestRunner库。getNowTime 方法用来获取当前时间,每一次生成的测试报告如果文件名称一致,由于加上了最新时间信息,便可以根据文件名称确认哪个是最新的测试报告;run 方法用来执行测试套件中的测试用例和生成测试报告。针对HTMLTestRunner.py 文件,在Python3中需要对代码进行修改,否则,执行allTests.py后就会报错。
再次运行 allTests.py 文件后,在report 文件夹下生成了最新的测试报告,report的目录如图所示。
打开该HTML文件,可以看到基于HTML的测试报告,如图所示。
注解:在图4-9-2所示的基于HTML的测试报告中,我们可以看到开始时间、执行时间、测试用例通过数、失败数、错误数,点击Detail还可以看到更详细的信息,如图所示。
10、代码覆盖率统计实战
Coverage.py 是 Python 程序代码覆盖率的测试工具,用于监视程序执行了哪些代码,未执行哪些代码。在Python3中,首先需要通过pip3 install coverage来安装它,安装过程如图所示。
安装coverage后运行allTests.py文件,程序会运行所有以test开头的测试模块的文件。到allTests.py模块的路径下运行以下代码:
coverage3 run allTests.py
再次执行 coverage html,也就是代码覆盖率统计,通过 html 的文件查看,执行的命令如图所示。
执行后,在Chapter4目录下会生成一个htmlcov文件夹,在该文件夹里面显示的是代码覆盖率统计的文件,如图所示。
点击打开 index.html 文件,显示的是每个文件运行代码的覆盖率统计,如图所示。
点击任意一个模块文件,就会显示该模块执行的代码,如点击 test_sina.py文件,代码如图所示。