单元测试框架 —— unittest

简介

unittest 单元测试框架最初是受JUnit的启发并具有与其他语言中的主流单元测试框架相似的风格。它支持测试的自动化,测试设置和关闭代码的共享,将测试聚合为测试及,以及与报告框架的分离等。

为了达成这些功能,unittest通过面向对象方式支持一些重要的概念:
*

复制代码
###### 测试脚手架

test fixture 表示为开展一项或多项测试所需要进行的准备工作,以及所有相关的清理操作。
复制代码
###### 测试用例

测试用例是单独的测试单元。它会检查对于特定输入集的特定响应。unittest提供了一个基类TestCase,它可被用来创建新的测试用例。
复制代码
###### 测试套件

test suite 是一系列的测试用例,或测试套件或两者皆有。它用于归档需要一起执行的测试。
复制代码
###### 测试运行器

test runner 是一个用于执行和输出测试结果的组件。这个运行器可能使用图形接口,文本接口,或返回一个特定的值表示运行测试的结果。
基本实例

unittest模块提供了丰富的工具集用于构造和运行测试。

python 复制代码
import unittest
class TestStringMethods(unittest.TestCase):
    def test_01(self):
        #调用assertEqual() 来检查预期的输出
        self.assertEqual('foo'.upper(),'FOO')

    def test_02(self):
        #调用assertTrue()或assertFalse()来验证一个条件
        self.assertTrue('FOO'.isupper())
        self.assertFalse('FOO'.isupper())

    def test_03(self):
        s = 'hello world'
        self.assertEqual(s.split(),['hello','world'])
        #调用assertRaises()来验证抛出了一个特定的异常。
        with self.assertRaises(TypeError):
            s.split(2)

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

每个测试的关键是:调用**assertEqual()来检查预期的输出;调用 assertTrue() assertFalse()来验证一个条件;调用assertRaise()**来验证抛出了一个特定的异常。使用这些方法而不是assert语句是为了让测试运行者能聚合所有的测试结果并产生结果报告。

通过setUp()和tearDown()方法,可以设置测试开始前与完成后需要执行的指令。

unittest.main()提供了一个测试脚本的命令行接口。
*

命令行接口

unittest模块可以通过命令行运行模块,类和独立测试方法的测试:

python 复制代码
py -m unittest test_01 test_02

测试模块也可以通过文件路径指定:

python 复制代码
py -m unittest pythonProject1/test_01
组织测试代码

单元测试的基本构成组件是测试用例 -- 需要设置并检查正确性的单独场景。在unittest中,测试用例由unittest.TestCase实例来表示。要创建自己的测试用例需要编写TestCase的子类或者使用FunctionTestCase。

一个TestCase实例的测试代码必须是完全自含的,因此它可以独立运行,或与其它任意组合任意数量的测试用例一起运行。

python 复制代码
import unittest

class WidgetSizeTest(unittest.TestCase):
    def test_default_widget_size(self):
        widget = Widget('The widget')
        self.assertEqual(widget.size(), (50, 50))

可能同时存在多个前置操作相同的测试,我们可以把测试的前置操作从测试代码中拆解出来 ,并实现测试前置方法setUP()。在运行测试时,测试框架会自动地为每个单独测试调用前置方法。

python 复制代码
import unittest

class WidgetSizeTest(unittest.TestCase):
    def setUp(self):
        self.widget = Widget('The widget')

    def test_default_widget_size(self):
        self.assertEqual(self.widget.size(), (50,50),
                         'incorrect default size')

    

在测试运行时,若setUP()方法引发异常,测试框架会认为测试发生了错误,因此测试方法不会被运行。相似的用tearDown()方法在测试方法运行后进行清理工作。

python 复制代码
import unittest

class WidgetSizeTest(unittest.TestCase):
    def setUp(self):
        self.widget = Widget('The widget')

    def tearDown(self):
        self.widget.dispose()

若setUP()成功运行,无论测试方法是否成功,都会运行tearDown()。

这种用于测试代码的工作环境称为test fixture。一个新的TestCase实例将作为一个单独的test fixture被创建用于执行各个独立的测试方法。这样setUp(),tearDown()和TestCase.init()将在每个测试中被调用一次。
*

复用已有的测试代码

