借助于Python自带的pydoc模块,可以非常方便地查看、生成帮助文档,该文档是HTML格式的,因此查看、使用起来都非常方便。
MY_NAME = '冰蓝工作室'
def say_hi(name):
'''
定义一个打招呼的函数
返回对指定用户打招呼的字符串
'''
print("执行say_hi函数")
return name + '您好!'
def print_rect(height, width):
'''
定义一个打印矩形的函数
:param heiht: 代表矩形的高
:param width: 代表矩形的宽
'''
print(('*' * width + '\n') * height)
class User:
NATIONAL = 'China'
'''
定义一个代表用户的类
该类包括name、age两个变量
'''
def __init__(self, name, age):
'''
name初始化该用户的name
age初始化该用户的age
'''
self.name = name
self.age = age
def eat(food):
'''
定义用户吃东西方法。
food——代表正在吃的东西
'''
print('{0}正在吃{1}'.format(self.name, food))
使用pydoc模块在控制台中查看帮助文档的命令如下:
python -m pydoc 模块名
其中-m是python命令的一个选项,表示运行指定模块,此处表示运行pydoc模块。
在代码目录下运行命令 python -m pydoc ibmodule
得到如下图输出结果:
使用pydoc模块生成HTML帮助文档的命令如下:
python -m pydoc -w 模块名
以上命令中额外指定了-w选项,该选项代表write,表明输出HTML文档。
可以看到,在模块所在目录下生成了一个ibmodule.html文件,该文件更加清晰的描述了模块的帮助信息。
pydoc还可用于为指定目录生成HTML文档。如:python -m pydoc -w 目录名
python -m pydoc -p 端口号
以上命令在指定端口启动HTTP服务器,接下来用户可以通过浏览器来查看python的所有模块的文档信息。
pydoc还提供了一个-k选项,该选项用于查找模块。
python -m pydoc -k 被搜索模块的部分内容
软件测试是保证软件质量的重要手段之一。
IEEE定义:测试是使用人工和自动手段来运行或检测某个系统的过程,其目的在于检验系统是否满足规定的需求,或者弄清预期结果与实际结果之间的差别。
软件测试基本原则:
从软件工程的总体把握来分,软件测试可以分为:
从软件测试工程的大小来分,软件测试又可分为如下类别:
软件开发常规流程:
主流Bug管理工具:
所谓文档测试,指的是通过doctest模块运行Python源文件的说明文档中的测试用例,从而生成测试报告。
文档测试工具可以提取说明文档中的测试用例,其中“>>>”之后的内容表示测试用例, 接下来的一行则代表该测试用例的输出结果。文档测试工具会判断测试用例的运行结果与输出结果是否一致,如果不一致就会显示错误信息。
def square(x):
'''
一个用于计算平方的函数
:param x: 待计算的数
:return: 计算结果
例如:
>>> square(2)
4
>>> square(3)
9
>>> square(-3)
9
>>> square(0)
0
'''
return x * 2 # 故意写错
class User:
'''
定义一个代表用户的类,该类包含如下两个属性
name:代表用户的名字
age:代表用户的年龄
例如:
>>> u = User('ib-top', 9)
>>> u.name
'ib-top'
>>> u.age
9
>>> u.say('i love python')
'ib-top说:i love python'
'''
def __init__(self, name, age):
self.name = 'ib_top' # 故意写错
self.age = age
def say(self, content):
return self.name + '说:' + content
if __name__ == '__main__':
import doctest
doctest.testmod()
单元测试是一种小粒度的测试,用以测试某个功能或代码块。
好处:
被测对象:
任务:
被测单元的接口测试指的是它与其他程序单元的通信接口,如调用方法的方法名、形参等。
接口测试是单元测试的基础,只有在数据能正确输入、输出的前提下,其他测试才有意义。
接口测试考虑因素:
如果被测程序单元还包含来自外部的输入/输出,则还应考虑如下因素:
局部变量往往是错误的根源,应仔细设计测试用例,确保能发现以下错误:
除此之外,如果被测程序单元还与程序中的全局变量耦合,那么在进行单元测试时还应该查清全局变量对被测单元的影响。
软件测试还应着重检查一下问题:
单元测试用例设计最基本、最简单的方法就是边界值分析。指测试经验表明大量的错误都发生在输入/输出范围的边界条件上,而不是某个范围内部。
单元测试的逻辑覆盖包括:
设计若干测试用例,执行被测程序,是的每条可执行语句都至少执行一次。语句覆盖是最弱的逻辑覆盖测试
def test(a, b, m):
if a > 10 and b == 0:
m = a + b
if a == 20 or m > 15:
m += 1
以上代码的执行流程如下:
对于以上代码,可设计如下测试用例:
设计若干测试用例,执行被测程序,使得程序中每个判断的取真分支和取假分支都至少执行一次,即判断真假值均得到满足,因此又被称为分支覆盖。
测试用例 | a,b,m取值 | 通过路径 |
---|---|---|
用例1 | 20,0,3 | ACE |
用例2 | 10,0,3 | ABD |
用例3 | 12,0,3 | ACD |
用例4 | 20,1,1 | ABE |
设计若干测试用例,执行被测试程序,使得每个判断中每个条件的可能取值都至少满足一次。
前面代码中一共包含以下几种情况:
(1) a>10,取真值,记为T1
(2) a>10,取假值,即a <= 10,记为F1
(3) b0,取真值,记为T2
(4) b0,取假值,即b != 0,记为F2
(5) a20,取真值,记为T3
(6) a20,取假值,即a != 20,记为F3
(7) m>15,取真值,记为T4
(8)m>15,取假值,即m <= 15,记为F4
测试用例 | a,b,m取值 | 通过路径 | 覆盖条件 |
---|---|---|---|
用例1 | 20,0,1 | ACE | T1, T2, T3, T4 |
用例2 | 5,0,2 | ABD | F1,T2,F3,F4 |
用例3 | 20,1,5 | ABE | T1,F2,T3,F4 |
3 个测试用例覆盖了4 个条件的8种情况。进一步分析发现, 3个测试用例也把两个判断的4个分支B 、C 、D 和E都覆盖了。但满足条件覆盖的测试用例不一定满足判定覆盖。
求设计足够多的测试用例,使得判断中每个条件所有可能的组合都至少出现一次,并且每个判断本身的判定结果也至少出现一次。
测试用例 | a,b,m取值 | 通过路径 | 覆盖条件 |
---|---|---|---|
用例1 | 20,0,3 | ACE | T1, T2, T3, T4 |
用例2 | 20,1,10 | ABC | T1,T2,T3,F4 |
用例3 | 10,0,20 | ABE | F1,T2,F3,T4 |
用例4 | 1,2,3 | ABD | F1,F2,F3,F4 |
设计足够多的测试用例,覆盖程序中所有可能的路径。
测试用例 | a,b,m取值 | 通过路径 |
---|---|---|
用例1 | 20,0,3 | ACE |
用例2 | 5,0,2 | ABD |
用例3 | 20,1,10 | ABE |
用例4 | 12,0,7 | ACD |
测试的目的不是要证明程序的正确性,而是要尽可能找出程序中的缺陷。没有完备的测试方法,也就没有完备的测试活动。
PyUnit是Python自带的单元测试框架,用于编写和运行可重复的测试。PyUnit 是xUnit 体系的一个成员, xUnit 是众多测试框架的总称,PyUnit 主要用于进行白盒测试和回归测试。
好处:
特征:
测试的本质:通过给定参数来执行函数,然后判断函数的实际输出结果和期望输出结果是否一致。
PyUnit基于断言机制来判断函数或方法的实际输出结果和期望输出结果是否一致。
测试驱动开发方式强调先编写测试用例,然后再编写函数和方法。
实例:
def one_equation(a, b):
'''
求一元一次方程a * x + b = 0的解
:param a: 方程中变量的系数
:param b: 房中的常量
:return: 方程的解
'''
# 如果a = 0,则方程无法求解
if a == 0:
raise ValueError("参数错误")
# 返回方程的解
else:
return -b / a
def two_equation(a, b, c):
'''
求一元二次方程a * x * x + b * x + c = 0的解
:param a: 方程中变量二次幂的系数
:param b: 方程中变量的系数
:param c: 方程中的常量
:return: 方程的解
'''
# 如果 a == 0 ,则变成一元一次方程
if a == 0:
raise ValueError("参数错误")
# 在有理数范围内无解
elif b * b - 4 * a * c < 0:
raise ValueError("方程在有理数范围内无解")
# 方程有唯一的解
elif b * b - 4 * a * c == 0:
# 使用数组返回方程的解
return -b / (2 * a)
# 方程有两个解
else:
r1 = (-b + (b * b - 4 * a * c)**0.5) / 2 / a
r2 = (-b - (b * b - 4 * a * c)**0.5) / 2 / a
return r1, r2
定义好以上程序之后,该程序就相当于一个模块,接下来就可以为该模块编写单元测试代码了。
unittest要求单元测试类必须继承unittest.TestCase,该类中的测试方法满足如下要求:
下面是测试用例的代码:
import unittest
from tb_math import *
class TestIbModule(unittest.TestCase):
# 测试一元一次方程的解
def test_one_equation(self):
# 断言该方程的解为-1.8
self.assertEqual(one_equation(5, 9), -1.8)
# 断言该方程的解应该为-2.5
self.assertTrue(one_equation(4, 10) == -2.5, .00001)
# 断言该方程的姐应该为27/4
self.assertTrue(one_equation(4, -27) == 27 / 4)
# 断言当 a == 0时的情况,断言引发ValueError
with self.assertRaises(ValueError):
one_equation(0, 9)
# 测试一元二次方程的解
def test_twoequation(self):
r1, r2 = two_equation(1, -3, 2)
self.assertCountEqual((r1, r2), (1.0, 2.0), '求解出错')
r1, r2 = two_equation(2, -7, 6)
self.assertCountEqual((r1, r2), (1.5, 2.0), '求解出错')
# 断言只有一个解的情况
r = two_equation(1, -4, 4)
self.assertEqual(r, 2.0, '求解出错')
# 断言当 a == 0 时的情况,断言引发ValueError
with self.assertRaises(ValueError):
two_equation(0, 9, 3)
# 断言引发ValueError
with self.assertRaises(ValueError):
two_equation(4, 2, 3)
以上代码中使用断言方法判断函数的实际输出结果与期望结果是否一致,如果一致则表明测试通过,否则表明测试失败。
在测试某个方法时,如果实际测试要求达到某种覆盖程度,那么在编写测试用例时必须传入多组参数来进行测试,使得测试能达到指定的逻辑覆盖。
** TestCast中常用的断言方法**
断言方法 | 检查条件 |
---|---|
assertEqual(a, b) | a == b |
assertNotEqueal(a, b) | a != b |
assertTrue(x) | bool(x) is True |
asseretFalse(x) | bool(x) is False |
assertIs(a, b) | a is b |
assertIsNot(a, b) | a is not b |
assertIsNone(x) | x is None |
assertIsNotNone(x) | x is not None |
assertIn(a, b) | a in b |
assertNotIn(a, b) | a not in b |
assertIsInstance(a, b) | isinstance(a, b) |
assertNotIsInstance(a, b) | not instance(a, b) |
** TestCase包含的与异常、错误、警告和日志相关的断言方法 **
断言方法 | 检查条件 |
---|---|
assertRaises(exc, fun, *args, **kwds) | fun(*args, **kwads)引发exc异常 |
assertRaisesRegex(exc, r, fun, *args, **kwds) | fun(*args, **kwds)引发exc异常,且异常信息匹配r正则表达式 |
assertWarns(warn, fun, *args, **kwds) | fun(*args, **kwds)引发warn警告 |
assertWransRegex(warn, r, fun, *args, **kwds) | fun(*args, **kwds)引发wran警告,且警告信息匹配r正则表达式 |
assertLogs(logger, level) | With语句块使用日志器生成level级别的日志 |
** TestCase包含的用于完成某种特定检查的断言方法 **
断言方法 | 检查条件 |
---|---|
assertAlmostEqual(a, b) | round(a-b, 7) == 0 |
assertNotAlmostEqual(a, b) | round(a-b, 7) !=0 |
assertGreater(a, b) | a > b |
assrtGreaterEqual(a, b) | a >=b |
assertLess(a, b) | a < b |
assertLessEqual(a, b) | a <= b |
assertRegex(s, r) | r.search(s) |
assertNotRegex(s, r) | not r.search(s) |
assertCountEqual(a, b) | a、b两个序列包含的元素相同,不管元素出现的顺序如何 |
** TestCase包含的针对特定类型的断言方法**
断言方法 | 用于比较的类型 |
---|---|
assertMultiLineEqual(a, b) | 字符串(string) |
assertSequenceEqual(a, b) | 序列(sequence) |
assertListEqual(a, b) | 列表(list) |
assertTupleEqual(a, b) | 元组(tuple) |
assertSetEqual(a, b) | 集合(set或frozenset) |
assertDictEqual(a, b) | 字典(dict) |
(1) 通过代码调用测试用例。程序可以通过调用unittest.main()来运行当前源文件中的所有测试用例。
if __name__ == '__main__':
unittest.main()
(2) 使用unittest模块运行测试用例。
python -m unittest 测试文件
使用测试包(TestSuite)可以组织多个测试用例,测试包还可以嵌套测试包。在使用测试包组织多个测试用例和测试包之后,程序可以使用测试运行期(TestRunner)来运行该测试包所包含的所有测试用例。
假设有一个hello.py程序
# 该方法简单地返回字符串
def say_hello():
return "Hello World."
# 计算两个整数的和
def add(nA, nB):
return nA + nB
接下来编写一个测试类
import unittest
from hello import *
class TestHello(unittest.TestCase):
# 测试say_hello函数
def test_say_hello(self):
self.assertEqual(say_hello(), "Hello World.")
# 测试add函数
def test_add(self):
self.assertEqual(add(3, 4), 7)
self.assertEqual(add(0, 4), 4)
self.assertEqual(add(-3, 0), -3)
然后就可以通过TestSuite将前面的测试一元二次方程的测试用例和这个测试用例组织到一起,然后使用TestRunner来运行测试包了。
import unittest
from testuni_test_math import TestIbModule
from test_hello import TestHello
test_case = (TestHello, TestIbModule)
def whole_suite():
# 创建测试加载数据
loader = unittest.TestLoader()
# 创建测试包
suite = unittest.TestSuite()
# 遍历所有测试类
for test_class in test_case:
# 从测试类中加载测试用例
tests = loader.loadTestsFromTestCase(test_class)
# 将测试用例添加到测试包中
suite.addTests(tests)
return suite
if __name__ == '__main__':
# 创建测试运行期(TestRunner)
runner = unittest.TextTestRunner(verbosity=2)
runner.run(whole_suite())
测试固件:代表执行一个或多个测试用例所需的准备工作,以及相关联的准备操作,准备工作可能包括穿件临时数据库、创建目录、开启服务器进程等。
unittest.TestCase包含了setUp()和tearDown()两个方法,其中setUp()方法用于初始化测试固件;而tearDown()用于销毁测试固件。程序会在运行每个测试用例(以test_开头的方法)之前自动执行setUp()方法来初始化测试固件,并在每个测试用例运行完成之后自动执行tearDwon()方法来销毁测试固件。
(1) 使用skipXxx装饰器来跳过测试用例。unittest一共提供了3个装饰器,分别是@unittest.skip(reason)、@unittest.skipIf(condition, reason)和@unittest.skipUnless(condition, reason)。其中skip代表无条件跳过,skipIf代表当condition为True时跳过;skipUnless代表当condition为False时跳过。
(2) 使用TestCase的skipTest()方法来跳过测试用例。
# -*- encoding: utf-8 -*-
"""
# @Time : 2020/1/1 19:08
# @Author : 冰蓝工作室
# @File : test_module1.py
# @Contact : [email protected]
# @License : (C)Copyright 2019, www.ib-top.com
# @Desc : None
"""
class Test_Class1:
'''
一个用来测试文档编写的类
该类包括name、age两个属性
'''
def __init__(self, name, age):
'''
:param name: 初始化测试类的name属性
:param age: 初始化测试类的age属性
'''
self.name = name
self.age = age
def run(self):
'''
定义奔跑的方法
'''
print('{0}正在奔跑!'.format(self.name))
def test_function1():
'''
一个测试文档编写的函数
该函数打印自己的名字
'''
print("正在执行test_module1中的函数test_function1")
def test_function2(name, age):
'''
另一个测试文档编写的函数
:param name: 代表姓名
:param age: 代表年龄
:return: 返回对指定用户打招呼的字符串
'''
return name + "您好!您的年龄是" + age
testmodule2.py
# -*- encoding: utf-8 -*-
"""
# @Time : 2020/1/1 19:39
# @Author : 冰蓝工作室
# @File : test_module2.py
# @Contact : [email protected]
# @License : (C)Copyright 2019, www.ib-top.com
# @Desc : None
"""
class Test_class2:
'''
一个用来测试文档编写的类
该类包含name,age两个属性
'''
def __init__(self, name, age):
'''
类的初始化方法
:param name: 初始化name属性
:param age: 初始化age属性
'''
self.name = name
self.age = age
def sayHi(self):
'''
定义说话的方法
:return: 说话的字符串
'''
return self.name + "说:我的年龄是" + self.age + "岁!"
def test_function1():
'''
模块test_module2的文档编写测试函数
该函数打印自己的函数名
'''
print("正在执行test_module2中的函数test_function1")
def test_function2():
'''
模块test_module2的文档编写测试函数
:return: 该函数返回自己的函数名
'''
return "test_module2中函数test_function2"
③使用pydoc模块生成HTML文档
从命令行进入test_package目录的上级目录,使用命令 python -m pydoc -w test_package.test_module1
为第一个模块生成HTML文档,使用命令python -m pydoc -w test_package.test_module2
为第二个模块生成HTML文档,使用命令python -m pydoc -w test_package
为包生成HTML文档。
命令执行完后的目录结构为:
test_package
test_package.html
test_package.test_module1.html
test_package.test_module2.html
def add(x, y):
'''
一个用于计算加法的函数
:param x: 加数
:param y: 被加数
:return: 两个数的和
例如:
>>> add(1, 2)
3
>>> add(1.1, 2)
3.1
>>> add(-1, 1)
0
>>> add(0, 1)
1
'''
return x + y
def cube(x):
'''
计算一个数的立方
:param x: 待计算的数
:return: 计算结果
例如:
>>> cube(3)
27
>>> cube(2)
8
>>> cube(-2)
-8
>>> cube(1.1)
1.3310000000000004
>>> cube(0)
0
'''
return x**3
def sayHi():
'''
打印固定字符串
例如:
>>> sayHi()
Hello World!
'''
print('Hello World!')
class Person:
'''
定义一个代表人的类,该类包含如下两个属性
name: 代表姓名
age: 代表年龄
例如:
>>> p = Person('ib-top', 20)
>>> p.name
'ib-top'
>>> p.age
20
>>> p.say('i love python')
'ib-top说:i love python'
'''
def __init__(self, name, age):
self.name = name
self.age = age
def say(self, content):
return self.name + '说:' + content
if __name__ == '__main__':
import doctest
doctest.testmod()
import unittest
def add(x, y):
'''
计算两个数的和
:param x: 加数
:param y: 被加数
:return: 两个数的和
'''
return a + b
def square(x):
'''
计算一个数的平方
:param x: 待计算平方的数
:return: 计算结果
'''
return x**2
def say():
'''
打印字符串Hello World!
'''
print('Hello World!')
class Person:
'''
定义代表人的类,包含两个属性name和age
'''
def __init__(self, name, age):
self.name = name
self.age = age
def sayHi(self):
return self.name + '说:我今年' + self.age + '岁!'
# 测试用例
class TestSequenceFunctions(unittest.TestCase):
# 测试加法函数
def test_add(self):
# 断言结果为3
self.assertEqual(add(1, 2), 3)
# 断言结果为-1
self.assertTrue(add(-2, 1) == -1, .00001)
# 断言结果为0
self.assertTrue(add(0, 0) == 0)
# 测试平方函数
def test_square(self):
# 断言结果为4
self.assertEqual(square(2), 4)
# 断言结果为1
self.assertTrue(square(-1) == 1)
# 断言结果为0
self.assertEqual(square(0), 0)
# 测试打印函数
def test_say(self):
# 断言结果为Hello World!
self.assertEqual(say(), 'Hello World!')
def setUp(self):
self.man = Person('ib-top', 29)
print('set up now')
def test1(self):
self.assertEqual(self.man.name, 'ib-top')
def test2(self):
self.assertEqual(self.man.age, 29)
def test3(self):
self.assertEqual(self.man.sayHi(), 'ib-top说:我今年29岁!')
if __name__ == '__main__':
unittest.main()