Python单元测试实战:从TestCase到TestSuite完整指南

认真发呆
认真发呆
发布时间:2026-10-12 | 3人浏览过
原创

本文深入解析Python标准库unittest模块,详细讲解如何使用TestCase构建静态与动态测试用例,利用TestSuite组织测试集,并通过TextTestRunner执行测试。涵盖setUp、tearDown等生命周期方法,以及main()函数和discover自动发现机制,提供Widget类测试实例,帮助开发者掌握自动化测试核心流程。

本文通过具体实例详细解析Python单元测试的实现方法,供读者参考学习。

在Python中进行单元测试,通常依赖自动测试框架PyUnit。自Python 2.1版本起,PyUnit已作为标准模块(即)集成在Python中。若使用的是较旧版本,需从PyUnit官网下载源码进行安装方可使用。

一、Python单元测试范例

单元测试的核心原理是对比预期结果与实际执行结果,两者一致则测试通过,否则视为失败。为深入理解自动测试框架PyUnit,下文将以对类进行单元测试为例进行说明。

#widget.py
#将要被测试的类Widget
class Widget:
  def __init__(self, size = (40, 40)):
    self._size = size
  def getSize(self):
    return self._size
  def resize(self, width, height):
    if width < 0 or height < 0:
      raise ValueError, "illegal size"
    self._size = (width, height)
  def dispose(self):
    pass

二、测试用例TestCase

软件测试的基本组成单元是测试用例(test case)。在PyUnit中,类用于表示测试用例,所有执行测试的类必须继承自该类。TestCase子类实现的测试代码应具备自包含性(self contained),即既能单独运行,也能与其他测试用例组合共同运行。TestCase类中常用的函数或方法包括:

setUp:用于测试前的初始化工作。 tearDown:用于测试后的资源清理工作。 failedinfo:可选参数,用于表示断言失败时打印的信息。 self.assertEqual(value1, value2, failedinfo):无条件导致测试失败,不推荐使用。 self.assertTrue(, failedinfo):断言value1等于value2。 self.assertFalse(, failedinfo):断言value为真。 self.assertRaises(ValueError, self.widget.resize, -1, -1):断言必然发生异常;若未发生异常,则测试失败。参数1指定异常类型,参数2为抛出异常的调用对象,其余参数为传递给该可调用对象的参数。 在PyUnit测试框架中,TestCase被视为测试单元的运行实体。Python程序员可通过其派生自定义的测试过程与方法(测试单元)。利用Command和Composite设计模式,多个TestCase还可以组合成测试用例集合。当PyUnit框架运行一个测试用例时,TestCase子类定义的setUp()、runTest()和tearDown()方法会被依次执行。最简单的测试用例只需覆盖runTest()方法来执行特定的测试代码即可。

1、静态方法

一个测试用例仅针对软件模块中的一个方法进行测试,通过覆盖方法来构造测试用例,这在PyUnit中被称为静态方法。举例说明如下:

#static.py
from widget import Widget
import unittest
#执行测试的类
class WidgetTestCase(unittest.TestCase):
  def runTest(self):
    widget = Widget()
    self.assertEqual(widget.getSize(), (40, 40))
#测试
if __name__ == "__main__":
  testCase = WidgetTestCase()
  testCase.runTest()

若采用静态方法,Python程序员不得不为每个待测方法编写一个独立的测试类。该类通过覆盖runTest()方法来执行测试,并在每个测试类中生成一个待测试的对象,这种方式显得非常繁琐与笨拙。

2、动态方法

鉴于静态方法的缺陷,PyUnit提供了一种更为高效的解决方案,即动态方法。只需编写一个测试类即可完成对整个软件模块的测试,对象的初始化工作可在setUp()方法中完成,而资源释放则可在tearDown()方法中处理。举例说明如下:

#dynamic.py
from widget import Widget
import unittest
class WidgetTestCase(unittest.TestCase):
  def setUp(self):
    self.widget = Widget()
  def tearDown(self):
    self.widget.dispose()
    self.widget = None
  def testSize(self):
    self.assertEqual(self.widget.getSize(), (40, 40))
  def testResize(self):
    self.widget.resize(100, 100)
    self.assertEqual(self.widget.getSize(), (100, 100))

动态方法不再覆盖方法,而是为测试类编写多个测试方法。按照惯例,这些方法通常以test开头,但这并非强制要求。在创建TestCase子类的实例时,必须指定测试方法的名称,以便PyUnit测试框架在运行该测试用例时调用测试类中的相应方法。这通常会结合测试用例集一起使用。

三、测试用例集TestSuite

完整的单元测试很少仅执行一个测试用例,开发人员通常需要编写多个测试用例才能对某一软件功能进行较为完整的测试。这些相关的测试用例被称为一个测试用例集,在PyUnit中由类表示。PyUnit测试框架允许Python程序员在单元测试代码中定义一个名为suite()的全局函数,并将其作为整个单元测试的入口。PyUnit通过调用该函数来完成整个测试过程:

