在PyTorch上使用pytest工作

627 阅读8分钟

前提条件

要自己运行本帖的代码,请确保你有 torch, ipytest>0.9,以及要引入的插件 pytest-pytorch安装了。

pip install torch 'ipytest>0.9' pytest-pytorch

在我们开始测试之前,我们需要配置 ipytest.我们使用 ipytest.autoconfig()为基础,并添加一些pytest CLI标志,以便获得一个简洁的输出。

在[1]中。

import

如果你在PyTorch上工作并喜欢 pytest你可能已经注意到,在测试套件中,你无法使用默认的 pytest双冒号语法{MODULE}::TestFoo::test_bar

在[2]中。

%%
1 warning in 0.01s

ERROR: not found: /home/user/tmp35zsok9u.py::TestFoo::test_bar
(no name '/home/user/tmp35zsok9u.py::TestFoo::test_bar' in any of [<Module tmp35zsok9u.py>])


如果没有这个非常基本的 pytest的功能曾经让你感到沮丧,你不需要再担心了。通过安装 pytest-pytorch插件

pip install pytest-pytorch

conda install -c conda-forge pytest-pytorch

你可以得到默认的 pytest经验,即使你的工作流程涉及到从你的IDE中运行测试!

在[3]中。

enable_pytest_pytorch

在[4]中。

%%
F                                                                        [100%]
=================================== FAILURES ===================================
___________________________ TestFooCPU.test_bar_cpu ____________________________

self = <__main__.TestFooCPU testMethod=test_bar_cpu>, device = 'cpu'

    def test_bar(self, device):
>       assert False, "Don't worry, this is supposed to happen!"
E       AssertionError: Don't worry, this is supposed to happen!
E       assert False

<ipython-input-2-f22a5e9e7b30>:7: AssertionError
=========================== short test summary info ============================
FAILED tmpt8c1r46_.py::TestFooCPU::test_bar_cpu - AssertionError: Don't worry...
1 failed, 1 warning in 0.17s

正如你所看到的,在 pytest-pytorch启用。 pytest运行了正确的测试,但用不同的名字收集了它。在这篇文章中,我们将找出发生这种情况的原因,以及如何 pytest-pytorch以使您的生活更轻松。

PyTorch测试生成¶

PyTorch有一个广泛的测试套件,有很多配置选项和自动生成的测试。在内部,PyTorch使用了一个TestCase ,该类派生于 unittest.TestCase.在文章的第一部分,我们将探讨PyTorch测试套件中自动生成的测试是如何工作的,以及它们是如何被收集到 pytest.

在其默认定义中,PyTorchTestCase 在测试收集方面的行为与它的基类完全相同。

在[5]中。

disable_pytest_pytorch

在[6]中。

%%
tmpy90fpw2t.py::TestFoo::test_bar
tmpy90fpw2t.py::TestFoo::test_baz

2 tests collected in 0.04s

设备参数化¶

不过,大多数TestCase's都会使用额外的配置。在PyTorch中,大多数操作可以在CPU之外的其他device ,例如GPU上进行。因此,为了不重复自己为多个device's编写相同的测试,可能的设备被用作测试的参数。在PyTorch中,这是由instantiate_device_type_tests 函数完成的。

在[7]中。

%%
tmpyt_bxm9e.py::TestFooCPU::test_bar_cpu
tmpyt_bxm9e.py::TestFooCPU::test_baz_cpu
tmpyt_bxm9e.py::TestFooCUDA::test_bar_cuda
tmpyt_bxm9e.py::TestFooCUDA::test_baz_cuda

4 tests collected in 0.01s

顾名思义,instantiate_device_type_tests 使用通过的测试案例作为模板,为不同的device's实例化新的测试案例。在这个过程中,测试用例的名称以及它的测试函数都被改变。

  • 测试用例的名称是由大写字母后缀的device 名称组成的(TestFooTestFooCPU)
  • 每个测试函数的命名方式是用小写字母后缀device ,另外用下划线作为分隔符 (test_bartest_bar_cpu)

在安装之后,当前测试的device ,提供给每个测试函数。

我们在这里掩盖了许多细节,其中有两个我至少应该提到。

  1. 虽然看起来只有测试函数需要被参数化,但参数化的测试案例在每个device ,执行不同的设置和拆除。
  2. 有许多装饰器可用,允许在每个功能的基础上调整device 参数化。

数据类型参数化¶

