将测试嵌入到与实现相同的模块中

Embedding tests in the same module with the implementation

我有一些独立且太小(~20 行)的实用程序模块,无法保证有自己的 tests 文件夹。是否有任何约定将 pytest 测试与代码嵌入到同一模块中,以保持紧凑?测试不需要使用任何高级 pytest 功能(夹具、范围等)

例如

"""My beautiful sum helper"""

def sum_helper(a, b):
    return a+b


def test_sum_helper():
   assert sum_helper(1, 2) == 3

我了解此约定可能需要 pytest 命令的特殊启动参数。

这是一种不好的做法,因为您的测试方法是生产代码的一部分。这是应该避免的事情。

恕我直言,正如@Guy 提到的那样,您可以拥有一个 tests 文件夹,而不是在测试文件夹中创建子文件夹或模块,您可以拥有一个名为 test_utils 的通用测试模块,您可以在其中添加测试所有实用程序模块。

是的,您正确地提到要将这些视为测试,您需要变通,这本身就是一种反模式。 如果您需要更多信息,请告诉我。

您只需要告诉 pytest 在您的包文件夹中发现测试并查看所有 *.py 文件而不只是 test_*.py 文件。这是 python_files option.

您可能还需要将 testpaths 提供给 pytest,也可能不需要。如果你使用相对 .. 导入 pytest 可能会遇到困难,除非你给出明确的 --rootdir.

运行 pytests

的示例命令
pytest -o "python_files='*.py'" -o "testpaths=src tests" 

在实现路径中查找测试似乎没有明显的惩罚 - pytest 倾向于非常快速地扫描文件。如果你觉得这很难看,你可以跳过 import pytest 检查。

这是一个带有附加测试的示例实现,零 运行 时间惩罚。测试在模块的末尾,只有在安装 pytest 时才启用。

"""CAIP handling.

https://github.com/ChainAgnostic/CAIPs
"""
from dataclasses import dataclass

from eth_utils import is_checksum_address


class BadChainAddressTuple(Exception):
    pass


class InvalidChainId(BadChainAddressTuple):
    pass


class InvalidChecksum(BadChainAddressTuple):
    pass


@dataclass
class ChainAddressTuple:
    """Present one chain-agnostic address"""
    chain_id: int
    address: str

    @staticmethod
    def parse_naive(v: str):
        """
        Example tuple: `1:0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc` - ETH-USDC on Uniswap v2
        """
        assert type(v) == str

        if not v:
            raise BadChainAddressTuple("Empty string passed")

        parts = v.split(":")

        if len(parts) != 2:
            raise BadChainAddressTuple(f"Cannot split chain id in address {v}")

        address = parts[1]
        if not is_checksum_address(address):
            raise InvalidChecksum("Address checksum or format invalid")

        try:
            chain_id = int(parts[0])
        except ValueError:
            raise InvalidChainId(f"Invalid chain_id on {v}")

        if chain_id <= 0:
            raise InvalidChainId("Invalid chain_id")

        return ChainAddressTuple(chain_id, address)


# Run tests with pytest -o "python_files='*.py'"
try:
    # We assume pytest is not installed in the production environment,
    # thus including the test code as part of the module does not incur any extra penalty
    import pytest

    def test_caip_parse_naive():
        tuple = ChainAddressTuple.parse_naive("1:0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc")
        assert tuple.chain_id == 1
        assert tuple.address == "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc"


    def test_caip_bad_checksum():
        # Notive lower b, as Ethereum encodes the checksum in the hex capitalisation
        with pytest.raises(InvalidChecksum):
            ChainAddressTuple.parse_naive("1:0xb4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc")

except ImportError:
    # pytest not installed, tests not available
    pass