在某些情况下,现有的测试可能是用doctest模块编写的。如果是这样,doctest提供了一个DocTestSuite类可以根据现有的基于doctest的测试自动构建unittest.TestSuite实例。
*

跳过测试与预计的失败

Unittest支持跳过单个或整组的测试用例。它还支持把测试标注成"预期失败"的测试。这些坏测试会失败,但不会算进TestResult的失败里。

要跳过测试只需要使用skip()decorator或其附带条件的版本,在setUp()内部使用TestCase.skipTest(),或是直接引发SkipTest。

python 复制代码
class MyTestCase(unittest.TestCase):

    @unittest.skip("demonstrating skipping")
    def test_nothing(self):
        self.fail("shouldn't happen")

    @unittest.skipIf(mylib.__version__ < (1, 3),
                     "not supported in this library version")
    def test_format(self):
        # 测试其是否仅适用于特定的库版本。
        pass

    @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
    def test_windows_support(self):
        # Windows 专属的测试代码
        pass

    def test_maybe_skipped(self):
        if not external_resource_available():
            self.skipTest("external resource not available")
        # 依赖于外部资源的测试代码
        pass

跳过测试类的写法跟跳过测试方法的写法相似:

python 复制代码
@unittest.skip("showing class skipping")
class MySkippedTestCase(unittest.TestCase):
    def test_not_run(self):
        pass

TestCase.setUp()也可以跳过测试。可以用于所需资源不可用的情况下跳过接下来的测试。使用expectedFailure()装饰器表明这个测试预计失败。

python 复制代码
class ExpectedFailureTestCase(unittest.TestCase):
    @unittest.expectedFailure
    def test_fail(self):
        self.assertEqual(1, 0, "broken")

以下的装饰器和异常实现了跳过测试和预期失败两种功能:

  • @unittest.skip(reason):跳过被此装饰器装饰的测试。reason为测试被跳过的原因。

  • @unittest.skipIf(condition,reason):当condition为真时,跳过被装饰的测试。

  • @unittest.skipUnless(condition,reason):跳过被装饰的测试,除非condition为真。

  • @unittest.expectedFailure:将测试标记为预期的失败或错误。如果测试失败或在测试函数自身(而非在某个test fixture方法)中出现错误则将认为是测试成功。如果测试通过,则将认为是测试失败。

  • exception unittest.SkipTest(reason):引发此异常以跳过一个测试。

    通常来说,可以使用TestCase.skipTest()或其中一个跳过测试的装饰器实现跳过测试的功能,而不是直接引发此异常。

被跳过的测试的setUp()和tearDown()不会被运行。被跳过的类的setUpClass()和tearDownClass()不会被运行。被跳过的模块的setUpModule()和tearDownModule()不会被运行。
*

类与函数
测试用例

class unittest.TestCase(methodName='runTest')

每个TestCase实例将运行一个单位的基础方法:即名为methodName的方法。在使用TestCase的大多数场景中,不需要修改methodName或重新实现默认的runTest()方法。

TestCase 的实例提供了三组方法:一组用来运行测试,另一组被测试实现用来检查条件和报告失败,还有一些查询方法用来收集有关测试本身的信息。

第一组(用于运行测试的)方法是:

setUp() -> 为测试预备而调用的方法。此方法会在调用测试方法之前被调用。

**tearDown() ->**在测试方法被调用并记录结果之后立即被调用的方法。此方法即使在测试方法引发异常时仍会被调用,因此子类中的实现将需要特别注意检查内部状态。此方法将只在setUp()成功执行时被调用,无论测试方法的结果如何。默认的实现将不做任何事情。

setUpClass() -> 在一个单独类中的测试运行之前被调用的类方法。setUpClass会被作为唯一的参数在类上调用且必须使用classmethod()装饰器。

python 复制代码
@classmethod
def setUpClass(cls):
    ...

tearDownClass() -> 在一个单独类的测试完成运行之后被调用的类方法。tearDownClass会被作为唯一的参数在类上调用且必须使用classmethod()装饰器

python 复制代码
@classmethod
def setUpClass(cls):
    ...

run(result=None) -> 运行测试,将结果收集至作为result传入的TestResult。如果result被省略或为None,则会创建一个临时的结果对象(通过调用defaultTestResult()方法)并使用它。结果对象会被返回给run()的调用方。

