Add unit tests to a collection
This section describes all of the steps needed to add unit tests to a collection and how to run them locally using the ansible-test
command.
See Unit Testing Ansible Modules for more details.
Understanding the purpose of unit tests
Unit tests ensure that a section of code (known as a unit
) meets its design requirements and behaves as intended. Some collections do not have unit tests but it does not mean they are not needed.
A unit
is a function or method of a class used in a module or plugin. Unit tests verify that a function with a certain input returns the expected output.
Unit tests should also verify when a function raises or handles exceptions.
Ansible uses pytest as a testing framework.
See Unit Testing Ansible Modules for complete details.
Inclusion in the Ansible package requires integration and/or unit tests You should have tests for your collection as well as for individual modules and plugins to make your code more reliable To learn how to get started with integration tests, see Adding integration tests to a collection.
See Prepare your environment to prepare your environment.
Determine if unit tests exist
Ansible collection unit tests are located in the tests/units
directory.
The structure of the unit tests matches the structure of the code base, so the tests can reside in the tests/units/plugins/modules/
and tests/units/plugins/module_utils
directories. There can be sub-directories, if modules are organized by module groups.
If you are adding unit tests for my_module
for example, check to see if the tests already exist in the collection source tree with the path tests/units/plugins/modules/test_my_module.py
.
Example of unit tests
Let’s assume that the following function is in 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
Unit tests for this function should, at a minimum, check the following:
If the function gets a
Decimal
argument, it returns a correspondingfloat
value.If the function gets a
timedelta
argument, it returns a correspondingstr
value.If the function gets
42
as an argument, it raises aValueError
.If the function gets an argument of any other type, it does nothing and returns the same value.
To write these unit tests in collection is called community.mycollection
:
If you already have your local environment prepared, go to the collection root directory.
cd ~/ansible_collection/community/mycollection
Create a test file for
my_module
. If the path does not exist, create it.touch tests/units/plugins/modules/test_my_module.py
Add the following code to the file:
# -*- 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://docs.pytest.org/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)See Unit Testing Ansible Modules for examples on how to mock
AnsibleModule
objects, monkeypatch methods (module.fail_json
,module.exit_json
), emulate API responses, and more.
Run the tests using docker:
ansible-test units tests/unit/plugins/modules/test_my_module.py --docker
Recommendations on coverage
Use the following tips to organize your code and test coverage:
Make your functions simple. Small functions that do one thing with no or minimal side effects are easier to test.
Test all possible behaviors of a function including exception related ones such as raising, catching and handling exceptions.
When a function invokes the
module.fail_json
method, passed messages should also be checked.
See also
- Unit Testing Ansible Modules
Unit testing Ansible modules
- Testing Ansible
Ansible Testing Guide
- Adding integration tests to a collection
Integration testing for collections
- Integration tests
Integration tests guide
- Testing collections
Testing collections
- Resource module integration tests
Resource module integration tests
- How to test a collection PR
How to test a pull request locally