NumPy参考 >例行程序 >Test Support (numpy.testing) > Testing Guidelines
在1.15版之前,NumPy使用鼻子测试框架,现在使用pytest框架。为了支持使用旧numpy框架的下游项目,仍旧维护了旧框架,但是所有针对NumPy的测试都应使用pytest。
我们的目标是SciPy和NumPy中的每个模块和软件包都应具有一套完整的单元测试。这些测试应使用给定例程的全部功能,以及对错误或意外输入参数的鲁棒性。长期的经验表明,到目前为止,编写测试的最佳时间是在编写或更改代码之前-这是 测试驱动的开发。这听起来很抽象,但我们可以向您保证,您会发现编写测试首先会导致更健壮和更好地设计代码。设计良好且覆盖范围广的测试对重构的便利性有巨大的影响。每当在例程中发现新错误时,您都应针对该特定情况编写一个新测试,并将其添加到测试套件中,以防止该错误再次被发现。
要运行SciPy的完整测试套件,请使用以下命令:
>>> import scipy
>>> scipy.test()
或从命令行:
$ python runtests.py
SciPy使用来自的测试框架numpy.testing
,因此此处显示的所有SciPy示例也适用于NumPy。NumPy的完整测试套件可以按以下方式运行:
>>> import numpy
>>> numpy.test()
测试方法可以使用两个或多个参数。第一个label
是指定测试对象的字符串,第二个verbose
是给出输出详细程度的整数。有关详细信息,请参见numpy.test的文档字符串。的默认值为label
“快速”-将运行标准测试。字符串“ full”将运行全部测试,包括那些确定为运行缓慢的测试。如果verbose
小于等于1,则测试将仅显示有关正在运行的测试的信息消息;否则为0。但是如果大于1,则测试还将针对缺少的测试提供警告。因此,如果您想运行每个测试并获取有关哪些模块没有测试的消息:
>>> scipy.test(label='full', verbose=2) # or scipy.test('full', 2)
最后,如果您仅对测试SciPy的子集(例如integrate
模块)感兴趣,请使用以下命令:
>>> scipy.integrate.test()
或从命令行:
$python runtests.py -t scipy/integrate/tests
本页面的其余部分将为您提供有关如何向SciPy中的模块添加单元测试的基本思想。对我们而言,进行大量的单元测试非常重要,因为该代码将由科学家和研究人员使用,并且由遍布世界各地的许多人开发。因此,如果您要编写一个要加入SciPy的程序包,请在开发程序包时编写测试。同样,由于SciPy的大部分是遗留代码,最初是在没有单元测试的情况下编写的,因此仍有几个模块尚未进行测试。阅读本简介时,请随时选择这些模块之一并为其开发测试。
SciPy软件包目录中的每个Python模块,扩展模块或子软件包都应有一个对应的test_<name>.py
文件。Pytest检查这些文件中的测试方法(名为test *)和测试类(名为Test *)。
假设您有一个scipy/xxx/yyy.py
包含function 的SciPy模块zzz()
。要测试此功能,您将创建一个名为的测试模块test_yyy.py
。如果您只需要测试的一个方面
zzz
,则只需添加一个测试功能即可:
def test_zzz():
assert_(zzz() == 'Hello from zzz')
通常,我们需要将多个测试分组在一起,因此我们创建了一个测试类:
from numpy.testing import assert_, assert_raises
# import xxx symbols
from scipy.xxx.yyy import zzz
class TestZzz:
def test_simple(self):
assert_(zzz() == 'Hello from zzz')
def test_invalid_parameter(self):
assert_raises(...)
在这些测试方法中,assert_()
相关功能用于测试某个假设是否有效。如果断言失败,则测试失败。请注意,assert
不应使用Python内置函数,因为在编译过程中会剥离它-O
。
请注意,test_
函数或方法不应包含文档字符串,因为这样会使从带有verbose=2
(或类似详细程度设置)运行测试套件的输出中识别测试变得困难。#
如有必要,请使用纯注释()。
作为替代pytest.mark.<label>
,您可以使用许多标签。
未标记的测试(如上述测试)以默认
scipy.test()
运行方式运行。如果您想将测试标记为慢速-因此保留用于完整scipy.test(label='full')
运行,则可以使用装饰器将其标记为:
# numpy.testing module includes 'import decorators as dec'
from numpy.testing import dec, assert_
@dec.slow
def test_big(self):
print 'Big, slow test'
方法类似:
class test_zzz:
@dec.slow
def test_simple(self):
assert_(zzz() == 'Hello from zzz')
可用的标签有:
slow
:将测试标记为需要很长时间
setastest(tf)
:测试名称不一致时解决测试发现的方法
skipif(condition, msg=None)
:当跳过测试eval(condition)
是
True
knownfailureif(fail_cond, msg=None)
:如果eval(fail_cond)
为is True
,将避免运行测试
,对于有条件的段错误测试很有用
deprecated(conditional=True)
:过滤测试中发出的弃用警告
paramaterize(var, input)
:pytest.mark.paramaterized的替代
品
测试按名称查找模块级别或类级别的设置和拆卸功能。从而:
def setup():
"""Module-level setup"""
print 'doing setup'
def teardown():
"""Module-level teardown"""
print 'doing teardown'
class TestMe(object):
def setup():
"""Class-level setup"""
print 'doing setup'
def teardown():
"""Class-level teardown"""
print 'doing teardown'
功能和方法的设置和拆卸功能称为“固定装置”,不鼓励使用它们。
测试的一个非常不错的功能是可以轻松地对一系列参数进行测试,这对于标准单元测试是一个棘手的问题。使用
dec.paramaterize
装饰器。
Doctests是记录函数行为并允许同时测试该行为的便捷方法。交互式Python会话的输出可以包含在函数的文档字符串中,并且测试框架可以运行该示例并将实际输出与预期输出进行比较。
可以通过将doctests
参数添加到
test()
调用中来运行doctest 。例如,运行numpy.lib的所有测试(包括doctests):
>>> import numpy as np
>>> np.lib.test(doctests=True)
doctest的运行就像它们在已执行的新Python实例中一样。作为SciPy子程序包一部分的测试将具有已导入的子程序包。例如,对于中的测试,将创建已执行的名称空间
。import numpy as np
scipy/linalg/tests/
from scipy import linalg
tests/
¶与其将代码和测试保留在同一目录中,我们将给定子包的所有测试都放在一个tests/
子目录中。对于我们的示例,如果尚不存在,则需要在中创建tests/
目录scipy/xxx/
。所以对于路径test_yyy.py
就是scipy/xxx/tests/test_yyy.py
。
一旦scipy/xxx/tests/test_yyy.py
写入,就可以通过转到tests/
目录并键入以下命令来运行测试:
python test_yyy.py
或者,如果添加scipy/xxx/tests/
到Python路径,则可以在解释器中以交互方式运行测试,如下所示:
>>> import test_yyy
>>> test_yyy.test()
__init__.py
和setup.py
¶但是,通常tests/
不希望将目录添加到python路径。相反,最好直接从模块调用测试xxx
。为此,只需将以下行放在包__init__.py
文件的末尾:
...
def test(level=1, verbosity=1):
from numpy.testing import Tester
return Tester().test(level, verbosity)
您还需要在setup.py的配置部分中添加测试目录:
...
def configuration(parent_package='', top_path=None):
...
config.add_data_dir('tests')
return config
...
现在,您可以执行以下操作来测试您的模块:
>>> import scipy
>>> scipy.xxx.test()
另外,当调用整个SciPy测试套件时,将找到并运行您的测试:
>>> import scipy
>>> scipy.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.solve
和
来测试使用几种数据类型的矩阵来解决线性代数问题linalg.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):
...
Of course, a test can be unconditionally skipped or marked as a known
failure by using skip
or xfail
without argument, respectively.
A total of the number of skipped and known failing tests is displayed
at the end of the test run. Skipped tests are marked as 'S'
in
the test results (or 'SKIPPED'
for verbose > 1
), and known
failing tests are marked as 'x'
(or 'XFAIL'
if verbose >
1
).
对随机数据进行测试是好的,但是由于测试失败是为了暴露新的错误或回归,因此在大多数情况下通过但偶尔失败且没有代码更改的测试无济于事。通过在生成随机数种子之前对其进行设置,使随机数据具有确定性。根据随机数的来源使用Python random.seed(some_number)
或NumPy
numpy.random.seed(some_number)
。