测试开发基础知识与unittest使用
测试流程
- 分析需求
- 制定测试计划
设计测试用例
- 测试方向
具体设计
- 黑白盒测试
- 手工和自动测试
- 冒烟测试: 测试前先验证基本功能是否能实现,是否具有可测性
- 执行测试: 搭建环境、冒烟测试、正式测试
- 测试评估
测试方向
- 功能测试: 各个功能是否正常,输入是否有限制
- 易用性测试: 是否有各种提示
- 界面测试: ui界面是否有错,风格是否统一
- 性能测试: 加载时间,高并发下性能,弱网环境下,耗电量
- 安全测试: 敏感内容处理,多用户单设备,单用户多设备
- 兼容性测试: 设备、语言、系统等
黑盒白盒
白盒测试(White Box Testing):
- 定义:白盒测试是一种测试方法,它基于对软件内部结构、设计和代码的理解来进行测试。
- 特点:测试人员需要访问源代码和系统内部结构,以设计测试用例和确定测试覆盖范围。
- 目的:主要测试逻辑路径、控制流、数据流和代码覆盖率,以发现潜在的错误和漏洞。
黑盒测试(Black Box Testing):
- 定义:黑盒测试是一种测试方法,它不考虑程序的内部逻辑和结构,而是通过测试软件的功能和输入/输出来评估系统的正确性。
- 特点:测试人员只需关注软件的外部行为,无需了解其内部实现。
- 目的:测试系统是否符合规格说明书和用户需求,发现功能性、性能和安全方面的问题。
python assert
assert即断言,用于测试代码的正确性,并在条件不满足时引发异常或输出错误消息。
# 测试用例
def test_something():
result = one_class.one_function("some_stuff")
# 断言结果是否等于期望值
assert result == "right_answer", "message showing it's not valid"
assert expression [, message]
即,对assert后的表达式进行判断,如果为False则引发 AssertionError
异常,使用可选的参数[, message]
输出自定义异常信息
值得注意的是,如果表达式为True,程序什么也不会做并会继续执行,而若为False,抛出异常后会中断测试,如果使用unittest
的assertEqual
方法则可以在抛出异常且不中断测试的情况下继续
单元测试
使用python中的unittest
包来实现自动化的单元测试
其有四个模块
TestLoader
TestCase
TestSuite
TestRunner
TestResult
一个TestCase
的实例就是一个测试用例。什么是测试用例呢?就是一个完整的测试流程,包括测试前准备环境的搭建(setUp
),执行测试代码(run
),以及测试后环境的还原(tearDown
)。其中setUP
,teaerDown
这种对一个测试用例环境的搭建和销毁,是一个fixture
而多个测试用例集合在一起,就是TestSuite
,而且TestSuite
也可以嵌套TestSuite
TestLoader
是用来加载TestCase
到TestSuite
中的
TestRunner
是来执行测试用例的
测试的结果会保存到TextTestResult
实例中,包括运行了多少测试用例,成功了多少,失败了多少等信息。
断言方法
上文提到过,使用assert
时,如果抛出异常,测试会直接中断,为了实现连续的单元测试,unittest
提供了一系列断言方法
self.assertEqual(result, 3 , "Should be 3")
assert result == 3, "Result should be 3"
从逻辑上来说,这两种写法是一致的,unittest
中提供的其他方法如下:
assertEqual(a, b)
:检查a
和b
是否相等。assertTrue(x)
:检查x
是否为True。assertFalse(x)
:检查x
是否为False。assertIs(a, b)
:检查a
和b
是否是同一个对象。assertIsNot(a, b)
:检查a
和b
是否不是同一个对象。assertIn(a, b)
:检查a
是否在b
中。assertNotIn(a, b)
:检查a
是否不在b
中。
上述断言方法都有其他参数可以传入,比如常用的[, message]
默认情况
unittest给了一些适合编写简单测试的默认规则
- 创建一个测试用的类,传参
unittest.TestCase
以继承 - 定义数个以
test_
开头的方法,会被自动识别 - 在主函数中使用
unittest.main()
自动运行全部的testcase
运行unittest.main()
时可以加入参数verbosity
可选值0、1、2,决定了测试输出的详细度(递增)
例:
import unittest
class CalculateSth:
def __init__(self,a,b):
self.a = a
self.b = b
def add(self):
return self.a+self.b
class TestCalculate(unittest.TestCase):
def test_two_positive_numbers(self):
cal = CalculateSth(1,2)
result = cal.add()
self.assertEqual(result, 3 , "Should be 3")
if __name__ == '__main__':
unittest.main(verbosity=1)
组织TestSuite
上述运行有一个弊端:无法决定测试样例运行的顺序。要按照一定顺序执行TestCase
,需要将用例组织进一个TestSuite
import unittest
import SomeTestFunc
if __name__ == '__main__':
suite = unittest.TestSuite()
tests = [SomeTestFunc("test_one"), SomeTestFunc("test_two"), SomeTestFunc("test_three")]
suite.addTests(tests)
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)
将结果输出到文件中
with open('UnittestTextReport.txt', 'a') as f:
runner = unittest.TextTestRunner(stream=f, verbosity=2)
runner.run(suite)
将结果输出为HTML页面
可以使用第三方库HTMLTestRunner
,这样在使用TestRunner
时可以使用类似的方法输出到一个html文件中
with open('HTMLReport.html', 'w') as f:
runner = HTMLTestRunner(stream=f,
title='title',
description='generated by HTMLTestRunner.',
verbosity=2
)
runner.run(suite)
======================or==============================
report_path = "./report.html"
report = HTMLTestReport(report_path,
title='title',
description='generated by HTMLTestRunner.',
verbosity=2)
# 执行测试套件
report.run(suite)
Test Fixture
考虑到测试时有时需要在测试前进行一些准备(比如连接数据库等等)并在测试后进行一些善后操作(比如断开连接)TestCase
提供了两个方法,我们可以在测试类中重写这两个方法
def setUp(self):
print "do something before a case."
def tearDown(self):
print "do something after a case."
这样,在每次执行一个case
前后,我们都会进行一系列固定的操作
但是如果我们仅需要在执行所有的case
前后进行一些固定的操作,我们需要重写两个另外的方法类
@classmethod
def setUpClass(cls):
print "do something before all cases."
@classmethod
def tearDownClass(cls):
print "do something after all cases"
跳过某些case
使用@unittest.skip()
装饰器修饰特定的case以跳过,其仍会被计数在结果中,但并不会被执行
skip装饰器一共有三个
unittest.skip(reason)
unittest.skipIf(condition, reason)
unittest.skipUnless(condition, reason)
其中skip
无条件跳过,skipIf
当condition为True时跳过,skipUnless
当condition为False时跳过,reason可以是一个字符串,为你跳过的理由