介绍
什么是unittest?它是Python自带的⼀个单元测试框架,用它来做单元测试。自带:由于是Python自带所以不需要单独安装,只要安装了Python就可以使用;而第三方框架则需要安装后才能使用,如pytest。单元测试框架:用来做单元测试,一般单元测试是开发完成,对于测试来说, unittest 框架的作用是自动化脚本(用例代码) 执行框架(使用unittest 框架来管理运行多个测试用例的)
unittest优点:1. 能够组织多个用例去执行;2. 提供丰富的断言方法(让程序代码代替人工自动的判断预期结果和实际结果是否相符);3. 能够生成测试报告
如何使用unittest?需要按照框架的规定套路去书写代码
unittest的核心要素有5个:
1.TestCase(测试用例):书写真正的用例代码的代码文件
2.TestSuite(测试套件):将多个TestCase即多个代码文件组装起来
3.TestRunner(测试运行):运行打包完成的TestSuite
4.TestLoader(测试加载):是对TestSuite功能的补充
5.Fixture(测试夹具):类似切面,TestCase执行前后都会执行的内容,这个内容主要是多个代码文件重复的部分,未免重复编写,将其都放在Fixture在TestCase执行前后执行,减少代码冗余
核心要素的使用
1.TestCase
步骤:

代码:
# 1, 导包
import unittest
# 2, 自定义测试类, 需要继承 unittest 模块中的 TestCase 类即可
class TestDemo(unittest.TestCase):
# 3, 书写测试方法, 即 用例代码. 目前没有真正的用例代码, 使用 print 代替
# 书写要求, 测试方法 必须以 test_ 开头(本质是以 test 开头)
def test_method1(self):
print('测试方法 1')
def test_method2(self):
print('测试方法 2')
# 4, 执行用例(方法)
# 4.1 将光标放在 类名的后边 运行, 会执行类中的所有的测试方法
# 4.2 将光标放在 方法名的后边 运行, 只执行当前的方法
2.TestSuite&TestRunner
步骤:

代码:
# 1. 导包(unittest)
import unittest
from hm_07_testcase1 import TestDemo1
from hm_07_testcase2 import TestDemo2
# 2. 实例化(创建对象)套件对象,
suite = unittest.TestSuite()
# 3. 使用套件对象添加用例方法
# 方式一, 套件对象.addTest(测试类名('方法名')) # 建议测试类名和方法名直接去复制,不要手写
suite.addTest(TestDemo1('test_method1'))
suite.addTest(TestDemo1('test_method2'))
suite.addTest(TestDemo2('test_method1'))
suite.addTest(TestDemo2('test_method2'))
# 4. 实例化运行对象
runner = unittest.TextTestRunner()
# 5. 使用运行对象去执行套件对象
# 运行对象.run(套件对象)
runner.run(suite)
步骤三的方式二:
# 3. 使用套件对象添加用例方法
# 方式二 将一个测试类中的所有方法进行添加
# 套件对象.addTest(unittest.makeSuite(测试类名))
# 缺点: makeSuite() 不会提示
suite.addTest(unittest.makeSuite(TestDemo1))
suite.addTest(unittest.makeSuite(TestDemo2))
运行结果看符号:. =>执行成功 f=>失败 e=>错误 s=>跳过

3.TestLoader
步骤:

包结构:

代码:
# 1, 导包
import unittest
# 2, 实例化加载对象并添加用例
# unittest.TestLoader().discover('用例所在的路径', '用例的代码文件名')
# 用例所在的路径,建议使用相对路径, 用例的代码文件名可以使用 *(任意多个任意字符) 通配符
# suite = unittest.TestLoader().discover('./case', 'hm*.py')
# suite = unittest.TestLoader().discover('./case', '*test*.py')
# suite = unittest.TestLoader().discover('./case', '*test*')
suite = unittest.TestLoader().discover('./case', '*case1.py')
# 3, 实例化运行对象
# runner = unittest.TextTestRunner()
# # 4, 执行
# runner.run(suite)
# 可以将 3 4 步 变为一步
unittest.TextTestRunner().run(suite)
第二步关于TestLoader的另一种写法:
# 1, 导包
import unittest
# 2, 使用默认的加载对象并加载用例
suite = unittest.defaultTestLoader.discover('case', 'hm_*.py')
# 可以将 3 4 步 变为一步
unittest.TextTestRunner().run(suite)
4.Fixture
方法级别
在每个测试方法(用例代码) 执行前后都会自动调用的结构
# 方法执行之前
def setUp(self):
每个测试方法执行之前都会执行
pass
# 方法执行之后
def tearDown(self):
每个测试方法执行之后都会执行
pass
类级别
在每个测试类中所有方法执行前后都会自动调用的结构(在整个类中执行之前执行之后个一次)
# 类级别的Fixture 方法, 是一个 类方法
# 类中所有方法之前
@classmethod
def setUpClass(cls):
pass
# 类中所有方法之后
@classmethod
def tearDownClass(cls):
pass
模块级别
模块是一个代码文件,在每个代码文件执行前后执行的代码结构
# 模块级别的需要写在类的外边直接定义函数即可
# 代码文件之前
def setUpModule():
pass
# 代码文件之后
def tearDownModule():
pass
案例
1. 打开浏览器(整个测试过程中就打开一次浏览器) 类级别
2. 输入网址(每个测试方法都需要一次) 方法级别
3. 输入用户名密码验证码点击登录(不同的测试数据) 测试方法
4. 关闭当前页面(每个测试方法都需要一次) 方法级别
5. 关闭浏览器(整个测试过程中就关闭一次浏览器) 类级别
代码:
import unittest
class TestLogin(unittest.TestCase):
def setUp(self):
"""每个测试方法执行之前都会先调用的方法"""
print('输入网址......')
def tearDown(self) -> None:
"""每个测试方法执行之后都会调用的方法"""
print('关闭当前页面......')
@classmethod
def setUpClass(cls) -> None:
print('------1. 打开浏览器')
@classmethod
def tearDownClass(cls) -> None:
print('------5. 关闭浏览器')
def test_1(self):
print('输入正确用户名密码验证码,点击登录 1')
def test_2(self):
print('输入错误用户名密码验证码,点击登录 2')
断言
断言即让程序代替人工自动的判断预期结果和实际结果是否相符
断言的结果有两种,True, 用例通过;False, 代码抛出异常, 用例不通过
在 unittest 中使用断言, 都需要通过self.断言方法来试验
assertEqual
self.assertEqual(预期结果, 实际结果) # 判断预期结果和实际结果是否相等
1. 如果相等, 用例通过
2. 如果不相等,用例不通过, 抛出异常
assertIn
self.assertIn(预期结果, 实际结果) # 判断预期结果是否包含在实际结果中
1. 包含 ,用例通过
2. 不包含, 用例不通过, 抛出异常
代码:
登录:
def login(username, password):
if username == 'admin' and password == '123456':
return '登录成功'
else:
return '登录失败'
import unittest
from tools import login
class TestLogin(unittest.TestCase):
def test_username_password_ok(self):
"""正确的用户名和密码: admin, 123456, 登录成功"""
self.assertEqual('登录成功', login('admin', '123456'))
def test_username_error(self):
"""错误的用户名: root, 123456, 登录失败"""
self.assertEqual('登录失败', login('root', '123456'))
def test_password_error(self):
"""错误的密码: admin, 123123, 登录失败"""
self.assertEqual('登录失败', login('admin', '123123'))
def test_username_password_error(self):
"""错误的用户名和错误的密码: aaa, 123123, 登录失败"""
# self.assertEqual('登录失败', login('aaa', '123123'))
self.assertIn('失败', login('aaa', '123123'))
参数化
参数化在测试方法中, 使用变量来代替具体的测试数据, 然后使用传参的方法将测试数据传递给方法的变量
好处:相似的代码不需要多次书写.
工作中场景:
1. 测试数据一般放在 json 文件中
2. 使用代码读取 json 文件,提取我们想要的数据 ---> [(), ()] or [[], []]
步骤:

