将单元测试添加到集合

本节介绍将单元测试添加到集合所需的所有步骤,以及如何使用 ansible-test 命令在本地运行它们。

有关更多详细信息,请参见 单元测试 Ansible 模块

了解单元测试的目的

单元测试确保代码的一部分(称为 单元)满足其设计要求,并按预期执行。一些集合没有单元测试,但这并不意味着它们不需要。

单元 是模块或插件中使用的类函数或方法。单元测试验证具有特定输入的函数是否返回预期的输出。

单元测试还应验证函数何时引发或处理异常。

Ansible 使用 pytest 作为测试框架。

有关完整详细信息,请参见 单元测试 Ansible 模块

包含在 Ansible 软件包中 需要集成测试和/或单元测试。您应该为您的集合以及各个模块和插件进行测试,以使您的代码更可靠。要了解如何开始集成测试,请参见 将集成测试添加到集合

请参见 准备您的环境,以准备您的环境。

确定是否存在单元测试

Ansible 集合单元测试位于 tests/units 目录中。

单元测试的结构与代码库的结构相匹配,因此测试可以位于 tests/units/plugins/modules/tests/units/plugins/module_utils 目录中。如果模块按模块组组织,则可以存在子目录。

例如,如果您要为 my_module 添加单元测试,请检查测试是否已存在于集合源代码树中,路径为 tests/units/plugins/modules/test_my_module.py

单元测试示例

假设以下函数在 my_module

def convert_to_supported(val):
    """Convert unsupported types to appropriate."""
    if isinstance(val, decimal.Decimal):
        return float(val)

    if isinstance(val, datetime.timedelta):
        return str(val)

    if val == 42:
        raise ValueError("This number is just too cool for us ;)")

    return val

此函数的单元测试应至少检查以下内容

  • 如果函数获取 Decimal 参数,则返回相应的 float 值。

  • 如果函数获取 timedelta 参数,则返回相应的 str 值。

  • 如果函数获取 42 作为参数,则引发 ValueError

  • 如果函数获取任何其他类型的参数,则不执行任何操作,并返回相同的值。

要在集合中编写这些单元测试,该集合称为 community.mycollection

  1. 如果您已经有了本地环境 准备好了,请转到集合根目录。

cd ~/ansible_collection/community/mycollection
  1. my_module 创建一个测试文件。如果路径不存在,请创建它。

    touch tests/units/plugins/modules/test_my_module.py
    
  2. 将以下代码添加到文件

# -*- coding: utf-8 -*-

from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

from datetime import timedelta
from decimal import Decimal

import pytest

from ansible_collections.community.mycollection.plugins.modules.my_module import (
    convert_to_supported,
)

# We use the @pytest.mark.parametrize decorator to parametrize the function
# https://pytest.cn/en/latest/how-to/parametrize.html
# Simply put, the first element of each tuple will be passed to
# the test_convert_to_supported function as the test_input argument
# and the second element of each tuple will be passed as
# the expected argument.
# In the function's body, we use the assert statement to check
# if the convert_to_supported function given the test_input,
# returns what we expect.
@pytest.mark.parametrize('test_input, expected', [
    (timedelta(0, 43200), '12:00:00'),
    (Decimal('1.01'), 1.01),
    ('string', 'string'),
    (None, None),
    (1, 1),
])
def test_convert_to_supported(test_input, expected):
    assert convert_to_supported(test_input) == expected

def test_convert_to_supported_exception():
    with pytest.raises(ValueError, match=r"too cool"):
        convert_to_supported(42)

请参见 单元测试 Ansible 模块,以了解如何模拟 AnsibleModule 对象、修补方法(module.fail_jsonmodule.exit_json)、模拟 API 响应等等的示例。

  1. 使用 docker 运行测试

ansible-test units tests/unit/plugins/modules/test_my_module.py --docker

关于覆盖率的建议

使用以下技巧来组织您的代码和测试覆盖率

  • 使您的函数简单。执行一项操作且没有或只有少量副作用的小型函数更容易测试。

  • 测试函数的所有可能行为,包括与异常相关的行为,例如引发异常、捕获异常和处理异常。

  • 当函数调用 module.fail_json 方法时,还应检查传递的消息。

另请参见

单元测试 Ansible 模块

单元测试 Ansible 模块

测试 Ansible

Ansible 测试指南

将集成测试添加到集合

集合的集成测试

集成测试

集成测试指南

测试集合

测试集合

资源模块集成测试

资源模块集成测试

如何测试集合 PR

如何在本地测试拉取请求