测试您的代码

../_images/34435687940_8f73fc1fa6_k_d.jpg

测试代码非常重要。

习惯于编写测试代码并并行运行这段代码现在被认为是一种好习惯。如果使用得当,此方法有助于更准确地定义代码的意图,并具有更解耦的体系结构。

测试的一些一般规则:

  • 测试单元应该专注于一个小的功能,并证明它是正确的。

  • 每个测试单元必须完全独立。无论调用顺序如何,每个测试都必须能够单独运行,也可以在测试套件中运行。这条规则的含义是每个测试都必须装载一个新的数据集,并且可能必须在之后进行一些清理。这通常由 setUp()tearDown() 方法。

  • 努力让测试快速运行。如果一个测试运行需要几毫秒以上的时间,那么开发速度将会减慢,或者测试运行的频率不会像期望的那样频繁。在某些情况下,测试不能很快,因为它们需要一个复杂的数据结构来工作,并且每次测试运行时都必须加载这个数据结构。将这些较重的测试保存在一个单独的测试套件中,该套件由某个计划任务运行,并根据需要经常运行所有其他测试。

  • 学习工具,学习如何运行单个测试或测试用例。然后,在模块内开发函数时,经常运行该函数的测试,最好是在保存代码时自动运行。

  • 总是在编码会话之前运行完整的测试套件,然后再次运行它。这将给您更多的信心,使您不会破坏其余代码中的任何内容。

  • 在将代码推送到共享存储库之前,最好实现一个运行所有测试的钩子。

  • 如果您正处于开发会话的中间,并且必须中断您的工作,那么最好编写一个中断的单元测试来说明您接下来要开发什么。当你回到工作岗位时,你会有一个指向你所在位置的指针,并能更快地回到正轨。

  • 调试代码的第一步是编写一个新的测试来精确定位错误。虽然不可能总是这样做,但是那些捕获bug的测试是项目中最有价值的代码。

  • 对测试函数使用长的描述性名称。这里的样式指南与运行代码的样式指南稍有不同,通常首选短名称。原因是从未显式调用测试函数。 square() 甚至 sqr() 在运行代码时是可以的,但在测试代码中,您可以使用诸如 test_square_of_number_2()test_square_negative_number() .这些函数名在测试失败时显示,并且应尽可能具有描述性。

  • 当出现问题或需要更改时,如果您的代码有一组好的测试,您或其他维护人员将在很大程度上依赖测试套件来修复问题或修改给定的行为。因此,测试代码的读取量将与正在运行的代码相同甚至更多。在这种情况下,目的不明确的单元测试并不是很有帮助。

  • 测试代码的另一个用途是作为新开发人员的介绍。当有人必须在代码库上工作时,运行和读取相关的测试代码通常是他们能够开始做的最好的事情。他们将或应该发现最困难出现的热点和角落案例。如果必须添加一些功能,第一步应该是添加一个测试,以确保新功能不是尚未插入接口的工作路径。

基础知识

单元测试

unittest python标准库中是否包含电池测试模块?任何使用过JUnit/Nunit/CPPPUnit系列工具的人都会熟悉它的API。

创建测试用例是通过子类化完成的。 unittest.TestCase

import unittest

def fun(x):
    return x + 1

class MyTest(unittest.TestCase):
    def test(self):
        self.assertEqual(fun(3), 4)

从Python 2.7unittest开始,它还包括自己的测试发现机制。

Doctest

这个 doctest 模块在docstrings中搜索类似于交互式Python会话的文本片段,然后执行这些会话以验证它们是否如图所示工作。

与正确的单元测试相比,doctest有不同的用例:它们通常不那么详细,也不会捕获特殊的用例或模糊的回归错误。它们作为模块及其组件的主要用例的表达文档非常有用。但是,每次运行完整的测试套件时,doctests都应该自动运行。

函数中的简单doctest:

def square(x):
    """Return the square of x.

    >>> square(2)
    4
    >>> square(-2)
    4
    """

    return x * x

if __name__ == '__main__':
    import doctest
    doctest.testmod()

从命令行运行此模块时,如中所示 python module.py ,如果有任何不符合docstrings中描述的行为,doctests将运行并投诉。

工具

py.test

py.test是python标准单元测试模块的无样板文件替代品。

$ pip install pytest

尽管它是一个功能齐全、可扩展的测试工具,但它拥有一个简单的语法。创建一个测试套件和编写一个具有以下几个功能的模块一样简单:

# content of test_sample.py
def func(x):
    return x + 1

def test_answer():
    assert func(3) == 5

然后运行 py.test 命令:

$ py.test
=========================== test session starts ============================
platform darwin -- Python 2.7.1 -- pytest-2.2.1
collecting ... collected 1 items

test_sample.py F

================================= FAILURES =================================
_______________________________ test_answer ________________________________

    def test_answer():
>       assert func(3) == 5
E       assert 4 == 5
E        +  where 4 = func(3)

test_sample.py:5: AssertionError
========================= 1 failed in 0.02 seconds =========================

比单元测试模块的等效功能所需的工作量要少得多!

假设

假设是一个库,它允许您编写由示例源参数化的测试。然后,它会生成简单易懂的示例,使您的测试失败,从而让您在工作较少的情况下发现更多的错误。

$ pip install hypothesis

例如,float的测试列表将尝试许多示例,但报告每个bug的最小示例(可分辨的异常类型和位置):

@given(lists(floats(allow_nan=False, allow_infinity=False), min_size=1))
def test_mean(xs):
    mean = sum(xs) / len(xs)
    assert min(xs) <= mean(xs) <= max(xs)
Falsifying example: test_mean(
    xs=[1.7976321109618856e+308, 6.102390043022755e+303]
)

这个假设既实用又非常强大,并且经常会发现那些逃过所有其他形式测试的错误。它与py.test很好地集成,并且在简单和高级场景中都非常注重可用性。

tox

tox是一个自动化测试环境管理和针对多个解释器配置进行测试的工具。

$ pip install tox

tox允许您通过一个简单的ini样式配置文件配置复杂的多参数测试矩阵。

嘲弄

unittest.mock 是一个在Python中测试的库。从python 3.3开始,它在 standard library

对于旧版本的python:

$ pip install mock

它允许您用模拟对象替换测试中的系统部分,并对它们的使用方式作出断言。

例如,可以对方法进行monkey修补:

from mock import MagicMock
thing = ProductionClass()
thing.method = MagicMock(return_value=3)
thing.method(3, 4, 5, key='value')

thing.method.assert_called_with(3, 4, 5, key='value')

要模拟被测试模块中的类或对象,请使用 patch 装饰师。在下面的示例中,外部搜索系统替换为始终返回相同结果(但仅在测试期间)的模拟。

def mock_search(self):
    class MockSearchQuerySet(SearchQuerySet):
        def __iter__(self):
            return iter(["foo", "bar", "baz"])
    return MockSearchQuerySet()

# SearchForm here refers to the imported class reference in myapp,
# not where the SearchForm class itself is imported from
@mock.patch('myapp.SearchForm.search', mock_search)
def test_new_watchlist_activities(self):
    # get_search_results runs a search and iterates over the result
    self.assertEqual(len(myapp.get_search_results(q="fish")), 3)

Mock有许多其他方法可以用来配置和控制其行为。