集成测试#

conda 中的集成测试从高层测试应用程序,其中每个测试可能覆盖代码的大部分。这些测试也可能使用本地文件系统和/或执行网络调用。在以下章节中,我们将介绍一些关于这些测试样子的示例。当编写您自己的集成测试时,这些应该作为一个良好的起点。

conda_cli Fixture:运行 CLI 级别测试#

注意

conda_cli fixture 是一个 scope="function" fixture,意味着它只能在测试或其他 scope="function" fixtures 中使用。对于 "module""package""session" 作用域的 fixtures,请使用 session_conda_cli 代替。

CLI 级别测试是您可以编写的最高级别的集成测试。这意味着测试中的代码执行方式就像您从命令行运行它一样。例如,您可能想要编写一个测试来确认在成功运行 conda create 后创建了一个环境。 这样的测试看起来像下面这样

conda create 的集成测试#
 1from __future__ import annotations
 2
 3import json
 4from typing import TYPE_CHECKING
 5
 6if TYPE_CHECKING:
 7    from pathlib import Path
 8
 9    from conda.testing.fixtures import CondaCLIFixture
10
11pytest_plugins = "conda.testing.fixtures"
12
13
14def test_conda_create(conda_cli: CondaCLIFixture, tmp_path: Path):
15    # setup, create environment
16    out, err, code = conda_cli("create", "--prefix", tmp_path, "--yes")
17
18    assert f"conda activate {tmp_path}" in out
19    assert not err  # no errors
20    assert not code  # success!
21
22    # verify everything worked using the `conda env list` command
23    out, err, code = conda_cli("env", "list", "--json")
24
25    assert any(
26        tmp_path.samefile(path)
27        for path in json.loads(out).get("envs", [])
28    )
29    assert not err  # no errors
30    assert not code  # success!
31
32    # cleanup, remove environment
33    out, err, code = conda_cli("remove", "--all", "--prefix", tmp_path)
34
35    assert out
36    assert not err  # no errors
37    assert not code  # success!

让我们分解一下上面的代码片段中到底发生了什么

首先,我们依赖于一个 fixture (conda_cli),它允许我们使用当前运行的进程来运行命令。这比通过子进程运行 CLI 测试更有效率和更快。

在测试本身中,我们首先通过有效地运行 conda create 来创建一个新环境。此函数返回命令的标准输出、标准错误和退出代码。这允许我们执行检查,以确定命令是否成功运行。

测试的第二部分再次使用 conda_cli fixture 来调用 conda env list。这次,我们传递 --json 标志,这允许捕获 JSON,我们可以更好地解析和更轻松地检查。然后,我们断言我们刚刚创建的环境是否真正在可用环境列表中。

最后,我们销毁刚刚创建的环境,并确保标准错误和退出代码是我们期望的那样。

警告

首选使用临时目录(例如,tmp_path),以便在测试运行后自动清理。否则,请记住删除测试期间创建的任何内容,因为它将在其他测试运行时存在,并可能导致意外的竞争条件。

tmp_env Fixture:创建临时环境#

注意

tmp_env fixture 是一个 scope="function" fixture,意味着它只能在测试或其他 scope="function" fixtures 中使用。对于 "module""package""session" 作用域的 fixtures,请使用 session_tmp_env 代替。

tmp_env fixture 是一种为测试创建临时环境的便捷方法

numpy 创建环境的集成测试#
 1from __future__ import annotations
 2
 3from typing import TYPE_CHECKING
 4
 5if TYPE_CHECKING:
 6    from conda.testing.fixtures import CondaCLIFixture, TmpEnvFixture
 7
 8pytest_plugins = "conda.testing.fixtures"
 9
10
11def test_environment_with_numpy(
12    tmp_env: TmpEnvFixture,
13    conda_cli: CondaCLIFixture,
14):
15    with tmp_env("numpy") as prefix:
16        out, err, code = conda_cli("list", "--prefix", prefix)
17
18        assert out
19        assert not err  # no error
20        assert not code  # success!

path_factory Fixture:创建唯一的(不存在的)路径#

path_factory fixture 扩展了 pytest 的 tmp_path fixture,以提供唯一的、未使用的路径。 这使得在测试中生成新路径更容易

#
 1from __future__ import annotations
 2
 3from typing import TYPE_CHECKING
 4
 5if TYPE_CHECKING:
 6    from pathlib import Path
 7
 8    from conda.testing.fixtures import (
 9        CondaCLIFixture,
10        PathFactoryFixture,
11        TmpEnvFixture,
12    )
13
14pytest_plugins = "conda.testing.fixtures"
15
16
17def test_conda_rename(
18    path_factory: PathFactoryFixture,
19    tmp_env: TmpEnvFixture,
20    conda_cli: CondaCLIFixture,
21    tmp_path: Path,
22):
23    # each call to `path_factory` returns a unique path
24    assert path_factory() != path_factory()
25
26    # each call to `path_factory` returns a path that is a child of `tmp_path`
27    assert path_factory().parent == path_factory().parent == tmp_path
28
29    with tmp_env() as prefix:
30        out, err, code = conda_cli("rename", "--prefix", prefix, path_factory())
31
32        assert out
33        assert not err  # no error
34        assert not code  # success!

使用 fixtures 的测试#

有时在集成测试中,您可能想要多次重复使用相同类型的环境。 将此设置和拆卸代码复制并粘贴到每个单独的测试中会使这些测试更难以阅读和维护。

为了克服这个问题,conda 测试广泛使用 pytest fixtures。下面是先前显示的测试示例,除了我们现在将测试的重点放在 conda env list 命令上,并将环境的创建和删除移到一个 fixture 中

conda create 的集成测试#
 1from __future__ import annotations
 2
 3import json
 4from typing import TYPE_CHECKING
 5
 6if TYPE_CHECKING:
 7    from pathlib import Path
 8
 9    from conda.testing.fixtures import CondaCLIFixture, TmpEnvFixture
10
11pytest_plugins = "conda.testing.fixtures"
12
13
14@pytest.fixture
15def env_one(tmp_env: TmpEnvFixture) -> Path:
16    with tmp_env() as prefix:
17        yield prefix
18
19
20def test_conda_create(env_one: Path, conda_cli: CondaCLIFixture):
21    # verify everything worked using the `conda env list` command
22    out, err, code = conda_cli("env", "list", "--json")
23
24    assert any(
25        env_one.samefile(path)
26        for path in json.loads(out).get("envs", [])
27    )
28    assert not err  # no errors
29    assert not code  # success!

在名为 env_one 的 fixture 中,我们使用 tmp_env fixture 创建一个新环境。我们 yield 以标记设置的结束。由于 tmp_env fixture 扩展了 tmp_path,因此不需要额外的拆卸。

此 fixture 将使用 pytest 中的默认作用域运行,即 function。这意味着设置和拆卸将在请求此 fixture 的每个测试之前和之后发生。如果您需要在测试之间共享环境或其他数据,只需记住适当地设置 fixture 作用域。在此处阅读有关 pytest fixture 作用域的更多信息。