skipTest(reason) -> 在测试方法或setUp()执行期间调用此方法将跳过当前测试。

**subTest(msg=None,**params) ->**返回一个上下文管理器以将其中的代码块作为子测试来执行。

debug() -> 运行测试而不收集结果。这允许测试所引发的异常被传递给调用方,并可被用于支持在调试器中运行测试。

常用的方法

方法 检查对象 详情
assertEqual(first,second,msg=None) first==second 测试first和second是否相等。如果两个值的比较结果是不相等,则测试将失败。
assertNotEqual(first,second,msg=None) a!=b 测试first和second是否不等。如果两个值的比较结果是相等,则测试将失败。
assertTrue(expr,msg=None) bool(x) is True 测试expr是否为真值(或假值)。
assertFalse(expr,msg=None) bool(x) is False 测试expr是否为假值
assertIs(first,second,msg=None) first is second 测试first和second是(或不是)同一个对象。
assertIsNot(first,second,msg=None) first is not second 测试first和second是(或不是)同一个对象。
assertIsNone(expr,msg=None) expr is None 测试expr是None。
assertIsNotNone(expr,msg=None) expr is not None 测试expr不是None
assertIn(member,container,msg=None) a in b 测试member是container成员
assertNotIn(member,container,msg=None) a not in b 测试member不是container的成员
assertIsInstance(obj,cls,msg=None) isinstance(a,b) 测试obj是cls(此参数可以为一个类或包含类的元组,即isinstance()所接受的参数)
assertNotIsInstance(obj,cls,msg=None) not isinstance(a,b) 测试obj不是cls(此参数可以为一个类或包含类的元组,即isinstance()所接受的参数)
assertIsSubclass(obj,cls,msg=None) issubclass(a,b) 测试cls是superclass的子类(此参数可以是一个类或由类组成的元组,即issubclass()的所接受的值)。
assertNotIsSubclass(a,b) not issubclass(a,b) 测试cls不是superclass的子类(此参数可以是一个类或由类组成的元组,即issubclass()的所接受的值)。

还可以使用下列方法来检查异常,警告和日志消息的产生:

方法 检查对象 引入版本
assertRaises(exc,fun,*args,**kwds) fun(*args,**kwds)引发了exc
assertRaisesRegex(exc,r,fun,*args,**kwds) fun(*args,**kwds)引发了exc并且消息可与正则表达式r相匹配 3.1
assertWarns(warn,fun,*args,**kwds) fun(*args,**kwds)引发了warn 3.2
assertWarnRegex(warn,r,fun,*args,**kwds) fun(*args,**kwds)引发了warn并且消息可与正则表达式r相匹配 3.2
assertLogs(logger,level) with代码块在logger上使用了最小的level级别写入日志 3.4
assertNoLogs(logger,level) with 代码块没有在logger上使用最小的level级别写入日志 3.10
相关推荐
apcipot_rain1 小时前
原神“十盒半价”问题的兹白式建模分析
python·数学·算法·函数·数据科学·原神·数列
喵手1 小时前
Python爬虫实战:舆情语料项目 - 从新闻抓取到文本挖掘的完整实战(附CSV导出 + SQLite持久化存储)!
爬虫·python·爬虫实战·零基础python爬虫教学·舆论语料项目·新闻抓取到文本挖掘·爬虫实战采集舆论语料
坚持就完事了1 小时前
Python的类型注解
开发语言·python
岱宗夫up1 小时前
FastAPI进阶:从入门到生产级别的深度实践
python·信息可视化·fastapi
七夜zippoe1 小时前
图神经网络实战:从社交网络到推荐系统的工业级应用
网络·人工智能·pytorch·python·神经网络·cora
啊阿狸不会拉杆1 小时前
《计算机视觉:模型、学习和推理》第 1 章 - 绪论
人工智能·python·学习·算法·机器学习·计算机视觉·模型
was1721 小时前
Python 自动化实践:Typora 自定义上传接口与兰空图床集成
python·自动化·typora·兰空图床
二十雨辰2 小时前
[python]-网络编程
python
ljxp12345682 小时前
判断两棵二叉树是否相同
python