为集合添加单元测试

本节介绍为集合添加单元测试以及如何使用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)

有关如何模拟AnsibleModule对象、monkeypatch 方法(module.fail_jsonmodule.exit_json)、模拟 API 响应等的示例,请参见Ansible 模块的单元测试

  1. 使用 docker 运行测试

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

关于覆盖率的建议

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

  • 使您的函数简单。执行一项操作且无或最小副作用的小型函数更容易测试。

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

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

另请参见

Ansible 模块的单元测试

Ansible 模块的单元测试

测试 Ansible

Ansible 测试指南

为集合添加集成测试

集合的集成测试

集成测试

集成测试指南

测试集合

测试集合

资源模块集成测试

资源模块集成测试

如何测试集合 PR

如何在本地测试拉取请求