一、UnitTest框架
UnitTest是目前市场应用主流的测试框架技术之一。其实只是用来做测试用例管理以及生成测试报告的一个模块而已。Junit和UnitTest是同一个家族的成员。
UnitTest是Python的自带官方库之一,所以不需要做任何的环境安装和部署。
UnitTest4大组件:
前置与后置条件:测试流程执行前必须要提前准备好的内容叫做前置条件。测试流程执行结束之后必须要执行的内容叫做后置条件。前置条件一般用于做初始化行为,后置条件一般用于做资源释放的相关操作。前后置条件不会关联到任何业务逻辑。setup与teardown是前置与后置。定义是通过函数来实现。分为函数级别和类级别的前后置。
测试用例:TestCase,在UnitTest中有自己的用例管理模式,包括排序、命名规则等相关。
断言机制:UnitTest封装有自己的断言函数。用于在断言时调用,所有的断言都可以通过self.的方式来进行断言的方法调用。
套件与运行器:套件TestSuite是用于做测试用例的管理的模块,便于整个测试流程的执行、冒烟测试、测试抽查、持续集成等需求的应用。运行器Runner是用于生成测试结果(测试报告),通过运行套件的测试用例来生成最终的测试结果
二、 UnitTest的语法规则
UnitTest的语法规则:
1. 创建一个UnitTest类,是通过继承的方式来实现,继承的对象固定写法是unittest.TestCase
2. 测试用例管理,测试用例在UnitTest中是以函数的形式来实现的。且用例的名称一定要用test开头,否则无法识别为测试用例
个人建议,所有用例命名的时候通过test_序号_名称来定义,例如def test_01_login
3. unittest的调用,一定是在main中调用unittest.main()的方式来实现。
4. 测试用例运行流程:
1. 获取所有的测试用例(包含test开头的函数)
2. 对测试用例进行排序,排序规则是固定的ascii规则进行排序。0-9,A-Z,a-z的顺序
5. 用例在运行过程中,如果没有报错,则表示通过,如果报错,不管是否为断言报错,都会判定为是失败的测试用例。一般不会轻易
添加try..except来进行用例内容的管理
6. UnitTest运行时控制台信息的顺序,不要纠结。因为UnitTest本身打印信息在控制台会出现有乱序的情况。
7. 用例的内容设计的时候,一定要尽可能减少彼此之间的关联性。每一个测试用例尽可能的独立化。更好确保其他的流程用例执行时不受到影响。
复杂业务流程下,可以通过用多个不同的文件,来管理不同的测试流程,
简单业务流程下,可以一个用例实现一个流程。
8. UnitTest支持普通函数的编写。需要注意命名的时候不要用test开头
9. UnitTest支持你进行源码的修改,修改用例排序规则,修改用例更名规则等。应用场景极少,基本不需要,做知识扩充了解即可。'''··
下面我们对UnitTest规则进行详细介绍:
2.1 创建一个UnitTest类
创建一个UnitTest类,是通过继承的方式来实现,继承的对象固定写法是unittest.TestCase
在创建类的同时,我们也需要导包
import unittest
# 创建UnitTest类
class UnitDemo(unittest.TestCase):
2.2 测试用例管理
测试用例管理,测试用例在UnitTest中是以函数的形式来实现的。且用例的名称一定要用test开头,否则无法识别为测试用例。
个人建议,所有用例命名的时候通过test_序号_名称来定义,例如def test_01_login。因为对测试用例的调用是有一定顺序的:0-9,A-Z,a-z的顺序,我们可以看下面的例子:
import unittest
class UnitDemo(unittest.TestCase):
def test_02_login(self):
print("这是测试用例02")
def test_01_simple(self):
print("这是测试用例01")
结果是:
可以看到测试用例执行的时候是按照一定的顺序,并不按照从上到下的顺序。
2.3测试用例的调用
def 定义的test_ 是测试用例,只有执行 if __name__ == '___mian___' 的时候会执行测试用例,其他普通函数则不执行,通过 self 来调用执行。
具体来说就是在测试用例下面加上main方法:
# unittest导包
import unittest
# 创建UnitTest类
class UnitDemo(unittest.TestCase):
# 定义测试用例是通过def来完成
def test_02_login(self):
print('这是测试用例02')
def test_01_simple(self):
print('这是测试用例01')
# UnitTest运行的固定写法
if __name__ == '__main__':
unittest.main()
2.4断言的使用
# 创建UnitTest类
class UnitDemo(unittest.TestCase):
# 定义测试用例是通过def来完成
def test_02_login(self):
print('这是测试用例02')
def test_01_simple(self):
print('这是测试用例01')
def test_03_failed(self):
assert 1 == 2
# UnitTest运行的固定写法
if __name__ == '__main__':
unittest.main()
assert也可以使用在测试用例中, 用例在运行过程中,如果没有报错,则表示通过,如果报错,不管是否为断言报错,都会判定为是失败的测试用例。一般不会轻易, 添加try..except来进行用例内容的管理。
import unittest
class UnitDemo(unittest.TestCase):
def test_02_login(self):
print("这是测试用例02")
def test_01_simple(self):
print("这是测试用例01")
def test_03_falied(self):
assert 1 == 2
def test_04_exception(self):
try:
print(1/0)
except:
print("hei,报错了")
def login5(self):
print("这是login")
#unitTest运行的固定写法
if __name__ == '__main__':
unittest.main()
比如在第四个测试用例里面,通过异常处理机制,纵然1/0会报错。但是由于try..except捕获异常,因此这个用例也会显示通过,看一下以上四个测试用例的执行结果。4条只有用例3没通过。
2.5UnitTest中的普通函数
UnitTest支持普通函数的编写。需要注意命名的时候不要用test开头。
普通函数,在UnitTest中不会被直接运行,有需要的时候通过调用函数来执行。
def test_05(self):
self.login()
# 普通函数,在UnitTest中不会被直接运行,有需要的时候通过调用函数来执行
def login(self):
print('这是login')
我们可以看到是可以被调用的
三、UnitTest前后置
前置与后置条件:
Unittest中前后置是固定写法,都是setup和teardown
1. 前后置只能够定义一个。
2. 前后置级别是函数级和类级。
3. 函数级前后置条件,在每一个测试用例执行前后都会分别执行一次。
4. 类级别的前后置条件,在每一个类运行前后分别执行一次。
5. 不要在前后置中进行逻辑代码的编写,所有的初始化和资源释放,是不需要有逻辑的
3.1函数级setup和teardown
import unittest
class TestSimple(unittest.TestCase):
# 函数级
# 前置条件的定义
def setUp(self):
print('这是setup')
# 后置条件
def tearDown(self) -> None:
print('这是teardown')
当我们去运行以上写的5条测试用例
mport unittest
class TestSimple(unittest.TestCase):
# 函数级
# 前置条件的定义
def setUp(self):
print('这是setup')
# 后置条件
def tearDown(self) -> None:
print('这是teardown')
def test_02_login(self):
print('这是测试用例02')
def test_01_simple(self):
print('这是测试用例01')
def test_03_failed(self):
assert 1 == 2
def test_04_exception(self):
try:
print(1 / 0)
except:
print('嘿! 我报错啦~')
def test_05(self):
self.login()
加了函数级的用例执行:在每一个测试用例执行前后都会分别执行一次。如果有多个setup,只会按照第一个setup。
3.2类级别setup和teardown
# 类级别
@classmethod
def setUpClass(cls) -> None:
print('这是类级别的setup')
cls.a = 'hcc'
@classmethod
def tearDownClass(cls) -> None:
print('这是类级别的teardown')
我们加载之后发现报错了
在定义类级别的setup时,我们一定要加@classmethod装饰器,证明他是类级别的
类级别的前后置条件,只会在整个类前后运行一次。
四、断言的使用
在UnitTest中,提供有非常多的自定义断言内容,直接通过self.的方式就可以直接调用。其中,所定义的这些断言机制,已经覆盖有非常非常多的内容了。所以在进行自动化测试时,我们可以直接通过调用自带的断言来实现用例的通过与否的判断。
那些以assert开头的方法都是可以用作断言的方法:
4.1断言常见方法
- assertEqual() # 判断两者是否相等
- assertTrue() # 判断目标是否为真
- assertIn() # 判断是否包含
- self.assertIs() #判断是否是
具体使用:
assertEqual(参数1,参数2,msg) 方法的使用:参数1等于参数2断言成功,参数1不等于参数2断言失败,发送msg
class UnitDemo(unittest.TestCase):
def test_01(self):
self.assertEqual(1,2,msg='断言失败')#判断两者是否相等
if __name__ == '__main__':
unittest.main()
assertTrue(参数1,msg)方法的使用:参数1为真,断言成功;参数1不为真,发送msg
def test_02(self):
self.assertTrue(False,msg='断言失败')#判断目标是否为真
assertIn(参数1,参数2,msg) 方法的使用:参数1在参数2断言成功,参数1在参数2断言失败,发送msg
def test_03(self):
self.assertIn('a','abc',msg='断言成功')
assertIn(参数1,参数2,msg)方法的使用:参数1是参数2类型的,断言成功,参数1不是参数2类型的,断言失败,发送msg
def test_04(self):
self.assertIs(type(1),int,msg='断言呦呦呦失败了')
五、测试套件与运行器
5.1测试套件与运行器的使用
在UnitTest中,创建测试套件,必须要在新的py文件中进行创建,在原有UnitTest文件中创建,运行时无效。
测数套件:
是用于专门管理UnitTest中所有测试用例的类。相当于是文件系统中的文件夹。而测试用例则是一个个的文件。
套件在实际使用中,可以不遵循UnitTest的用例排序规则,遵循的规则是添加顺序的规则。
测试套件,是通过unittest.TestSuite类来实现运行器:
所有的测试套件,都是基于运行器来执行的,所以有套件情况下,必须通过运行器来实现执行。运行器其实就是测试报告的生成
unittest中默认的运行器是TextTestRunner。
如何使用测试套件和运行器呢?
- 创建测试套件对象
- 将测试用例添加到测试套件中
- 生成运行器
- 运行运行器
具体代码如下:
#创建一个套件对象 suite = unittest.TestSuite() #1.基于测试用例的名称来进行用例添加 #添加测试用例到一个套件中 suite.addTest(UnitDemo1('test_02_login'))这里是提前准备好的测试用例test_02_login suite.addTest(UnitDemo1('test_03_failed'))这里是提前准备好的测试用例test_03_failed #运行器的生成 run = unittest.TextTestRunner() #运行运行器 run.run(suite)
我们看一下结果:
测试用例执行状态:
. 表示PASS
F 表示Failed
5.2添加测试用例的方法
1. 基于测试用例的名称来进行用例添加
# suite.addTest(UnitDemo('test_02_login'))
# suite.addTest(UnitDemo('test_03_failed'))
2. 批量添加测试用例:通过list把所有的用例名称保存,然后进行套件的用例添加
# cases = [UnitDemo('test_02_login'), UnitDemo('test_03_failed'), UnitDemo('test_04_exception')]
# suite.addTests(cases)
3. 基于class名称来实现用例的添加:将制定的class中所有的测试用例全部添加进来
# suite.addTests(unittest.TestLoader().loadTestsFromTestCase(UnitDemo1))
4. 基于py文件来实现用例的添加:基于py文件的文件名称来实现用例的添加,添加的必须是当前路径下的py文件,不然容易报错。
names = ['unittest_demo.UnitDemo', 'unittest_demo.UnitDemo1']
case = unittest.TestLoader().loadTestsFromNames(names)
suite.addTest(case)
名称的定义一定要用py文件名称+class名称的形式来实现。
5. 基于discover方式来实现用例的添加:个人推荐的一种方式,可以批量添加符合规则的文件的所有用例内容,以及搭配持续集成实现自动化持续集成
获取测试用例
#获取测试用例 discover = unittest.defaultTestLoader.discover(start_dir=path, #运行器的生成 pattern='test*.py') run = unittest.TextTestRunner() #运行运行器 run.run(discover)
- 用例的获取路径:path = './' ./表示当前路径
- start_dir表示用例获取的路径
- pattern表示文件名称
- discover默认返回一个测试套件
5.3运行器的测试报告参数
所有的测试套件,都是基于运行器来执行的,所以有套件情况下,必须通过运行器来实现执行。运行器其实就是测试报告的生成 unittest中默认的运行器是TextTestRunner
运行器可以在控制台生成一个简易版的测试报告,这个报告的详略程度我们是可以控制的。主要是通过verbosity参数来进行控制的。
run = unittest.TextTestRunner(verbosity=2)
默认是1,详细版是2,0不用填写,没有意义
六、HTMLTestRunner测试报告
HTMLTestRunner测试报告生成:UnitTest中非常主流应用的测试报告运行器,不是UnitTest官方自带的。HTMLTestRunner生成html格式的测试报告 。
安装:将文件直接复制到python安装路径下的Lib文件家中:D:\Python\Lib 网上下载的HTMLTestRunner本身默认支持python2.7的版本,要3的版本中使用,必须要修改源码的内容。 不要pip安装
HTMLTestReport测试报告生成,相比较HTMLTestRunner而言,多了一个tester参数,用于描述测试人。除此之外一切相同。 安装:将文件直接复制到python安装路径下的Lib文件家中:
对mac来说:
在访达中command+shift+G,在弹出的输入框中输入:/资源库/Frameworks/Python.framework/Versions/3.6/lib/python3.6,找到这个目录后把文件放在这个文件夹里面就可以了
首先我们要配置一些相关信息
我们要提前准备一些信息:保存路径、测试报告的标题、测试报告描述的相关信息、测试报告
文件名称以及完整的路径
report_dir = './report/' # 保存路径
report_title = '晴天的测试报告' # 测试报告的标题
report_description = '测试报告中的描述部分的内容' # 测试报告的描述相关信息
# 测试报告的文件名称及完整路径:为了将每一份测试报告都能够保存下来,建议在用例命名的时候通过时间戳的形式来实现
report_file = report_dir + 'report.html'
然后判断一个测试报告是否存在
if not os.path.exists(report_dir):
os.mkdir(report_dir)
然后生成一个测试报告,其实也就是文件的写入操作
with open(report_file, 'wb') as file:
run = HTMLTestRunner(stream=file, title=report_title, description=report_description, verbosity=2)
run.run(discover)
运行之后,可以看到生成一个测试报告
七、skip装饰器
Skip装饰器的使用,用于做用例的跳过管理,skip装饰器只有三个,用于管理用例是否跳过。
UnitTest下还有一个特殊的装饰器叫做@unittest.expectedFailure,在运行前已经预期会失败了。默认执行会失败 。
无条件跳过的装饰器 有条件跳过,符合条件,跳过- 有条件跳过,与if相反,当条件为False时跳过
import unittest
class UnitDemo(unittest.TestCase):
#无条件跳过的装饰器
@unittest.skip('这是无条件跳过')
def test_01(self):
print('1')
#有条件跳过
@unittest.skipIf(1==1,'这是skipIf的reason')
def test_02(self):
print('2')
#有条件跳过,与if相反,当条件为False时跳过
@unittest.skipUnless(1==1,'这是skipUnless的reason')
def test_03(self):
print('3')
if __name__ == '__main__':
unittest.main