测试指南

本节描述了astropycore和coordinated包中测试的测试框架和格式标准,并作为附属包的建议。

测试框架

astropy使用的测试框架(以及使用 Astropy package templatepytest 框架。

测试依赖项

Astropy测试运行程序使用的依赖项由一个名为 pytest-astropy . 此软件包提供 pytest 除了几个 pytest Astropy使用的插件,也将对其他包具有通用性。

由于实际安装或使用Astropy并不需要测试依赖项,因此它们不包含在 install_requires 在里面 setup.cfg . 相反,它们列在 extras_require 调用的节 test 在里面 setup.cfg . 想要运行测试套件的开发人员需要直接安装pytest astropy::

pip install pytest-astropy

或者以“可编辑”模式安装核心包,指定 [test] 选项:

pip install -e .[test]

插件的详细描述可以在 Pytest插件 部分。

运行试验

目前有三种不同的方法来调用天体测试。每个方法调用 pytest 运行测试,但在调用时提供不同的选项。要运行测试,您需要确保 pytest 已安装程序包。

除了运行Astropy测试之外,还可以调用这些方法,以便检查Python源代码 PEP8 compliance . 所有PEP8测试选项都需要 pytest-pep8 plugin ,必须单独安装。

托克斯

运行测试的最可靠的方法(也可能是最慢的)是利用 Tox ,这是一个用于自动化Python测试的通用工具。tox的好处之一是,它首先创建被测试包的源发行版,并在运行测试之前将其与包中声明的任何依赖项一起安装到新的虚拟环境中。因此,这可以捕获与未声明的包数据或缺少依赖项相关的问题。由于我们使用tox对持续集成服务运行许多测试,因此它在许多情况下也可以用来重现在这些服务上看到的问题。

要使用tox运行测试,首先确保安装了tox,例如:

pip install tox

然后使用以下命令运行基本测试套件:

tox -e test

或使用以下命令运行具有所有可选依赖项的测试套件:

tox -e test-alldeps

您可以使用以下内容查看可用测试环境的列表:

tox -l -v

这也解释了它们各自的作用。

也可以直接运行-for-instance相关的命令:

tox -e codestyle

将使用flake8工具运行检查。

在运行tox时可以将选项传递给pytest-若要执行此操作,请添加一个 -- 在常规的tox命令之后,之后的任何内容都将传递给pytest,例如:

tox -e test -- -v --pdb

这可以与 -P 选项由 pytest-filter-subpackage 插件只运行测试套件的一部分。

脓包

测试套件也可以直接从本机运行 pytest 命令,这通常比使用tox进行迭代开发要快。在这种情况下,开发人员必须意识到他们必须通过运行以下命令手动重建任何扩展:

pip install -e .[test]

在使用pytest运行测试之前:

pytest

而不是打电话 pip install -e .[test] ,也可以使用以下命令生成扩展:

python setup.py build_ext --inplace

这也避免了在当前环境中安装astropy的开发人员版本,但是请注意 pip 如果您需要测试依赖于 entry points 正在安装。

可以只运行特定子包或一组子包的测试。例如,只运行 wcs 来自命令行的测试:

pytest -P wcs

或者,只运行 wcsutils 测验::

pytest -P wcs,utils

您也可以从命令行指定要测试的单个目录或文件,例如:

pytest astropy/modeling

或:

pytest astropy/wcs/tests/test_wcs.py

这对 .rst 文件也:

pytest astropy/wcs/index.rst

astropy.test()

测试可以从已安装的Astropy版本运行:

import astropy
astropy.test()

这将运行Astropy的所有默认测试(但不会运行 .rst 文档,因为这些文件没有安装)。

可以通过在调用中指定包来运行特定包的测试 test() 功能:

astropy.test(package='io.fits')

此方法仅适用于可以映射到Astropy目录的包名。另一种方法是使用 test_path 选项:

astropy.test(test_path='wcs/tests/test_wcs.py')

这个 test_path 必须相对于工作目录或绝对指定。

默认情况下 astropy.test() 将跳过从internet检索数据的测试。要启用这些测试,请使用 remote_data 旗帜:

astropy.test(package='io.fits', remote_data=True)

此外, test 函数支持任何可以传递给的选项 pytest.main() 和便利的选择 verbose=pastebin= .

启用PEP8符合性测试 pep8=True 在召唤 astropy.test . 这将启用PEP8检查并禁用常规测试。

Astropy测试函数

astropy.test(**kwargs)

运行包的测试。

此方法为生成参数,然后调用 pytest.main .

参数
package可选的STR

要测试的特定包的名称,例如'io.配合'或'utils'。接受逗号分隔字符串以指定多个包。如果未指定任何内容,则运行所有默认测试。

args可选的STR

要传递给的其他参数 pytest.mainargs 关键字参数。

docs_path可选的STR

documentation.rst文件的路径。

open_files可选的布尔

当任何测试打开文件时失败。默认情况下关闭,因为这会向测试套件添加额外的运行时间。需要 psutil 包裹。

parallelint或“auto”,可选

如果提供,请在指定数量的CPU上并行运行测试。如果并行是 'auto' ,它将使用机器上的所有核心。需要 pytest-xdist 插件。

pastebin('failed','all',None),可选

打开pytest pastebin输出的便利选项。设置为“failed”可上载失败测试的信息,或设置为“all”可上载所有测试的信息。

pdb可选的布尔

对失败的测试启用PDB事后分析。与指定相同 --pdb 在里面 args .

pep8可选的布尔

通过pytest-PEP8插件打开PEP8检查并禁用正常测试。与指定相同 --pep8 -k pep8 在里面 args .

plugins可选列表

要传递给的插件 pytest.mainplugins 关键字参数。

remote_data{'none'、'astropy'、'any'},可选

控制是否运行标记为的测试@pytest.mark.remote_数据. 这可以设置为不使用远程数据运行测试 (none ),仅使用来自http://data.astropy.org (astropy ),或使用远程数据的所有测试 (any )默认值为 none .

重复int 可选可选的

如果设置,则指定每个测试应运行多少次。这对于诊断偶发故障非常有用。

skip_docsbool 可选可选的布尔

什么时候? True ,跳过运行.rst文件中的doctest。

test_path可选的STR

指定要按路径测试的位置。可以是单个文件或目录。必须绝对指定或相对于调用目录指定。

verbose可选的布尔

打开pytest的详细输出的便利选项。传递True与指定 -v 在里面 args .

测试运行选项

测试打开的文件

使用 pytest打开文件 插件(在安装pytest astropy时自动安装),我们可以测试任何单元测试是否无意中打开了任何文件。由于这会大大减慢运行测试所需的时间,因此默认情况下将其关闭。

要从命令行使用它,请执行以下操作:

pytest --open-files

要从Python中使用它,请执行以下操作:

>>> import astropy
>>> astropy.test(open_files=True)

有关 pytest-openfiles 插件请参见 pytest打开文件

测试覆盖率报告

可以使用 pytest-cov 插件(在安装pytest astropy时自动安装),例如:

pytest --cov astropy --cov-report html

内部有一些配置 setup.cfg 定义要忽略的文件和要排除的行的文件。

并行运行测试

使用 pytest-xdist 插件。

安装后,可以使用 '-n' 命令行选项。例如,要使用4个过程:

pytest -n 4

通过 -n auto 在计算机上创建与核心相同数量的进程。

类似地,可以从 astropy.test ::

>>> import astropy
>>> astropy.test(parallel=4)

写作测试

pytest 具有以下测试发现规则:

  • test_*.py*_test.py 文件夹

  • Test 带前缀的类(没有 __init__ 方法)

  • test_ 带前缀的函数和方法

咨询 test discovery rules 有关如何命名文件和测试以便由自动发现它们的详细信息 pytest .

简单的例子

下面的示例显示了一个简单函数和测试此函数的测试:

def func(x):
    """Add one to the argument."""
    return x + 1

def test_answer():
    """Check the return value of func() for an example argument."""
    assert func(3) == 5

如果我们把这个放在 test.py 文件然后运行:

pytest test.py

其结果是:

============================= test session starts ==============================
python: platform darwin -- Python 3.x.x -- pytest-x.x.x
test object 1: /Users/username/tmp/test.py

test.py F

=================================== FAILURES ===================================
_________________________________ test_answer __________________________________

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

test.py:5: AssertionError
=========================== 1 failed in 0.07 seconds ===========================

测试放在哪里

包装特定试验

每个包应该包括一套单元测试,尽可能多地覆盖公共方法/函数。这些测试应包含在每个子包中,例如:

astropy/io/fits/tests/

tests 目录应包含 __init__.py 文件,以便可以导入测试,并且可以使用相对导入。

互操作性测试

涉及两个或多个子包的测试应包括在:

astropy/tests/

回归检验

任何时候只要一个bug被修复,只要有可能,就应该添加一个或多个回归测试,以确保将来不会引入该bug。回归测试应该包括报告错误的票证URL。

使用数据文件

需要使用数据文件的测试应使用 get_pkg_data_fileobjget_pkg_data_filename 功能。这些函数首先在本地搜索,然后在astropy数据服务器或任意URL上搜索,然后分别返回一个类似文件的对象或一个本地文件名。如果获得远程数据,它们会自动在本地缓存数据,然后本地副本将被透明地使用。有关在测试中处理缓存的特定注意事项,请参阅下一节。

它们还支持使用MD5散列来获取数据文件的特定版本。在向astropy数据服务器提交文件之前,可以使用 compute_hash 函数在文件的本地副本上。

可能检索远程数据的测试应标记为 @pytest.mark.remote_data 装饰器,或者,如果是医生测试,则标记为 REMOTE_DATA 旗子。默认情况下,以这种方式标记的测试将被跳过 astropy.test() 以防止测试运行花费太长时间。这些测试可以由 astropy.test() 通过添加 remote_data='any' 旗子。使用在命令行中打开远程数据测试 pytest --remote-data=any .

可以用测试来标记 @pytest.mark.remote_data(source='astropy') ,它可用于指示唯一需要的数据来自http://data.astropy.org服务器。要仅启用这些测试,可以使用 pytest --remote-data=astropy .

有关 pytest-remotedata 插件,请参见 pytest远程数据 .

实例

from ...config import get_data_filename

def test_1():
    """Test version using a local file."""
    #if filename.fits is a local file in the source distribution
    datafile = get_data_filename('filename.fits')
    # do the test

@pytest.mark.remote_data
def test_2():
    """Test version using a remote file."""
    #this is the hash for a particular version of a file stored on the
    #astropy data server.
    datafile = get_data_filename('hash/94935ac31d585f68041c08f87d1a19d4')
    # do the test

def doctest_example():
    """
    >>> datafile = get_data_filename('hash/94935')  # doctest: +REMOTE_DATA
    """
    pass

这个 get_remote_test_data 将文件放在由指定的临时目录中 tempfile 模块,以便测试文件最终将被系统删除。从长远来看,一旦测试数据文件变得太大,我们就需要设计一种机制来立即删除测试数据。

使用文件缓存的测试

默认情况下,Astropy测试运行程序在一个临时目录中设置一个干净的文件缓存,该目录仅用于该测试运行,然后销毁。这是为了确保测试运行之间的一致性,以及不混乱用户的缓存(即 get_cache_dir )有测试文件。

然而,一些测试作者(尤其是附属的软件包)可能会发现,希望将测试运行期间下载的文件缓存到更永久的位置(例如,对于大型数据集)。为此目的 set_temp_cache 可以使用助手。它可以用作测试中的上下文管理器以临时将缓存设置为自定义位置,也可以用作 decorator 这将对整个测试函数生效(不包括setup或teardown,它们必须单独进行修饰)。

此外,通过设置 XDG_CACHE_HOME 环境变量。

创建文件的测试

测试通常是从用户没有写权限的目录运行的,所以创建文件的测试应该总是在临时目录中运行。这可以用 pytest 'tmpdir' fixture 或者使用Python内置的 tempfile module .

安装/拆卸试验

在某些情况下,运行一系列需要先设置的测试是很有用的。有四种方法:

模块级设置/拆卸

如果 setup_moduleteardown_module 函数是在文件中指定的,它们分别在文件中的所有测试之前和之后调用。这些函数有一个参数,即模块本身,这使得设置模块范围内的变量变得非常容易:

def setup_module(module):
    """Initialize the value of NUM."""
    module.NUM = 11

def add_num(x):
    """Add pre-defined NUM to the argument."""
    return x + NUM

def test_42():
    """Ensure that add_num() adds the correct NUM to its argument."""
    added = add_num(42)
    assert added == 53

例如,我们可以使用这个来下载一个远程测试数据文件,并让文件中的所有函数访问它:

import os

def setup_module(module):
    """Store a copy of the remote test file."""
    module.DATAFILE = get_remote_test_data('94935ac31d585f68041c08f87d1a19d4')

def test():
    """Perform test using cached remote input file."""
    f = open(DATAFILE, 'rb')
    # do the test

def teardown_module(module):
    """Clean up remote test file copy."""
    os.remove(DATAFILE)

等级设置/拆除

测试可以组织成具有自己的setup/teardown函数的类。以下内容:

def add_nums(x, y):
    """Add two numbers."""
    return x + y

class TestAdd42(object):
    """Test for add_nums with y=42."""

    def setup_class(self):
        self.NUM = 42

    def test_1(self):
        """Test behavior for a specific input value."""
        added = add_nums(11, self.NUM)
        assert added == 53

    def test_2(self):
        """Test behavior for another input value."""
        added = add_nums(13, self.NUM)
        assert added == 55

    def teardown_class(self):
        pass

在上面的示例中, setup_class 方法,然后调用类中的所有测试,最后调用 teardown_class 被称为。

方法级设置/拆卸

在某些情况下,您可能希望setup和teardown方法在之前和之后运行 each 测试。为此,请使用 setup_methodteardown_method 方法::

def add_nums(x, y):
    """Add two numbers."""
    return x + y

class TestAdd42(object):
    """Test for add_nums with y=42."""

    def setup_method(self, method):
        self.NUM = 42

    def test_1(self):
    """Test behavior for a specific input value."""
        added = add_nums(11, self.NUM)
        assert added == 53

    def test_2(self):
    """Test behavior for another input value."""
        added = add_nums(13, self.NUM)
        assert added == 55

    def teardown_method(self, method):
        pass

功能级设置/拆卸

最后,我们可以使用 setup_functionteardown_function 定义在模块中每个函数前后运行的设置/拆卸机制。它们有一个参数,即被测试的函数:

def setup_function(function):
    pass

def test_1(self):
   """First test."""
    # do test

def test_2(self):
    """Second test."""
    # do test

def teardown_function(function):
    pass

基于属性的测试

Property-based testing 让你专注于测试中重要的部分,通过更一般的声明——“对任何两个数字有效”而不是“对1+2有效”。试想一下,如果随机测试给了你最小的、非片状的失败例子,以及一种清晰的方法来描述甚至是最复杂的数据——那就是基于属性的测试!

pytest-astropy 包括对 Hypothesis ,因此安装很简单-您可以阅读文档或 work through the tutorial 开始编写测试,比如:

from astropy.coordinates import SkyCoord
from hypothesis import given, strategies as st

@given(
    st.builds(SkyCoord, ra=st.floats(0, 360), dec=st.floats(-90, 90))
)
def test_coordinate_transform(coord):
    """Test that sky coord can be translated from ICRS to Galactic and back."""
    assert coord == coord.galactic.icrs  # floating-point precision alert!

您可以测试的其他属性包括:

  • 对于无失真映射,从图像到天空坐标和返回的往返应该是无损的,否则总是低于10^-5像素。

  • 花一点时间,在不同的帧之间来回切换,检查它没有改变或失去精度。(或至少不超过一纳秒)

  • IO例程将要处理的无损往返数据

  • 优化的例程在公差范围内计算与未优化相同的结果

这是一个开始为Astropy做贡献的好方法,并且已经在时间处理中发现了bug。有关详细信息,请参阅问题9017和拉取请求9532!

(如果你发现假说对你的研究有用, please cite it !)

参数化测试

如果要对稍微不同的值运行多次测试,可以使用 pytest 避免编写单独的测试。例如,不要写:

def test1():
    assert type('a') == str

def test2():
    assert type('b') == str

def test3():
    assert type('c') == str

你可以使用 @pytest.mark.parametrize decorator为每个输入简洁地创建一个测试函数:

@pytest.mark.parametrize(('letter'), ['a', 'b', 'c'])
def test(letter):
    """Check that the input is a string."""
    assert type(letter) == str

作为指导,使用 parametrize 如果你能列举所有可能的测试用例,每一个失败都会是一个不同的问题,并且假设当有很多可能的输入或者你只想报告一个简单的失败。

需要可选依赖项的测试

对于测试需要可选依赖项的函数或方法的测试(例如Scipy),如果依赖项不存在,则应指示pytest跳过测试。下面的示例说明了应该如何做到这一点:

import pytest

try:
    import scipy
    HAS_SCIPY = True
except ImportError:
    HAS_SCIPY = False

@pytest.mark.skipif('not HAS_SCIPY')
def test_that_uses_scipy():
    ...

这样,如果存在Scipy,则运行测试;如果不存在,则跳过测试。任何测试都不应因为不存在可选依赖项而失败。

使用pytest助手函数

如果你的测试需要使用 pytest helper functions ,如 pytest.raises ,导入 pytest 像这样进入你的测试模块:

import pytest

测试警告

为了测试在某些情况下警告是否按预期触发, pytest 提供自己的上下文管理器 pytest.warns 完全类似于 pytest.raises (见下文)允许显式探测特定的警告类,并通过 match 争论,信息。请注意,当没有触发指定类型的警告时,这将使测试失败。当检查可选但非强制警告时, pytest.warns(None) 可以用来捕捉和检查它们。

备注

pytest 也可以选择使用 recwarn 函数参数来测试警告是否在整个嵌入函数中触发。这种方法至少在一个案例中被发现有问题 (pull request 1174

测试异常

就像上面描述的处理警告一样,设计用来触发某些错误的测试应该验证预期类型的异常是否在预期的位置引发。通过在 pytest.raises 上下文管理器。它是可选的 match 参数允许使用 regex 语法。例如火柴 pytest.raises(OSError, match=r'^No such file')pytest.raises(OSError, match=r'or directory$') 相当于 assert str(err).startswith(No such file)assert str(err).endswith(or directory) ,分别在引发的错误消息上 err . 对于匹配多行消息,您需要传递 (?s) flag 到基础 re.search ,如下例所示:

with pytest.raises(fits.VerifyError, match=r'(?s)not upper.+ Illegal key') as excinfo:
    hdu.verify('fix+exception')
assert str(excinfo.value).count('Card') == 2

此调用还演示了如何获取 ExceptionInfo 对象返回以对该信息执行其他诊断。

测试配置参数

为了确保测试的再现性,当测试运行程序启动时,所有配置项都将重置为其默认值。

有时,您需要在某个配置项设置为特定值时测试代码的行为。在这种情况下,您可以使用 astropy.config.ConfigItem.set_temp 上下文管理器将配置项临时设置为该值,在该上下文中进行测试,并使其自动返回其原始值。

例如::

def test_pprint():
    from ... import conf
    with conf.set_temp('max_lines', 6):
        # ...

标记要排除在覆盖范围之外的代码块

通过添加包含短语的注释,覆盖测试可以忽略代码块 pragma: no cover 到块的开头:

if this_rarely_happens:  # pragma: no cover
    this_call_is_ignored()

使用pytest mpl进行图像测试

运行映像测试

我们利用 pytest-mpl 一个插件,用于编写测试,我们可以逐像素比较绘图命令的输出与参考文件(例如,在 astropy.visualization.wcsaxes

要使用图像比较运行Astropy测试,请使用:

pytest --mpl --remote-data

但是,请注意,输出可能对Matplotlib的版本及其所有依赖项(例如freetype)非常敏感,因此我们建议在 Docker 包含一组冻结的包版本的容器(Docker容器可以看作是迷你虚拟机)。我们做了一个 set of Docker container images 可以用来做这个。安装了Docker之后,要在Docker容器中运行带有图像比较的Astropy测试,请确保您位于Astropy存储库(或正在测试的包的存储库)中,然后执行以下操作:

docker run -it -v ${PWD}:/repo astropy/image-tests-py35-mpl300:1.3 /bin/bash

这将在Docker容器中启动bash提示符,您应该看到如下内容:

root@8173d2494b0b:/#

你现在可以去 /repo 目录,它与您正在测试的存储库的本地版本相同:

cd /repo

然后您可以按上述方式运行测试:

pip install -e .[test]
pytest --mpl --remote-data

类型 exit 离开容器。

您可以在上找到可用Docker图像的名称 Docker Hub .

编写图像测试

这个 README.rst 因为该插件包含有关使用此插件编写测试的信息。与这些指令相比,唯一的关键是 baseline_dir ::

from astropy.tests.image_tests import IMAGE_REFERENCE_DIR

@pytest.mark.mpl_image_compare(baseline_dir=IMAGE_REFERENCE_DIR)

这是因为由于参考图像文件会对存储库的大小产生重要影响,所以我们将它们存储在http://data.astropy.org网站。缺点是创建或重新生成引用文件稍微复杂一些,但是我们在这里描述这个过程。

生成参考图像

一旦你有了一个你想要(重新)生成参考图像的测试,启动一个Docker容器,例如:

docker run -it -v ${PWD}:/repo astropy/image-tests-py35-mpl300:1.3 /bin/bash

然后在里面进行测试 /repo--mpl-generate-path 参数,例如:

cd repo
pip install -e .[test]
pytest --mpl --mpl-generate-path=reference_tmp --remote-data

这将创建一个 reference_tmp 文件夹并将生成的参考图像放入其中-文件夹将在Docker容器外的存储库中可用。类型 exit 离开容器。

确保使用可用的容器为不同支持的Matplotlib版本生成图像。

上传参考图像

接下来,我们需要将这些图像添加到http://data.astropy.org服务器。为此,打开一个拉取请求 this 存储库。Astropy 测试的参考图像应该放在 testing/astropy 目录。在那个目录中是名为时间戳的文件夹。如果只是添加新测试,请将引用文件添加到最近的目录中。

如果由于Astropy中的更改而重新生成基线图像,请通过复制最新的时间戳目录来创建新的时间戳目录,然后替换所有已更改的基线图像。注意,由于Matplotlib版本之间的变化,我们需要为每个主要Matplotlib版本添加一整套参考图像。因此,在每个时间戳文件夹中,都有名为。 1.4.x1.5.x .

一旦参考图像被合并并在上可用http://data.astropy.org,更新 IMAGE_REFERENCE_DIR 中的变量 astropy.tests.image_tests 子模块。因为时间戳是硬编码的,所以添加一个新的时间戳目录不会影响到Astropy的发布版本的测试,所以您可以轻松地添加和调整一个新的时间戳目录,同时仍然在处理Astropy的pull请求。

撰写博士论文

Python中的doctest是一种特殊的测试,它嵌入在函数、类或模块的docstring中,或者嵌入到Sphinx文档中,并且被格式化为类似于Python交互会话的格式——也就是说,它们显示在 >>> prompt后接在交互式会话中运行该代码时预期的输出(如果有)。

其思想是用docstring编写用法示例,用户可以逐字输入并对照预期输出检查输出,以确认他们正确地使用了接口。

此外,Python还包括一个 doctest 模块,该模块可以检测这些doctest并将其作为项目自动化测试套件的一部分执行。这样我们就可以自动确保docstring中所有doctest类的示例都是正确的。

Astropy测试套件自动检测并运行Astropy源代码或文档中的任何doctest,或者使用Astropy测试运行框架的包中的任何doctest。例如doctest和关于如何编写它们的详细文档,请参阅 doctest 文档。

备注

由于Sphinx的叙述性文档没有与astropy源代码一起安装,因此只能通过运行 pytest 直接(或通过毒物检查),而不是 import astropy; astropy.test() .

有关 pytest-doctestplus Astropy使用的插件,请参见 pytest doctestplus .

跳过医生测试

有时有必要编写看起来像doctest但实际上不是逐字可执行的示例。例如,一个例子可能取决于某些外部条件的满足。在这些情况下,有几种方法可以跳过医生测试:

  1. 在示例旁边添加注释,如: # doctest: +SKIP . 例如:

    >>> import os
    >>> os.listdir('.')  # doctest: +SKIP
    

    在上面的示例中,我们希望引导用户运行 os.listdir('.') 但我们不希望这行代码作为doctest的一部分执行。

    要跳过需要获取远程数据的测试,请使用 REMOTE_DATA 改为标记。这样就可以使用 --remote-data 运行测试时的标志:

    >>> datafile = get_data_filename('hash/94935')  # doctest: +REMOTE_DATA
    
  2. Astropy的测试框架增加了对 __doctest_skip__ 变量,该变量可以放在任何模块的模块级,以列出该模块中不应运行其doctest的函数、类和方法。也就是说,如果将函数的示例用法作为doctest运行没有意义,那么可以在doctest收集阶段跳过整个函数。

    价值 __doctest_skip__ 应该是所有函数/类的通配符模式列表,这些函数/类的doctest应该被跳过。例如::

    __doctest_skip__ = ['myfunction', 'MyClass', 'MyClass.*']
    

    跳过调用的函数中的doctest myfunction ,类的doctest MyClass ,以及所有 方法 属于 MyClass .

    模块docstrings也可以包含doctest。若要跳过模块级doctest,请包含字符串 '.' 在里面 __doctest_skip__ .

    跳过所有模块测试:

    __doctest_skip__ = ['*']
    
  3. 在Sphinx文档中,doctest部分可以通过将其作为 doctest-skip 指令:

    .. doctest-skip::
    
        >>> # This is a doctest that will appear in the documentation,
        >>> # but will not be executed by the testing framework.
        >>> 1 / 0  # Divide by zero, ouch!
    

    也可以使用 doctest-skip-all 评论。注意缺少 :: 在这一行的末尾:

    .. doctest-skip-all
    
    All doctests below here are skipped...
    
  4. __doctest_requires__ 是列出特定doctest的依赖项的方法。它应该是映射通配符模式的字典(格式与 __doctest_skip__ )一个或多个模块的列表 可进口的 为了让测试运行。例如,如果某些测试需要scipy模块工作,则将跳过这些测试,除非 import scipy 是可能的。也可以使用通配符模式的元组作为这个dict::

    __doctest_requires__ = {('func1', 'func2'): ['scipy']}
    

    使用此模块级变量将需要 scipy 为了运行函数的doctest而被导入 func1func2 在那个模块里。

    在Sphinx文档中,doctest需求可以用 doctest-requires 指令:

    .. doctest-requires:: scipy
    
        >>> import scipy
        >>> scipy.hamming(...)
    

跳过输出

编写doctest的一个重要方面是,可以将示例输出与运行测试时生成的实际输出进行准确比较。

默认情况下,doctest系统逐字比较实际输出和示例输出,但这并不总是可行的。例如,示例输出可能包含 __repr__ 显示其id(每次运行时都会更改)的对象的,或预期出现异常的测试可能会输出回溯。

概括示例输出的最简单方法是使用省略号 ... . 例如::

>>> 1 / 0
Traceback (most recent call last):
...
ZeroDivisionError: integer division or modulo by zero

此doctest预期会出现一个带有回溯的异常,但是在示例输出中会跳过回溯的文本——只检查输出的第一行和最后一行。见 doctest 有关跳过输出的更多示例的文档。

忽略所有输出

忽略输出的另一种可能是使用 # doctest: +IGNORE_OUTPUT 旗子。这允许执行doctest(并检查代码是否执行无错误),但在我们不关心输出是什么的情况下,允许忽略整个输出。这与使用省略号的不同之处在于,我们仍然可以提供完整的示例输出,而不需要进行测试来确认它是否正确。例如::

>>> print('Hello world')  
We don't really care what the output is as long as there were no errors...

处理浮动输出

某些doctest可能会产生包含浮点值的字符串表示形式的输出。浮点表示通常不精确,并且在最低有效位数中包含舍入。根据运行测试的平台(不同的Python版本、不同的操作系统等),显示的确切数字可能不同。因为doctest是通过比较字符串来工作的,这可能会导致这些测试失败。

为了解决这个问题 pytest-doctestplus 插件提供对 FLOAT_CMP 可用于doctest的标志。例如:

>>> 1.0 / 3.0  # doctest: +FLOAT_CMP
0.333333333333333311

当使用此标志时,预期输出和实际输出都将被解析,以在字符串中找到任何浮点值。然后将它们转换为实际的Python float 对象并进行数值比较。这意味着舍入位数表示的细微差异将被doctest忽略。否则,这些值将被精确地比较,因此更显著(尽管可能很小)的差异仍然会被这些测试捕捉到。

持续集成

概述

Astropy使用以下持续集成(CI)服务:

  • GitHub Actions 对于64位Linux、OS X和Windows设置(注意:GitHub操作还没有“允许的失败”,因此您可能会看到针对PR报告的失败作业,其名称中带有“(允许的失败)”。尽管如此,有些失败可能是真实的,并且与您的更改有关,所以无论如何都要检查一下!)

  • CircleCI 对于32位Linux、文档构建和可视化测试

它们不断地测试每个提交和拉取请求的包,这些请求被推送到GitHub,以便在出现问题时进行通知。

Astropy和许多附属软件包使用一个名为 ci-helpers 为CI系统的通用部分提供支持。

可以使用相关YAML文件中的适当环境变量为不同的包定制依赖项。有关如何设置此机器的更多详细信息,请参阅 package-templateci-helpers

CircleCI上的32位测试使用 quay.io/pypa/manylinux1_i686 docker映像,每个主要Python版本都包含一个32位Python环境。看到圆圈了吗 configuration file 对于如何访问不同Python版本的核心包。

在某些情况下,您可能会看到在本地看不到的连续集成服务的故障,例如,因为操作系统不同,或者因为故障只发生在32位Python上。下面的部分解释如何在本地重新生成特定的构建。

复制失败的32位生成

如果您想在CircleCI使用的同一个32位Python环境中运行测试,请首先安装 Docker 如果你还没有安装。Docker可以安装在各种不同的操作系统上。

然后,确保您有一个git存储库(主Astropy存储库或fork)的版本,以便为其运行测试。转到该目录,然后使用以下命令运行Docker::

$ docker run -i -v ${PWD}:/astropy_src -t quay.io/pypa/manylinux1_i686 bash

这将把您放入Docker容器中的bashshell中。进去后,你可以去 /astropy_src 目录,您应该可以看到本地git存储库中的文件:

root@5e2b89d7b07c:/# cd /astropy_src
root@5e2b89d7b07c:/astropy_src# ls
astropy                     conftest.py      licenses              setup.py
cextern                     CONTRIBUTING.md  MANIFEST.in       static
CHANGES.rst         docs             pip-requirements  tox.ini
CITATION            examples             pyproject.toml
codecov.yml         GOVERNANCE.md    README.rst
CODE_OF_CONDUCT.md  LICENSE.rst      setup.cfg

然后,您可以使用以下工具运行测试:

root@5e2b89d7b07c:/astropy_src# /opt/python/cp36-cp36m/bin/pip install -e .[test]
root@5e2b89d7b07c:/astropy_src# pytest

Pytest插件

以下 pytest plugins are maintained and used by Astropy. They are included as dependencies to the pytest-astropy package, which is now required for testing Astropy. More information on all of the plugins provided by the pytest-astropy package (including dependencies not maintained by Astropy) can be found here .

pytest远程数据

这个 pytest-remotedata 插件允许开发人员控制是否运行从internet访问数据的测试。该插件提供了两个装饰器,可用于标记单个测试函数或整个测试类:

  • @pytest.mark.remote_data 对于需要来自internet的数据的测试

  • @pytest.mark.internet_off 只有在不应该访问internet时才运行测试。当没有可用的网络访问时,这对于测试本地数据缓存或回退非常有用。

该插件还添加了 --remote-data 选择权 pytest 命令(也可以通过Astropy test runner获得)。

如果 --remote-data 在运行测试套件时未提供选项,或者如果 --remote-data=none 提供的所有测试 remote_data 将被跳过。标记为的所有测试 internet_off 将被执行。尝试访问internet但未标记为的任何测试 remote_data 会导致失败。

提供 --remote-data 选项,或 --remote-data=any ,将导致标记为的所有测试 remote_data 待执行。标记为的任何测试 internet_off 将被跳过。

使用运行测试 --remote-data=astropy 将导致只运行从Astropy数据源接收远程数据的测试。将跳过任何其他数据源的测试。测试中的功能由测试代码表示 @pytest.mark.remote_data(source='astropy') . 用标记的测试 internet_off 在这种情况下也将被跳过。

也看到 使用数据文件 .

pytest doctestplus

这个 pytest-doctestplus 插件提供高级doctest功能,包括:

  • 处理将远程数据与 pytest-remotedata 上面的插件(参见 使用数据文件

  • 生成浮点结果的doctest的近似浮点比较(请参阅 处理浮动输出

  • 运行doctest时跳过特定的类、方法和函数(请参见 跳过医生测试

  • 可选包括 *.rst 医生文件

此插件提供两个命令行选项: --doctest-plus 用于启用上述高级功能,以及 --doctest-rst 包括 *.rst doctest集合中的文件。

Astropy测试运行程序默认启用这两个选项。直接从运行测试套件时 pytest (而不是通过Astropy测试运行程序),有必要在需要时显式地提供这些选项。

pytest打开文件

这个 pytest-openfiles 插件允许在单元测试结束时检测打开的I/O资源。此插件添加 --open-files 选择权 pytest 命令(也可以通过Astropy测试运行程序公开)。

使用运行测试时 --open-files ,如果在单元测试过程中打开了某个文件,但在测试完成之前该文件没有关闭,则测试将失败。这对于测试操纵文件句柄或其他I/O资源的代码特别有用。它允许开发人员确保这类代码在不再需要I/O资源时正确地清理它们。

也看到 测试打开的文件 .