摘要:本文介绍了线程应用程序的测试方法及其重要性。软件测试能提升质量、客户满意度,降低风险与成本。重点阐述了并发程序的两种测试方法:系统化探索法和属性驱动法。通过Python示例演示了单元测试模块unittest和doctest的使用方法:unittest适合复杂场景,提供测试固件、用例、套件和运行器;doctest则更简洁,通过文档字符串验证交互式执行结果。文章通过修改斐波那契数列代码展示了测试失败的具体表现,验证了测试工具的有效性。
目录
[unittest 模块](#unittest 模块)
[doctest 模块](#doctest 模块)
测试线程应用程序
在本章中,我们将学习线程应用程序的测试方法,同时了解测试的重要性。
为何要进行测试?
在探讨测试的重要性之前,我们需要先明确测试的定义。广义上,测试是一种检验事物运行效果的方法;而具体到计算机程序或软件领域,测试是评估软件程序功能的技术手段。
本节将阐述软件测试的重要性。在软件开发流程中,软件交付给客户前必须进行反复核查,因此由专业的测试团队开展软件测试工作至关重要。可通过以下几点理解软件测试的重要性:
提升软件质量
毫无疑问,没有企业愿意交付低质量软件,也没有客户愿意购买此类产品。测试通过发现并修复软件中的漏洞,能够有效提升软件质量。
提升客户满意度
客户满意度是任何商业活动的核心。企业通过提供无漏洞、高质量的软件,能够切实提升客户满意度。
降低新功能的影响风险
假设现有一个万行代码的软件系统,开发团队需要为其新增功能时,会担忧新功能对整个系统产生的影响。此时测试将发挥关键作用:若测试团队搭建了完善的测试套件,就能避免系统出现灾难性的故障。
优化用户体验
用户体验同样是商业活动的重要组成部分,唯有通过测试,才能确保终端用户能够简单、便捷地使用产品。
降低开发成本
在软件开发的测试阶段发现并修复漏洞,相比软件交付后再进行修复,能大幅降低整体成本。若软件交付后出现重大漏洞,不仅会产生直接的经济开支,还会带来客户不满、企业声誉受损等间接损失。
测试的核心内容
开展测试工作,首先要明确测试的核心对象。本节将先说明测试人员测试软件的首要目标:应避免单纯追求代码覆盖率(即测试套件覆盖的代码行数),因为仅关注代码行数无法为系统带来实际价值,即便完成测试,软件部署后仍可能出现漏洞。
关于测试核心内容,需关注以下要点:
- 测试的核心是验证代码的功能实现,而非单纯追求代码覆盖率。
- 优先测试代码的核心关键模块,再逐步测试次要模块,能有效节省测试时间。
- 测试人员需设计多样化的测试用例,充分测试软件的性能极限。
并发软件程序的测试方法
得益于对多核架构的充分利用,并发软件系统正逐步取代串行系统。如今,从手机、洗衣机到汽车、飞机,各类设备中都能见到并发系统程序的身影。并发软件程序的测试需要更加谨慎:若为一个本身存在漏洞的单线程应用添加多线程,最终可能引发多个漏洞。
并发软件程序的测试技术,核心是筛选出能暴露竞态条件、死锁、原子性违规等高危问题的线程执行交错情况。以下是两种主流的并发软件程序测试方法:
系统化探索法
该方法旨在尽可能全面地探索线程执行交错的所有可能性,可采用暴力枚举法,也可结合偏序约简法、启发式算法来探索交错空间。
属性驱动法
该方法的核心依据是:并发漏洞更易在暴露特定属性(如可疑的内存访问模式)的线程交错执行中出现。不同的属性驱动法针对不同类型的漏洞(竞态条件、死锁、原子性违规)设计,且每种方法都依托于特定的系统属性。
测试策略
测试策略也被称为测试方法,其定义了测试工作的执行方式,主要分为两种技术手段:
主动式测试
在软件构建完成前,尽早启动测试设计工作,提前发现并修复漏洞的测试方法。
被动式测试
待开发流程全部完成后,再开展测试工作的测试方法。
在对 Python 程序应用任何测试策略前,需先了解软件程序可能出现的两类错误:
语法错误
程序开发过程中出现的各类小错误,多由输入失误导致,例如遗漏冒号、关键字拼写错误等。此类错误源于程序语法规范的违反,与逻辑无关,因此被称为语法错误。
语义错误
语义错误也被称为逻辑错误。若软件程序存在逻辑 / 语义错误,代码能正常编译和运行,但因逻辑设计错误,无法输出预期结果。
单元测试
单元测试是 Python 程序最常用的测试策略之一,主要用于测试代码的独立单元或组件(即代码中的类、函数)。通过测试小的功能单元,单元测试能简化大型程序系统的测试工作。综上,单元测试可定义为:对源代码中的独立单元进行测试,验证其是否能输出预期结果的测试方法。
在后续章节中,我们将学习 Python 中用于单元测试的各类模块。
unittest 模块
unittest 是 Python 中首个单元测试模块,灵感来源于 JUnit 框架,且Python3.6 及以上版本已默认内置。该模块支持测试自动化、测试前后的初始化 / 清理代码复用、测试用例的集合管理,同时实现了测试用例与报告框架的解耦。
unittest 模块支持以下几个核心概念:
测试固件
用于为测试做前置准备(测试开始前执行)和后置清理(测试结束后执行)的机制,例如创建测试所需的临时数据库、目录等。
测试用例
验证特定输入是否能返回预期结果的测试单元。unittest 模块包含一个基础类TestCase,可基于该类创建自定义测试用例,且内置了两个核心方法:
- setUp():执行测试用例前的固件初始化钩子方法,在所有自定义测试方法执行前调用。
- tearDown():执行完类中所有测试用例后的固件销毁钩子方法。
测试套件
由多个测试套件、测试用例或二者组合构成的测试集合。
测试运行器
控制测试用例 / 测试套件的执行,并向用户反馈测试结果的组件,可通过图形界面或纯文本界面展示结果。
示例
以下 Python 程序通过 unittest 模块测试一个用于计算斐波那契数列的Fibonacci 模块。程序中创建了Fibo_test类,通过继承unittest.TestCase定义测试用例,同时使用了内置的setUp()和tearDown()方法,并自定义了testfibocal测试方法(测试方法名必须以 test 开头 )。最后通过unittest.main()为测试脚本提供命令行执行接口。
import unittest
def fibonacci(n):
a, b = 0, 1
for i in range(n):
a, b = b, a + b
return a
class Fibo_Test(unittest.TestCase):
def setUp(self):
print("测试执行前运行此方法")
def tearDown(self):
print("测试执行完成后运行此方法")
def testfibocal(self):
self.assertEqual(fibonacci(0), 0)
self.assertEqual(fibonacci(1), 1)
self.assertEqual(fibonacci(5), 5)
self.assertEqual(fibonacci(10), 55)
self.assertEqual(fibonacci(20), 6765)
if __name__ == "__main__":
unittest.main()
在命令行运行上述脚本,输出结果如下:
输出结果
plaintext
测试执行前运行此方法
测试执行完成后运行此方法..
----------------------------------------------------------------------
运行1个测试用例,耗时0.006秒
测试通过
为更直观展示测试效果,我们修改斐波那契模块的代码:
原代码:
def fibonacci(n):
a, b = 0, 1
for i in range(n):
a, b = b, a + b
return a
修改后代码:
def fibonacci(n):
a, b = 1, 1
for i in range(n):
a, b = b, a + b
return a
运行修改后的脚本,输出结果如下:
plaintext
测试执行前运行此方法
测试执行完成后运行此方法。
F
======================================================================
失败:testCalculation (__main__.Fibo_Test)
----------------------------------------------------------------------
回溯(最近的调用最后):
文件 "unitg.py",第15行,在testCalculation中
self.assertEqual(fibonacci(0), 0)
断言错误:1 != 0
----------------------------------------------------------------------
运行1个测试用例,耗时0.007秒
测试失败(失败数=1)
上述结果表明,该模块未能输出预期结果。
doctest 模块
doctest 模块同样用于单元测试,Python 默认内置,使用方式比 unittest 模块更简洁,而 unittest 模块更适合复杂的测试场景。使用 doctest 模块时,需先导入该模块,且目标函数的文档字符串中,必须包含交互式 Python 执行的代码及对应的预期输出。
若代码无问题,doctest 模块运行后无任何输出;若存在问题,则会输出具体的错误信息。
示例
以下 Python 示例通过 doctest 模块测试计算斐波那契数列的 Fibonacci 模块:
python
import doctest
def fibonacci(n):
"""
计算斐波那契数
>>> fibonacci(0)
0
>>> fibonacci(1)
1
>>> fibonacci(10)
55
>>> fibonacci(20)
6765
>>>
"""
a, b = 1, 1
for i in range(n):
a, b = b, a + b
return a
if __name__ == "__main__":
doctest.testmod()
可以看到,fibonacci函数的文档字符串中包含了交互式执行代码和预期输出。若代码无问题,doctest 模块运行后无输出;若需查看详细的测试过程,可添加-v参数运行。
plaintext
python
(base) D:\ProgramData>python dock_test.py -v
执行测试:
fibonacci(0)
预期结果:
0
测试通过
执行测试:
fibonacci(1)
预期结果:
1
测试通过
执行测试:
fibonacci(10)
预期结果:
55
测试通过
执行测试:
fibonacci(20)
预期结果:
6765
测试通过
1个模块无测试用例:
__main__
1个模块所有测试用例通过:
__main__.fibonacci 包含4个测试用例
2个模块共执行4个测试用例。
4个通过,0个失败。
测试通过。
接下来我们修改斐波那契模块的代码:
原代码:
python
def fibonacci(n):
a, b = 0, 1
for i in range(n):
a, b = b, a + b
return a
修改后代码:
python
def fibonacci(n):
a, b = 1, 1
for i in range(n):
a, b = b, a + b
return a
即使不添加-v参数,运行修改后的脚本也会输出以下错误信息:
输出结果
plaintext
python
(base) D:\ProgramData>python dock_test.py
**********************************************************************
文件 "unitg.py",第6行,在__main__.fibonacci中
失败的测试示例:
fibonacci(0)
预期结果:
0
实际结果:
1
**********************************************************************
文件 "unitg.py",第10行,在__main__.fibonacci中
失败的测试示例:
fibonacci(10)
预期结果:
55
实际结果:
89
**********************************************************************
文件 "unitg.py",第12行,在__main__.fibonacci中
失败的测试示例:
fibonacci(20)
预期结果:
6765
实际结果:
10946
**********************************************************************
1个模块存在测试失败:
__main__.fibonacci 中4个测试用例,3个失败
***测试失败*** 共3处失败。
从上述结果可以看到,有 3 个测试用例执行失败。