1.unittest简述
unittest是Python语言的单元测试框架。
unittest单元测试框架提供了创建测试用例、测试套件和批量执行测试用例的方案。利用单元测试框架,可以创建一个类,该类继承unittest的TestCase,这样可以把每个TestCase看成是一个最小单元,由测试套件组织起来运行时直接执行即可,同时可以引入测试报告。
2.测试固件
在unittest单元测试框架中,测试固件用于处理初始化的操作,例如,在对百度的搜索进行测试之前,首先需要打开浏览器并且进入到百度首页;测试结束后,需要关闭浏览器。测试固件提供了两种执行方式,一种是每执行一个测试用例,测试固件都会被执行到;另一种是不管执行多少个测试用例,测试固件都只执行一次。
2.1测试固件每次均执行
unittest单元测试框架提供了名为setUp和tearDown的测试固件。上代码:
__author__ = 'Hello'
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方法。执行结果:
test_baidu_so (__main__.BaiduTest) ... ok
start
测试用例执行
end
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Process finished with exit code 0
注:verbosity是一个选项,表示测试结果的信息复杂度,有0、1、2 三个值
0 (静默模式): 你只能获得总的测试用例数和总的结果 比如 总共10个 失败2 成功8
1 (默认模式): 非常类似静默模式 只是在每个成功的用例前面有个“.” 每个失败的用例前面有个 “F”
2 (详细模式):测试结果会显示每个测试用例的所有相关的信息
并且 你在命令行里加入不同的参数可以起到一样的效果
加入 --quiet 参数 等效于 verbosity=0
加入--verbose参数等效于 verbosity=2
什么都不加就是 verbosity=1
下面以百度首页为例,编写两个测试点。执行方式是setUp执行两次,tearDown也执行两次。代码:
__author__ = 'xiaotufei'
from selenium import webdriver
import unittest
class BaiduTest(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Firefox()
self.driver.implicitly_wait(5)
self.drivermaxmize_window()
self.driver.implicitly_wait(5)
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)
运行结果如下:
D:Pythonpython.exe "D:WorkToolsJetBrainsPyCharm Community Edition 3.4.4helperspycharmutrunner.py" D:WorkToolspython_workspacePython_unitest1aidu_news.py::BaiduTest true
Testing started at 18:30 ...
D:WorkToolsJetBrainsPyCharm Community Edition 3.4.4helperspycharmutrunner.py:2: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
import imp
Process finished with exit code 0
运行以上代码后,浏览器会被打开两次,也会关闭两次,如果是在一个测试类中有N个测试用例,那么也就意味着打开/关闭N次浏览器,很显然,这并不是一个理想的选择。
2.2测试固件只执行一次
setUp和tearDown虽然经常使用,但是在UI自动哈测试中,一个系统的测试用例通常多达数百条,打开和关闭数百次浏览器显然不是很好的选择。在unittest单元测试框架中可以使用另外一个测试固件来解决这一问题,它就是setUpClass和tearDownClass方法。该测试固件方法是类方法,需要在方法上面加装饰器@classmethod。使用该测试固件,不管有多少个测试用例,测试固件只执行一次。
代码如下:
__author__ = 'xiaotufei'
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)
输出结果如下:
Testing started at 14:38 ...
D:WorkToolsJetBrainsPyCharm Community Edition 3.4.4helperspycharmutrunner.py:2: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
import imp
start
第一个测试用例
第二个测试用例
end
Process finished with exit code 0
在上面代码中,虽然有两个测试方法,但是测试固件只执行了一次。
虽然使用类测试固件可以在执行多个测试用例时让测试固件只执行一次,但是实际使用类测试固件中,还需要解决另外一个问题。例如,以百度首页测试点击新闻页面和测试点击地图页面为例,意味着在点击新闻页面后,需要回到首页后才可以找得到地图页面的链接进行点击。
因为在新闻页面并没有地图的链接地址,从而导致地图页面的测试失败,反之亦然。代码:
__author__ = 'xiaotufei'
from selenium import webdriver
import unittest
class BaiduTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.driver=webdriver.Chrome()
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()
def test_baidu_map(self):
#验证:测试百度首页点击地图后的跳转
self.driver.find_element_by_link_text('地图').click()
if __name__ == '__main__':
unittest.main(verbosity=2)
运行结果:
Error
Traceback (most recent call last):
File "D:WorkToolspython_workspacePython_unitest12_1.py", line 20, in test_baidu_news
self.driver.find_element_by_link_text('新闻').click()
File "D:Pythonlibsite-packagesseleniumwebdriver
emotewebdriver.py", line 429, in find_element_by_link_text
return self.find_element(by=By.LINK_TEXT, value=link_text)
File "D:Pythonlibsite-packagesseleniumwebdriver
emotewebdriver.py", line 979, in find_element
'value': value})['value']
File "D:Pythonlibsite-packagesseleniumwebdriver
emotewebdriver.py", line 322, in execute
self.error_handler.check_response(response)
File "D:Pythonlibsite-packagesseleniumwebdriver
emoteerrorhandler.py", line 242, in check_response
raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"link text","selector":"新闻"}
(Session info: chrome=80.0.3987.149)
Process finished with exit code 0
执行过程中出现过的报错:
D:Pythonpython.exe "D:WorkToolsJetBrainsPyCharm Community Edition 3.4.4helperspycharmutrunner.py" D:WorkToolspython_workspacePython_unitest1aidu_news.py::BaiduTest true
Testing started at 18:20 ...
D:WorkToolsJetBrainsPyCharm Community Edition 3.4.4helperspycharmutrunner.py:2: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
import imp
Error
Traceback (most recent call last):
File "D:Pythonlibsite-packagesseleniumwebdrivercommonservice.py", line 76, in start
stdin=PIPE)
File "D:Pythonlibsubprocess.py", line 775, in __init__
restore_signals, start_new_session)
File "D:Pythonlibsubprocess.py", line 1178, in _execute_child
startupinfo)
FileNotFoundError: [WinError 2] 系统找不到指定的文件。
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "D:WorkToolspython_workspacePython_unitest1aidu_news.py", line 8, in setUp
self.driver=webdriver.Chrome()
File "D:Pythonlibsite-packagesseleniumwebdriverchromewebdriver.py", line 73, in __init__
self.service.start()
File "D:Pythonlibsite-packagesseleniumwebdrivercommonservice.py", line 83, in start
os.path.basename(self.path), self.start_error_message)
selenium.common.exceptions.WebDriverException: Message: 'chromedriver' executable needs to be in PATH. Please see https://sites.google.com/a/chromium.org/chromedriver/home
备注:出现以上错误,网上下载Chromedriver.exe文件,放到Chrome安装目录及Python目录下,即可通过Python打开Chrome浏览器。随后出现如下报错:
Error
Traceback (most recent call last):
File "D:WorkToolspython_workspacePython_unitest1aidu_news.py", line 9, in setUp
self.drivermaxmize_window()
AttributeError: 'BaiduTest' object has no attribute 'drivermaxmize_window'
Error
Traceback (most recent call last):
File "D:WorkToolspython_workspacePython_unitest1aidu_news.py", line 9, in setUp
self.drivermaxmize_window()
AttributeError: 'BaiduTest' object has no attribute 'drivermaxmize_window'
注释掉self.drivermaxmize_window() 这行代码后,运行成功。
3.测试执行
在以上实例中,可以看到测试用例的执行是在主函数中,unittest调用的是main,查看代码如下:
main = TestProgram
在unittest模块中包含的main方法,可以方便的将测试模块转变为可运行的测试脚本。main使用unittest.Testloader类来自动查找和加载模块内的测试用例。
在一个测试类中,有很多的测试用例,如果想单独执行某一个测试,用鼠标右键点击要执行的测试用例名称——》Run。如果我们想单独执行test_baidu_news的Testcase,将鼠标移动到该TestCase名称上右键点击——》Run:
执行结果:
4.构建测试套件
前面介绍了测试用例的执行,在一个测试类中会有很多个测试用例。如何来组织并使用这些测试用例呢?unittest提供了“测试套件”方法,它由unittest模块中的TestSuite类表示,测试套件可以根据所测试的特性把测试用例组合在一起。
4.1按顺序执行
在实际工作中,由于业务场景需要测试用例按顺序执行,例如,先执行X测试用例再执行Y测试用例,在TestSuite类中提供了addTest方法可以实现,也就是说要执行的测试用例按自己期望的执行顺序添加到测试套件中。如下第百度首页进行测试,测试用例的执行顺序是先测试百度新闻,再测试百度地图:
__author__ = 'xiaotufei'
from selenium import webdriver
import unittest
class BaiduTest(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Chrome()
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()
url = self.driver.current_url
self.assertEqual(url,'http://www.baidu.com/')
if __name__ == '__main__':
suite=unittest.TestSuite()
suite.addTest(BaiduTest('test_baidu_news'))
suite.addTest(BaiduTest('test_baidu_map'))
unittest.TestTestRunner(verbosity=2).run(suite)
执行结果如下:
Failure
Expected :'https://map.baidu.com/@12697919.69,2560977.31,12z'
Actual :'http://www.baidu.com/'
<Click to see difference>
Traceback (most recent call last):
File "D:WorkToolspython_workspacePython_unitest14_1_testSuite.py", line 26, in test_baidu_map
self.assertEqual(url,'http://www.baidu.com/')
AssertionError: 'https://map.baidu.com/@12697919.69,2560977.31,12z' != 'http://www.baidu.com/'
- https://map.baidu.com/@12697919.69,2560977.31,12z
+ http://www.baidu.com/
Process finished with exit code 0
备注:在运行代码时出现以下报错:
Error
TypeError: setUpClass() missing 1 required positional argument: 'self'
将setUpClass修改为setUp之后,报错消失。
注意:在以上代码中,首先需要对TestSuite类进行实例化,使之成为一个对象suite,然后调用TestSuite中的addTest方法,把测试用例添加到测试套件中,最后执行测试套件,从而执行测试套件中的测试用例。运行以上代码后,测试用例会按照添加到测试套件的顺序执行,也就是说先添加进去的先执行,后添加进去的后执行。
4.2按测试类执行
在自动化测试中,一般测试用例往往多达数百条,如果完全按照顺序来执行,其一是不符合自动化测试用例的原则,因为在UI自动化中,自动化测试用例最好单独执行,互相之间不影响且没有相互依赖关系。其二是当一个测试类中有很多测试用例时,逐一的向套件中添加用例是一件很繁琐的工作,这时,可以使用makeSuite类按测试类来执行。makeSuite可以实现把测试用例类中所有的测试用例组成测试套件TestSuite,这样可以避免逐一向测试套件中添加测试用例。修改后的测试类中代码如下:
if __name__ == '__main__':
suite=unittest.TestSuite(unittest.makeSuite(BaiduTest))
unittest.TextTestRunner(verbosity=2).run(suite)
4.3加载测试类
在unittest模块中也可以使用TestLoader类来加载测试类,也就是说TestLoader加载测试类并将他们返回添加到TestSuite中,TestLoader类应用代码如下:
if __name__ == '__main__':
suite=unittest.TestLoader().loadTestsFromTestCase(BaiduTest)
unittest.TextTestRunner(verbosity=2).run(suite)
4.4按测试模块执行
在TestLoader类中也可以按模块来执行测试。在Python中,一个Python文件就是一个模块,一个模块中可以有N个测试类,在一个测试类中可以有N个测试用例。按模块执行代码如下:
__author__ = 'xiaotufei'
from selenium import webdriver
import unittest
class BaiduTest(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Chrome()
#self.driver.maximize_window()
self.driver.get('http://www.baidu.com')
self.driver.implicitly_wait(30)
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') #跳过该条case不执行
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.Chrome()
#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('unittest4.py')
unittest.TextTestRunner(verbosity=2).run(suite)
以上代码中,测试类分别是BaiduMap和BaiduTest,模块名称为nittest4.py,TestLoader类直接调用loadTestFromModule方法返回给指定模块中包含的所有测试用例套件。
4.5
以上实例是吧测试套件写在了main主函数中,也可以单独把测试套件写成一个方法来调用。以加载测试类为例,把测试套件写成一个单独的方法:
__author__ = 'xiaotufei'
from selenium import webdriver
import unittest
class BaiduTest(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Chrome()
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()
url = self.driver.current_url
self.assertEqual(url,'http://www.baidu.com/')
@staticmethod
def suite1(testTaseClass):
suite1=unittest.TestLoader().loadTestsFromTestCase(testTaseClass)
return suite1
if __name__ == '__main__':
unittest.TextTestRunner(verbosity=2).run(BaiduTest.suite1(BaiduTest))
运行结果如下:
备注:
运行过程中出现以下问题:
经查找是因为使用了suite关键字,问题代码如下:
@staticmethod
def suite1(testTaseClass):
suite1=unittest.TestLoader().loadTestsFromTestCase(testTaseClass)
return suite1
将suite更改为suite1后,运行成功。
5.分离测试固件
在UI自动化测试中,不管编写哪个模块的的测试用例,都需要首先在测试类中编写测试固件初始化WebDriver类及打开浏览器,测试用例执行完成后还需要关闭浏览器,这部分代码如下:
def setUp(self):
self.driver=webdriver.Chrome()
self.driver.maximize_window()
self.driver.get('http://www.baidu.com')
self.driver.implicitly_wait(30)
def tearDown(self):
self.driver.quit()
在每一个测试类中都需要编写以上代码,因此需要重复编写很多代码。是否可以把测试固件这部分代码分离出去,测试类直接继承分离出去的类呢?我们把测试固件分离到config.py中,类名为InitTest,代码如下:
__author__ = 'xiaotufei'
import unittest
from selenium import webdriver
class InitTest(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Chrome()
self.driver.maximize_window()
self.driver.get('http://www.baidu.com')
self.driver.implicitly_wait(30)
def tearDown(self):
self.driver.quit()
测试类继承了InitTest,继承后,在测试类中直接编写要执行的测试用例:
__author__ = 'xiaotufei'
from config import InitTest
import unittest
class BaiduTest(InitTest):
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()
url = self.driver.current_url
self.assertEqual(url,'http://map.baidu.com/')
if __name__ == '__main__':
unittest.main(verbosity=2)
运行结果:
参考资料:《Python自动化测试实战》——无涯老师著