测试指南

介绍

在1.15版本之前,Numpy使用了 nose 测试框架,它现在使用 pytest 框架。为了支持使用旧的numpy框架的下游项目,仍然维护旧的框架,但是numpy的所有测试都应该使用pytest。

我们的目标是NumPy中的每个模块和包都应该有一套完整的单元测试。这些测试应该使用给定例程的全部功能,以及它对错误或意外输入参数的健壮性。长期的经验表明,到目前为止,编写测试的最佳时间是在编写或更改代码之前—这是一个很好的时机 test-driven development . 这方面的参数听起来相当抽象,但是我们可以向您保证,您会发现首先编写测试会导致代码更加健壮和设计更好。设计良好、覆盖率高的测试对重构的易用性有着巨大的影响。每当在一个例程中发现一个新的bug时,您应该为该特定的情况编写一个新的测试,并将其添加到测试套件中,以防止该bug在未被注意的情况下重新出现。

注解

Scipy使用的测试框架来自 numpy.testing ,因此下面显示的所有NumPy示例也适用于SciPy

测试NumPy

NumPy可以通过多种方式进行测试,选择任何让你感觉舒服的方式。

从Python内部运行测试

您可以通过以下方式测试已安装的NumPy numpy.test 例如,要运行NumPy的完整测试套件,请使用以下命令:

>>> import numpy
>>> numpy.test(label='slow')

测试方法可以有两个或更多的参数;第一个 label 是一个字符串,指定应该测试的内容和第二个 verbose 是表示输出详细程度的整数。查看文档字符串 numpy.test 详情。的默认值 label 是“快速”的,它将运行标准测试。字符串“full”将运行完整的测试电池,包括那些被标识为运行缓慢的测试。如果 verbose 小于等于1时,测试将只显示有关正在运行的测试的信息消息;但如果大于1,则测试还将提供有关缺少测试的警告。因此,如果要运行每个测试并获取有关哪些模块没有测试的消息:

>>> numpy.test(label='full', verbose=2) # or numpy.test('full', 2)

最后,如果您只对测试NumPy的一个子集感兴趣,例如 core 模块,使用以下内容:

>>> numpy.core.test()

从命令行运行测试

如果您想构建NumPy来处理NumPy本身,请使用 runtests.py 。要运行NumPy的完整测试套件:

$ python runtests.py

测试NumPy的子集:

$python runtests.py -t numpy/core/tests

有关测试的详细信息,请参阅 测试生成

运行测试的其他方法

使用您喜爱的IDE运行测试,例如 vscodepycharm

编写自己的测试