def suite():
  suite = unittest.TestSuite()
  suite.addTest(WidgetTestCase("testSize"))
  suite.addTest(WidgetTestCase("testResize"))
  return suite
    也可以直接定义一个TestSuite的子类,并在其初始化方法__init__中完成所有测试用例的添加:
class WidgetTestSuite(unittest.TestSuite)
  def __init__(self):
    unittest.TestSuite.__init__(self, map(WidgetTestCase, ("testSize", "testResize")))
    这样只需要在suite()方法中返回该类的一个实例就可以了:
def suite():
  return WidgetTestSuite()

在PyUnit测试框架中,类可视为类的容器,用于组织多个测试用例,从而使多个测试用例能在一次测试中全部完成。事实上,除了可以包含外,也可以包含,从而构成更庞大的测试用例集:

suite1 = mysuite1.TheTestSuite()
suite2 = mysuite2.TheTestSuite()
alltests = unittest.TestSuite((suite1, suite2))

四、实施测试TestRunner

编写测试用例(TestCase)并将其组织成测试用例集()的最终目的只有一个:实施测试并获得最终结果。PyUnit使用类作为测试用例的基本执行环境,以驱动整个单元测试过程。然而,Python开发人员在进行单元测试时通常不直接使用类,而是使用其子类来完成测试,并将测试结果以文本方式显示出来。举例说明如下:

#text_runner.py
from widget import Widget
import unittest
#执行测试的类
class WidgetTestCase(unittest.TestCase):
  def setUp(self):
    self.widget = Widget()
  def tearDown(self):
    self.widget.dispose()
    self.widget = None
  def testSize(self):
    self.assertEqual(self.widget.getSize(), (40, 40))
  def testResize(self):
    self.widget.resize(100, 100)
    self.assertEqual(self.widget.getSize(), (100, 100))
#测试
if __name__ == "__main__":
  #构造测试集
  suite = unittest.TestSuite()
  suite.addTest(WidgetTestCase("testSize"))
  suite.addTest(WidgetTestCase("testResize"))
  #执行测试
  runner = unittest.TextTestRunner()
  runner.run(suite)

使用如下命令执行该单元测试:

$python text_runner.py

默认情况下,将结果输出到sys.stdout/sys.stderr上。但是,如果在创建类实例时将一个文件对象传递给了构造函数,则输出结果将被重定向到该文件中。

五、大道至简main()

PyUnit模块中定义了一个名为main的全局方法,使用它可以很方便地将一个单元测试模块变成可以直接运行的测试脚本。main()方法使用类来搜索所有包含在该模块中的测试方法,并自动执行它们。如果Python程序员能够按照约定(以test开头)来命名所有的测试方法,那么只需要在测试模块的最后加入如下几行代码即可:

if __name__ == "__main__":
  unittest.main()

下面是利用main()方法来进行测试的完整例子:

#main_runner.py
from widget import Widget
import unittest
#执行测试的类
class WidgetTestCase(unittest.TestCase):
  def setUp(self):
    self.widget = Widget()
  def tearDown(self):
    self.widget.dispose()
    self.widget = None
  def testSize(self):
    self.assertEqual(self.widget.getSize(), (40, 40))
  def testResize(self):
    self.widget.resize(100, 100)
    self.assertEqual(self.widget.getSize(), (100, 100))
#测试
if __name__ == "__main__":
  unittest.main()

使用如下命令执行上面的单元测试:

$python main_runner.py

如上所示,将执行中的所有测试方法。但是如果只想执行testSize()方法,则可以如下这般操作:

$python main_runner.py WidgetTestCase.testSize

如果在单元测试脚本中定义了,还可以指定要运行的测试集。使用-h参数可以查看运行该脚本所有可能用到的参数:

$python main_runner.py -h

需要注意的是:PyUnit的中如果有多个test_xxx,则默认按照xxx的字母顺序执行测试用例函数。如果test_xxx之间存在依赖关系,就会出错。解决方法有二:1、解耦;2、编写xxx函数时人为地按字母顺序排序。

当然,如果你安装了Python 2.7.2及以上版本,你还可以利用discover函数来自动发现并执行测试用例:

$python2.7 -m unittest discover

更多关于Python单元测试的资料可以参看这里,还有这里。

更多Python相关内容感兴趣的读者可查看本站专题:《Python入门与进阶经典教程》、《Python字符串操作技巧汇总》、《Python列表(list)操作技巧总结》、《Python编码操作技巧总结》、《Python数据结构与算法教程》、《Python函数使用技巧总结》及《Python文件与目录操作技巧汇总》

希望本文所述对大家Python程序设计有所帮助。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表