本着与device 参数化相同的精神,大多数PyTorch操作符支持大量的数据类型(简称dtype's)。我们可以用@dtypes 装饰器对测试函数进行参数化,之后dtype 作为参数使用。请注意,我们只有在启用模板时才能使用@dtypes 装饰器,这意味着我们必须使用instantiate_device_type_tests

在 [8] 中。

%%
tmpttnibud4.py::TestFooCPU::test_bar_cpu_float32
tmpttnibud4.py::TestFooCPU::test_bar_cpu_int32

2 tests collected in 0.01s

device 与设备参数化类似,dtype 的名称被后缀到测试函数的名称之后(test_bartest_bar_cpu_float32)。由于不需要在每个dtype ,测试用例不为不同的dtype's instatiatied (TestFooTestFooCPU),所以不需要特别的设置或拆除。

同样,还有更多的装饰器可用于细化控制,但它们超出了本帖的范围。

操作符参数化

PyTorch测试套件中最近增加了一个OpInfo 类。它携带了一个操作符的元数据,如每device 支持的dtype's或另一个库的可选参考函数。仔细研究所有的选项会让我们自己写一篇博文,所以我们将在这里坚持基本的内容。

OpInfo在这里,我们将只讨论最基本的问题。例如,针对参考实现检查运算符的测试结构是与运算符无关的。为了参数化一个测试函数,我们使用@ops 装饰器。我们在这里定义我们自己的op_db ,但在PyTorch测试套件中,有预定义的数据库,用于不同的运算符类型,如单数或双数运算符。同样,请注意,我们只有在启用模板时才能使用@ops 装饰器,这意味着我们必须使用instantiate_device_type_tests

在[9]中。

%%
tmpe119_vdl.py::TestFooCPU::test_bar_add_cpu_int32
tmpe119_vdl.py::TestFooCPU::test_bar_sub_cpu_float32

2 tests collected in 0.04s

dtype 相比,在测试函数的名称中,op 的名称被放在device 的标识符之前(test_bartest_bar_add_cpu_int32)。尽管如此,在每个op ,不需要特殊的设置或拆分,所以测试用例只对device (TestFooTestFooCPU)进行实例化。

pytest"等价物"¶。

从一个 pytest角度来看,PyTorch的测试生成 "等同于 "使用@pytest.mark.parametrize 装饰器。当然,这忽略了所有血淋淋的细节,这使得它看起来比实际更容易。不过,这对那些来自不同背景的人来说,可能是一个很好的心理比喻。 pytest背景的人来说,这可能是一个很好的心理类比。

在[10]中。

%%
tmp8_w7dn68.py::TestFoo::test_bar[add-float32-cpu]
tmp8_w7dn68.py::TestFoo::test_bar[sub-float32-cpu]

2 tests collected in 0.00s

到目前为止,我们看了PyTorch测试套件中的测试生成,以及测试是如何被收集到 pytest.虽然PyTorch使用的参数化方案不同于 pytest但它是100%兼容的。只有当你想选择一个特定的测试案例或测试功能,而不是说整个模块或完整的测试套件时,问题才会出现。

PyTorch的测试选择有 pytest

正如我们上面所观察到的。 pytest的默认双冒号:: ,在PyTorch测试套件实例化的测试中不起作用。

在[11]中。

%%
no tests collected in 0.01s

ERROR: not found: /home/user/tmpg1b9f42o.py::TestFoo::test_bar
(no name '/home/user/tmpg1b9f42o.py::TestFoo::test_bar' in any of [<Module tmpg1b9f42o.py>])


有了我们收集的关于实例化的知识,就很容易看出为什么会发生这种情况。

在[12]中。

%%
tmpo9kwbe9q.py::TestFooCPU::test_bar_cpu
tmpo9kwbe9q.py::TestFooCPU::test_baz_cpu

2 tests collected in 0.01s

pytest用测试函数test_bar 搜索测试用例TestFoo ,但找不到它们,因为instantiate_device_type_tests 把它们重命名为TestFooCPUtest_bar_cpu 。然而,该测试可以通过其新的、实例化的名称来选择{MODULE}::TestFooCPU::test_bar_cpu

在[13]中。

%%
tmp_i0_ha5r.py::TestFooCPU::test_bar_cpu

1 test collected in 0.04s

从方便的角度来看,这并不是最佳选择,因为我们需要记住命名方案。此外,我们只能选择一个特定的参数化,而不是针对所有可用的参数化运行一个测试用例或一个测试函数。通常的方法是依靠pytest -k 标志来做模式匹配。

在[14]中。

%%
tmp633i_bea.py::TestFooCPU::test_bar_cpu

1/2 tests collected (1 deselected) in 0.01s

与默认的 pytest的做法,我们需要在模式中包括测试用例和测试函数的名称,而不是只包括我们想要选择的参数化。这带来了它自己的一系列问题,例如,如果测试用例或测试函数使用的名称是建立在彼此之上的。由于选择模式不支持正则表达式匹配,它可能变得冗长和混乱。

在[15]中。

%%
tmpzoevi0fu.py::TestFooCPU::test_spam_cpu

1/3 tests collected (2 deselected) in 0.01s

pytest-pytorch

介绍一下。 pytest-pytorch.在其安装后,无需任何配置,我们就可以得到默认的 pytest经验回来。因此,即使在复杂的命名情况下,我们也可以简单地选择一个带有双冒号符号的测试{MODULE}::TestFoo::test_spam

在[16]中。

enable_pytest_pytorch

在[17]中。

%%
tmpalkh4ddp.py::TestFooCPU::test_spam_cpu

1 test collected in 0.05s

当然,我们仍然可以使用pytest -k 标志来选择一个特定的参数化。

在[18]中。

%%
tmp6av_zpy3.py::TestFooCUDA::test_bar_cuda

1/2 tests collected (1 deselected) in 0.02s

对细节的处理 pytest-pytorch实现了这一目标,它挂接了 pytest的测试集合,并为你执行实例化与模板名称的匹配。如果这听起来很有趣,请看一下GitHub的仓库

支持的IDE pytest支持

的另一个用例是 pytest-pytorch的另一个用例是现代的Python集成开发环境,如PyCharmVSCode的内置 pytest支持。在这样的IDE中,你可以通过点击其定义旁边的一个按钮来运行一个测试,而不用进入终端。这样做,你还可以得到IDE的舒适性,包括调试器。

这个功能依赖于测试选择的默认 pytest语法,正如我们在上面看到的,这种语法在PyTorch测试套件中并不奏效。你可以摆弄你的IDE的默认测试运行器配置,或者你可以简单地安装 pytest-pytorch以使其开箱即用。

结论¶

在这篇文章中,我们看了PyTorch测试套件如何自动生成device-、dtype-,甚至是与操作员无关的测试。虽然它使用的方案与 pytest,但测试仍然可以被它收集和运行。如果试图使用默认的 pytest选择符号。为了克服这个问题,反过来提高开发人员的体验,我们引入了 pytest-pytorch插件。例如,它可以用来重新获得现代IDE中的开箱即用的 pytest在现代IDE中恢复对PyTorch的支持。