如果您正在编写一个希望成为NumPy一部分的包,请在开发包时编写测试。NumPy包目录中的每个Python模块、扩展模块或子包都应该有相应的 test_<name>.py 文件。Pytest检查这些文件中的测试方法(名为 test* )和测试类(命名为 Test*

假设你有一个NumPy模块 numpy/xxx/yyy.py 包含函数 zzz() . 要测试此函数,您将创建一个名为 test_yyy.py . 如果你只需要测试 zzz ,只需添加一个测试函数:

def test_zzz():
    assert_(zzz() == 'Hello from zzz')

通常,我们需要将一些测试组合在一起,因此我们创建一个测试类:

from numpy.testing import assert_, assert_raises

# import xxx symbols
from numpy.xxx.yyy import zzz

class TestZzz:
    def test_simple(self):
        assert_(zzz() == 'Hello from zzz')

    def test_invalid_parameter(self):
        assert_raises(...)

在这些试验方法中, assert_() 并利用相关函数检验某一假设是否有效。如果断言失败,则测试失败。注意python内置 assert 不应使用,因为它是在编译期间用 -O .

注意 test_ 函数或方法不应具有docstring,因为这样很难从运行测试套件的输出中识别测试 verbose=2 (或类似的冗长设置)。使用普通注释 (# )如有必要。

另外,由于NumPy的大部分代码都是最初编写时没有进行单元测试的遗留代码,因此仍有一些模块还没有进行测试。请随意选择其中一个模块并为其开发测试。

标记试验

与上述测试类似的未标记测试在默认情况下运行 numpy.test() 跑。如果您希望将测试标记为“慢”,因此将保留一个完整的 numpy.test(label='full') 跑,你可以给它贴上标签 pytest.mark.slow ::

import pytest

@pytest.mark.slow
def test_big(self):
    print('Big, slow test')

同样,对于方法:

class test_zzz:
    @pytest.mark.slow
    def test_simple(self):
        assert_(zzz() == 'Hello from zzz')

更简单的设置和拆卸功能/方法

测试按名称查找模块级或类级设置和拆卸函数;因此:

def setup():
    """Module-level setup"""
    print('doing setup')

def teardown():
    """Module-level teardown"""
    print('doing teardown')


class TestMe:
    def setup():
        """Class-level setup"""
        print('doing setup')

    def teardown():
        """Class-level teardown"""
        print('doing teardown')

函数和方法的设置和拆卸函数称为“fixture”,不鼓励使用它们。

参数测试

测试的一个非常好的特性是允许对一系列参数进行简单的测试——这对于标准单元测试来说是一个棘手的问题。使用 dec.paramaterize 装饰者。

博士学位论文

doctest是记录函数行为并允许同时测试该行为的一种方便方法。交互式Python会话的输出可以包含在函数的docstring中,测试框架可以运行该示例并将实际输出与预期输出进行比较。

可以通过添加 doctests 论据 test() 调用;例如,运行numpy.lib的所有测试(包括doctests)::

>>> import numpy as np
>>> np.lib.test(doctests=True)

这些doctest的运行方式就像它们在一个新的已执行的python实例中一样。 import numpy as np . 作为NumPy子包一部分的测试将已经导入该子包。E、 g.进行测试 numpy/linalg/tests/ ,将创建命名空间,以便 from numpy import linalg 已执行。

tests/

我们不将代码和测试放在同一个目录中,而是将给定子包的所有测试放在 tests/ 子目录。对于我们的示例,如果它不存在,则需要创建一个 tests/ 目录 numpy/xxx/ . 所以这条路 test_yyy.pynumpy/xxx/tests/test_yyy.py .

一旦 numpy/xxx/tests/test_yyy.py 是书面的,它可以通过转到 tests/ 目录和键入:

python test_yyy.py

或者如果你添加 numpy/xxx/tests/ 对于python路径,可以在解释器中以交互方式运行测试,如下所示:

>>> import test_yyy
>>> test_yyy.test()

__init__.py and setup.py

但是,通常添加 tests/ 不需要python路径的目录。相反,最好直接从模块调用测试 xxx . 为此,只需将以下行放在包的末尾 __init__.py 文件::

...
def test(level=1, verbosity=1):
    from numpy.testing import Tester
    return Tester().test(level, verbosity)

您还需要在安装程序的配置部分添加tests目录。py::

...
def configuration(parent_package='', top_path=None):
    ...
    config.add_subpackage('tests')
    return config
...

现在,您可以执行以下操作来测试模块:

>>> import numpy
>>> numpy.xxx.test()

另外,当调用整个NumPy测试套件时,您的测试将被找到并运行:

>>> import numpy
>>> numpy.test()
# your tests are included and run automatically!

小窍门

创建许多类似的测试

如果您有一组必须多次运行的测试,但有一些细微的变化,那么创建一个包含所有公共测试的基类,然后为每个变化创建一个子类可能会很有帮助。这种技术的几个例子存在于numpy中;下面是 numpy/linalg/tests/test_linalg.py ::

class LinalgTestCase:
    def test_single(self):
        a = array([[1.,2.], [3.,4.]], dtype=single)
        b = array([2., 1.], dtype=single)
        self.do(a, b)

    def test_double(self):
        a = array([[1.,2.], [3.,4.]], dtype=double)
        b = array([2., 1.], dtype=double)
        self.do(a, b)

    ...

class TestSolve(LinalgTestCase):
    def do(self, a, b):
        x = linalg.solve(a, b)
        assert_almost_equal(b, dot(a, x))
        assert_(imply(isinstance(b, matrix), isinstance(x, matrix)))

class TestInv(LinalgTestCase):
    def do(self, a, b):
        a_inv = linalg.inv(a)
        assert_almost_equal(dot(a, a_inv), identity(asarray(a).shape[0]))
        assert_(imply(isinstance(a, matrix), isinstance(a_inv, matrix)))

在本例中,我们希望使用多个数据类型的矩阵测试解决线性代数问题,使用 linalg.solvelinalg.inv . 常见的测试案例(单精度、双精度等矩阵)收集在 LinalgTestCase .

已知故障和跳过测试

当某个特定的测试在某个特定的测试之前,或者某个特定的测试在某个特定的测试中失败时,您可能想跳过它。

要跳过测试,只需使用 skipif ::

import pytest

@pytest.mark.skipif(SkipMyTest, reason="Skipping this test because...")
def test_something(foo):
    ...

如果 SkipMyTest 计算结果为非零,详细测试输出中的消息是给定给 skipif . 同样,可以使用 xfail ::

import pytest

@pytest.mark.xfail(MyTestFails, reason="This test is known to fail because...")
def test_something_else(foo):
    ...

当然,可以使用 skipxfail 没有争论。

在测试运行结束时,将显示已跳过和已知失败测试的总数。跳过的测试标记为 'S' 在测试结果中(或 'SKIPPED' 对于 verbose > 1 )和已知的失败测试标记为 'x' (或) 'XFAIL' 如果 verbose > 1

随机数据测试

对随机数据的测试是很好的,但是由于测试失败意味着要暴露新的错误或回归,所以大多数时候通过测试但偶尔失败而没有代码更改的测试是没有帮助的。通过在生成随机数种子之前设置随机数种子,使随机数据具有确定性。使用python的 random.seed(some_number) 还是NumPy的 numpy.random.seed(some_number) ,取决于随机数的来源。

或者,您可以使用 Hypothesis 生成任意数据。假设为您管理Python和Numpy的随机种子,并提供了一种非常简洁和强大的方法来描述数据(包括 hypothesis.extra.numpy ,例如,对于一组相互可广播的形状)。

与随机生成相比,它的优点包括可以在不需要固定种子的情况下重放和共享故障的工具,包括报告 极小值 每一个失败的例子,并优于天真的随机技术触发错误。

文件 numpy.test

numpy.test(label='fast', verbose=1, extra_argv=None, doctests=False, coverage=False, durations=- 1, tests=None)

Pyrunner测试。

测试函数通常添加到包的 __init__. 就像这样:

from numpy._pytesttester import PytestTester
test = PytestTester(__name__).test
del PytestTester

调用此测试函数将查找并运行与模块及其所有子模块关联的所有测试。

参数
module_name模块名

要测试的模块的名称。

笔记

与以前不同 nose -基于实现,这个类在执行某些操作时不会公开 numpy -特定警告抑制。

属性
module_nameSTR

要测试的包的完整路径。