单元测试框架unittest-2
6.测试断言
断言就是判断实际测试结果与预期结果是否一致,一致则测试通过,否则失败。因此,在自动化测试中,无断言的测试用例是无效的。这是因为当一个功能自动化已全部实现,在每次版本迭代中执行测试用例时,执行的结果必须是权威的,也就是说自动化测试用例执行结果应该无功能性和逻辑性问题。在自动化测试中最忌讳的就是自动化测试的用例功能测试虽然是通过的,但是被测功能本身却是存在问题的。自动化测试用例经常应用在回归测试中,发现的问题不是特别多,如果测试结果存在功能上的问题,自动化测试就没有太大意义了。
所以每一个测试用例必须要有断言。测试结果只有两种可能,通过or失败。在TestCase类中提供了assert方法来检查和报告失败,常用的方法如下:
Method | Checks that | New in |
---|---|---|
assertEqual(a,b) | a == b | |
assertNotEqual(a,b) | a != b | |
assertTrue(x) | bool(x) is True | |
assertFalse(x) | bool(x) is False | |
assertIs(a,b) | a is b | 3.1 |
assertIsNot(a,b) | a is not b | 3.1 |
assertIsNone(x) | x is None | 3.1 |
assertIsNotNone(x) | x is not None | 3.1 |
assertIn(a,b) | a in b | 3.1 |
assertNotIn(a,b) | a not in b | 3.1 |
assertassertIsInstance(a,b) | isinstance(a,b) | 3.2 |
assertNotIsInstance(a,b) | not isinstance(a,b) | 3.2 |
6.1 assertEqual
assertEqual方法用于测试两个值是否相等,如果不相等则测试失败。注意“相等”指的是内容相同且类型相同。例如,两个值虽然内容一致,但一个是bytes类型一个是str类型,则两个值仍为不相等,测试失败。如:
__author__ = 'xiaotufei'
import unittest
from selenium import webdriver
class BaiduTest(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Chrome()
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类型后,内容一致,类型不一致,所以失败:
D:\Python\python.exe D:/Work/Tools/python_workspace/Python_unitest/1/assert6.py
test_baidu_title (__main__.BaiduTest) ... FAIL
======================================================================
FAIL: test_baidu_title (__main__.BaiduTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:/Work/Tools/python_workspace/Python_unitest/1/assert6.py", line 17, in test_baidu_title
self.assertEqual(self.driver.title,'百度一下,你就知道'.encode('gbk'))
AssertionError: '百度一下,你就知道' != b'\xb0\xd9\xb6\xc8\xd2\xbb\xcf\xc2\xa3\xac\xc4\xe3\xbe\xcd\xd6\xaa\xb5\xc0'
----------------------------------------------------------------------
Ran 1 test in 5.785s
FAILED (failures=1)
Process finished with exit code 1
接下来我们把转化类型的代码去掉:
def test_baidu_title(self):
#验证测试百度首页的title
self.assertEqual(self.driver.title,'百度一下,你就知道')
此时内容一致,类型也一致,执行结果如下:
D:\Python\python.exe D:/Work/Tools/python_workspace/Python_unitest/1/assert6.py
test_baidu_title (__main__.BaiduTest) ... ok
----------------------------------------------------------------------
Ran 1 test in 5.543s
OK
Process finished with exit code 0
6.2. assertTrue
assertTrue返回的是布尔类型,他主要对返回的测试结果执行布尔类型的校验。例如,验证百度搜索输入框是否可编辑,is_enabled方法返回的结果是True,代码如下:
def test_baidu_news(self):
#验证百度搜索输入框是否可编辑
so=self.driver.find_element_by_id('kw')
self.assertTrue(so.is_enabled())
6.3.assertFalse
assertFalse和assertTrue一样,都是返回的布尔类型进行校验。不同的是,assertFalse要求返回的结果是False,测试用例才会执行成功。以新浪邮箱登录页面为例,取消自动登录按钮后,方法is_enabled返回的是False,代码如下:
class BaiduTest(InitTest):
def test_baidu_news(self):
#验证新浪邮箱页面取消自动登录
isautoLogin=self.driver.find_element_by_id('store1')
isautoLogin.click() #取勾自动登录
self.assertFalse(isautoLogin.is_selected()) #判断该元素是否被选中,预期结果是False,即未被选中。
执行结果如下:
D:\Python\python.exe D:/Work/Tools/python_workspace/Python_unitest/1/assertFalse63.py
test_baidu_news (__main__.BaiduTest) ... ok
----------------------------------------------------------------------
Ran 1 test in 7.553s
OK
Process finished with exit code 0
6.4. assertIn
assertIn指的是一个值是否包含在另外一个值的范围内。还是以百度首页URL为例,测试https://www.baidu.com/是否在https://www.baidu.com/的范围内,代码如下:
class BaiduTest(InitTest):
def test_baidu_news(self):
self.assertIn(self.driver.current_url,'https://www.baidu.com/')
7.断言的注意事项
测试用例写的不规范,会导致即使产品存在功能有问题,但是测试用例执行结果仍为正确。出现这种情况,多是因为在测试过程中,使用了不正确额if应用和异常应用。下面从这两个维度来解答这个错误信息是如何发生的,以及如何避免。
7.1 不正确的if应用
还是以新浪登录为例,来测试自动登录按钮是否选中,该代码中引入了判断的代码如下:
__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('https://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('store1')
isAutoLogin.click()
if isAutoLogin.is_selected():
print('Success')
else:
print('Fail')
if __name__ == '__main__':
unittest.main(verbosity=2)
执行结果如下:
D:\Python\python.exe "D:\Work\Tools\JetBrains\PyCharm Community Edition 3.4.4\helpers\pycharm\utrunner.py" D:\Work\Tools\python_workspace\Python_unitest\1\assert7.py true
Testing started at 20:26 ...
D:\Work\Tools\JetBrains\PyCharm Community Edition 3.4.4\helpers\pycharm\utrunner.py:2: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
import imp
Fail
Process finished with exit code 0
随后我们注释掉一行代码:
def test_sina_login(self):
isAutoLogin = self.driver.find_element_by_id('store1')
#isAutoLogin.click()
if isAutoLogin.is_selected():
print('Success')
else:
print('Fail')
运行结果如下:
D:\Python\python.exe "D:\Work\Tools\JetBrains\PyCharm Community Edition 3.4.4\helpers\pycharm\utrunner.py" D:\Work\Tools\python_workspace\Python_unitest\1\assert7.py true
Testing started at 20:24 ...
D:\Work\Tools\JetBrains\PyCharm Community Edition 3.4.4\helpers\pycharm\utrunner.py:2: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
import imp
Success
Process finished with exit code 0
由截图可以看到,虽然控制台分别输出了Success和Fail,但是测试用例的结果都是显示PASS,也就是说两种情况下测试用例都是执行通过的。因此是在自动化测试的测试用例中,切记不要使用if else这类判断代码来代替断言。
7.2不正确的异常应用
还是新浪登录页面:
def test_sina_login(self):
isAutoLogin = self.driver.find_element_by_id('store1')
#isAutoLogin.click()
try:
self.assertTrue(isAutoLogin.is_selected())
except:
print('Fail')
执行结果:
D:\Python\python.exe "D:\Work\Tools\JetBrains\PyCharm Community Edition 3.4.4\helpers\pycharm\utrunner.py" D:\Work\Tools\python_workspace\Python_unitest\1\assert72.py true
Testing started at 20:34 ...
D:\Work\Tools\JetBrains\PyCharm Community Edition 3.4.4\helpers\pycharm\utrunner.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
随后我们取消注释掉的一句代码:
def test_sina_login(self):
isAutoLogin = self.driver.find_element_by_id('store1')
isAutoLogin.click()
try:
self.assertTrue(isAutoLogin.is_selected())
except:
print('Fail')
D:\Python\python.exe "D:\Work\Tools\JetBrains\PyCharm Community Edition 3.4.4\helpers\pycharm\utrunner.py" D:\Work\Tools\python_workspace\Python_unitest\1\assert72.py true
Testing started at 20:37 ...
D:\Work\Tools\JetBrains\PyCharm Community Edition 3.4.4\helpers\pycharm\utrunner.py:2: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
import imp
Fail
Process finished with exit code 0
以上代码和if else一样,不管自动登录是否被选中,测试用例的执行结果都是PASS。另外,在自动化测试中尽量不要应用打印结果来判断测试用例的情况,用例如果在代码错误或者功能有bug的情况下就让用力报错或者失败,而不是结果显示pass,只有在功能正常的情况下测试用例执行结果才能是Pass的。
8.批量执行测试用例
在实际测试中,常常需要批量执行测试用例。例如,在TestCase包中有test_baidu.py和test_sina.py两个文件,下面批量执行这两个模块的测试用例。创建新文件allTest.py,在allTest.py文件中编写批量执行的代码,test_baidu.py模块代码如下:
__author__ = 'xiaotufei'
import unittest
from selenium import webdriver
class BaiduTest(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,'百度一下,你就知道')
if __name__ == '__main__':
unittest.main(verbosity=2)
test_sina.py模块代码如下:
__author__ = 'xiaotufei'
import unittest
from selenium import webdriver
class BaiduTest(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Chrome()
self.driver.maximize_window()
self.driver.implicitly_wait(30)
self.driver.get('https://mail.sina.com.cn/')
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/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)
allTest.py模块测试代码如下:
__author__ = 'xiaotufei'
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):
运行以上AllTest.py文件后,测试结果如下:
D:\Python\python.exe D:/Work/Tools/python_workspace/Python_unitest/1/allTest.py
test_baidu_title (test_baidu.BaiduTest) ... ok
test_username_password_null (test_sina.BaiduTest) ... ok
----------------------------------------------------------------------
Ran 2 tests in 14.710s
OK
Process finished with exit code 0
执行过程中出现以下报错:
D:\Python\python.exe D:/Work/Tools/python_workspace/Python_unitest/1/allTests1.py
Traceback (most recent call last):
File "D:/Work/Tools/python_workspace/Python_unitest/1/allTests1.py", line 32, in <module>
run()
File "D:/Work/Tools/python_workspace/Python_unitest/1/allTests1.py", line 29, in run
runner.run(allTests())
File "D:/Work/Tools/python_workspace/Python_unitest/1/allTests1.py", line 12, in allTests
suite = unittest.defaultTestLoader().discover(
TypeError: 'TestLoader' object is not callable
Process finished with exit code 1
经查找原因系此处多了一个括号:
去掉括号后运行正常。源码如下:
defaultTestLoader = TestLoader()
暗戳戳的表示没看懂,大概是自带了括号叭。。。。不管了,以后再考虑这个问题。。。
9.生成测试报告
运行allTest.py文件后得到的测试结果不够专业,无法直接提交,因此需要借助第三方库生成HTML格式的测试报告。下载HTMLTestRunner.py,下载地址:
https://github.com/tungwaiyip/HTMLTestRunner
下载HTMLTestRunner.py文件后,把文件放到Python安装路径的Lib子文件夹中。创建report文件夹,与TestCase包放在同一个目录下,继续完善allTest.py文件,最终生成测试报告,最终的allTest.py代码如下:
__author__ = 'xiaotufei'
import unittest
import os
import HTMLTestRunner
import time
def allTests():
'''获取所有要执行的测试用例'''
suite = unittest.defaultTestLoader.discover(
start_dir=os.path.join(os.path.dirname(__file__), "testCase1"),
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())
fp.close
if __name__ == '__main__':
run()
执行结果如下:
D:\Python\python.exe D:/Work/Tools/python_workspace/Python_unitest/1/allTests1.py
..
Time Elapsed: 0:00:13.868161
Process finished with exit code 0
可以看到在report文件夹下生成了最新的测试报告:
浏览器打开HTML文件如下:
也可以使用修改过的HTMLTestRunnerNew,生成的报告更友好:
参考资料:《Python自动化测试实战》——无涯老师著