向集合添加单元测试

本节描述了向集合添加单元测试以及如何使用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

如何在本地测试拉取请求