This project became part of the takelage devops workflow and has migrated to takelage-var. It has been renamed from testaid_ to takeltest to reflect this change.
Please use the issue tracker of takelage-var to report bugs and feature requests!
With the pytest plugin testinfra you can write unit tests in python to test your servers configured by the management tool ansible. testinfra is the default verifier of the molecule testing environment.
The pytest plugin testaid_ provides helper functions and fixtures to facilitate the use of testinfra. It helps to not only unit test your ansible roles but to integration and system test your whole ansible project.
testinfra wraps cli calls to the ansible executable. testaid uses the ansible python api to run ansible playbooks.
Table of contents
Install the testaid plugin using pip:
$ pip install testaid
Run unit tests (pytest
) and system tests (molecule test
) by invoking tox:
$ tox
Have a look at debian system test directory for an example of a molecule project using ansible, testinfra and testaid. The molecule project doubles as as a system test (golden master) for the testaid plugin.
As a boilerplate for testinfra tests it is enough to do:
import testaid
testinfra_hosts = testaid.hosts()
You can access gopass secrets by using the testpass fixture:
def test_mytest(host, testpass):
my_password = testpass('my_project/my_password')
Arguably the most useful feature of the testaid plugin is the testvars fixture. The fixture resolves and exposes ansible variables as a python dict:
def test_mytest(host, testvars):
my_variable = testvars['my_variable']
testvars runs a playbook against the molecule host using the ansible python api.
testvars creates a symbolic link to the roles directory of your ansible project
in the ephemeral playbook environment which molecule sets up.
It then runs a playbook with gather_facts:true
and a debug task to get
the ansible variables and the ansible facts of the play and host.
testvars uses the ansible VariableManager so the usual ansible variable precedence rules apply. Internally, the fixture uses the ansible debug module to resolve templates. Thus, it can resolve any kind of template that the debug module can resolve including jinja2 code and calls to lookup plugins.
The TESTVARS_EXTRA_VARS
environment variable can be set in molecule.yml.
It can contain dirpaths or filepaths relative to the
MOLECULE_SCENARIO_DIRECTORY
separated by colons:
verifier:
name: testinfra
env:
TESTVARS_EXTRA_VARS: "../../vars:../../extra_vars/extra_vars.yml"
The vars files will be included in moleculebook playbooks by adding
the paths to vars_files
(and not by adding include_vars
tasks).
Which roles are included is determined in this order:
- List of roles separated by colon specified in the
TESTVARS_ROLES_WHITELIST
environment variable - List of roles specified in playbook speciied in
molecule.yml
- List of roles specified in default playbook
playbook.yml
- All roles in
roles
directory in project directory
Roles blacklisted in TESTVARS_ROLES_BLACKLIST
won't be included.
testvars is a session scope fixture so its configuration is done in molecule.yml by using pytest command line options. You can add a couple of options in the options dictionary of the verifier section:
verifier:
name: testinfra
options:
testvars-no-gather-facts: true
By default, testvars runs a playbook to gather ansible variables and facts. It then runs a playbook to resolve the variables.
You can change the default behaviour with these options:
-
testvars-no-gather-facts
- Run playbook to gather variables with
gather_facts: false
. You won't be able to accessansible_facts
but your tests will run faster.
-
testvars-no-gather-molecule
- Do not resolve molecule variables. You probably won't need these variables but it won't take much time to resolve them, either.
-
testvars-no-extra-vars
- Do not add extra variables specified in
TESTVARS_EXTRA_VARS
. Ignores the environment variable.
Hopefully the testvars fixture allows fast test-driven development. It has session scope so variables are collected and resolved only once per testrun as pytest caches the result. If this is still too slow for you then you can enable the pytest cache plugin in molecule.yml:
verifier:
name: testinfra
options:
p: cacheprovider
You should use the testaid boilerplate code to be able to run pytest directly. Otherwise testinfra will complain about missing environment variables.
Remember to clear the cache when you add or change an ansible variable:
pytest --cache-clear; molecule verify
The cache will use the molecule ephemeral directory as the cache key which is unique for each molecule instance. When using the boilerplate you can inspect the cache by running:
pytest --cache-show
The testaid plugin provides four main pytest fixtures (and a couple of command line, environment variables and helper fixtures):
- testpass - exposes the ansible passwordstore plugin
- testvars - resolves and exposes ansible vars and facts
- moleculebook - api to run playbooks against a molecule host
- moleculeplay - api to leverage the ansible python api
The testvars and testpass fixtures use the moleculebook fixture which in turn uses the moleculeplay fixture. moleculeplay makes low-level calls to the ansible python api and uses the moleculeenv fixture to handle the sysadmin tasks of setting the right symlinks. moleculeplay and moleculeenv will probably not be very useful on their own but moleculebook might be handy in those situations where you know you shouldn't implement a hackaround. ;-)
Here is how you could run an ansible playbook programmatically from a test (or even better: from a fixture) using dependency injection.
def test_testaid_moleculebook(host, moleculebook):
playbook = moleculebook.get()
args = dict(path='/tmp/moleculebook_did_this', state='touch')
task_touch = dict(action=dict(module='file', args=args))
playbook['tasks'].append(task_touch)
moleculebook.set(playbook)
moleculebook.run()
assert host.file('/tmp/moleculebook_did_this').exists