代码:
f1
# 1. 导包 unittest/ pa
import unittest
from parameterized import parameterized
from tools import login
# 组织测试数据 [(), (), ()] or [[], [], []]
data = [
('admin', '123456', '登录成功'),
('root', '123456', '登录失败'),
('admin', '123123', '登录失败')
]
# 2. 定义测试类
class TestLogin(unittest.TestCase):
# 3. 书写测试方法(用到的测试数据使用变量代替)
@parameterized.expand(data)
def test_login(self, username, password, expect):
self.assertEqual(expect, login(username, password))
# 4. 组织测试数据并传参(装饰器 @)
f2
data.json
[
{
"desc": "正确的用户名和密码",
"username": "admin",
"password": "123456",
"expect": "登录成功"
},
{
"desc": "错误的的用户名",
"username": "root",
"password": "123456",
"expect": "登录失败"
},
{
"desc": "错误的的密码",
"username": "admin",
"password": "123123",
"expect": "登录失败"
}
]
# 1. 导包 unittest/ pa
import json
import unittest
from parameterized import parameterized
from tools import login
# 组织测试数据 [(), (), ()] or [[], [], []]
def build_data():
with open('data.json', encoding='utf-8') as f:
result = json.load(f) # [{}, {}, {}]
data = []
for i in result: # i {}
data.append((i.get('username'), i.get('password'), i.get('expect')))
return data
# 2. 定义测试类
class TestLogin(unittest.TestCase):
# 3. 书写测试方法(用到的测试数据使用变量代替)
@parameterized.expand(build_data())
def test_login(self, username, password, expect):
self.assertEqual(expect, login(username, password))
# 4. 组织测试数据并传参(装饰器 @)
跳过
对于一些未完成的或者不满足测试条件的测试函数和测试类,不想执行,可以使用跳过
使用方法=》 装饰器完成@
代码书写在 TestCase 文件
# 直接将测试函数标记成跳过
@unittest.skip('跳过原因')
# 根据条件判断测试函数是否跳过 , 判断条件成立, 跳过
@unittest.skipIf(判断条件, '跳过原因')
代码:
import unittest
# version = 30
version = 29
class TestDemo(unittest.TestCase):
@unittest.skip('没有什么原因,就是不想执行')
def test_1(self):
print('测试方法 1')
@unittest.skipIf(version >= 30, '版本大于等于 30, 不用测试')
def test_2(self):
print('测试方法 2')
def test_3(self):
print('测试方法 3')
测试报告
自带的测试报告只有在运行TestCase后才会生成,因此要掌握生成第三方的测试报告。
步骤:

b1:
HTMLTestRunner.py
"""
A TestRunner for use with the Python unit testing framework. It
generates a HTML report to show the result at a glance.
The simplest way to use this is to invoke its main method. E.g.
import unittest
import HTMLTestRunner
... define your tests ...
if __name__ == '__main__':
HTMLTestRunner.main()
For more customization options, instantiates a HTMLTestRunner object.
HTMLTestRunner is a counterpart to unittest's TextTestRunner. E.g.
# output to a file
fp = file('my_report.html', 'wb')
runner = HTMLTestRunner.HTMLTestRunner(
stream=fp,
title='My unit test',
description='This demonstrates the report output by HTMLTestRunner.'
)
# Use an external stylesheet.
# See the Template_mixin class for more customizable options
runner.STYLESHEET_TMPL = '<link rel="stylesheet" href="my_stylesheet.css" type="text/css">'
# run the test
runner.run(my_test_suite)
------------------------------------------------------------------------
Copyright (c) 2004-2007, Wai Yip Tung
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name Wai Yip Tung nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
# URL: http://tungwaiyip.info/software/HTMLTestRunner.html
__author__ = "Wai Yip Tung"
__version__ = "0.8.2"
"""
Change History
Version 0.8.2
* Show output inline instead of popup window (Viorel Lupu).
Version in 0.8.1
* Validated XHTML (Wolfgang Borgert).
* Added description of test classes and test cases.
Version in 0.8.0
* Define Template_mixin class for customization.
* Workaround a IE 6 bug that it does not treat <script> block as CDATA.
Version in 0.7.1
* Back port to Python 2


736

被折叠的 条评论
为什么被折叠?



