The pytest plugin takeltest provides helper functions and fixtures to facilitate the use of molecule and testinfra. It provides access to variables and secrets and helps to not only unit test your ansible roles but to integration and system test your whole ansible project.
|App||Deploy project||Test project||Test roles|
pip install takeltest
Run pytest unit tests and molecule system tests by invoking rake:
As a boilerplate for testinfra tests it is enough to do:
import takeltest testinfra_hosts = takeltest.hosts()
You can access gopass secrets by using the testpass fixture:
def test_mytest(testpass): my_password = testpass('my_project/my_password')
Fixtures multitestvars and testvars
Arguably the most useful feature of the takeltest plugin are the multitestvars and testvars fixtures. The fixtures resolve and expose ansible variables as a python dict:
def test_mytest(multitestvars): my_python_variable = multitestvars['my_host']['my_ansible_variable']
testvars is a list containing the ansible variables of the first molecule host. Use this if you only have one host:
def test_mytest(testvars): my_python_variable = testvars['my_ansible_variable']
multitestvars runs a playbook against the molecule hosts using the ansible python api.
multitestvars 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.
multitestvars uses the ansible VariableManager so the usual ansible variable precedence rules apply. Internally, the fixture uses the ansible debug module to resolve templates which have not been resolved by the setup module through the gather facts task. Thus, it can resolve any kind of template which the debug module can resolve including jinja2 code and calls to lookup plugins.
TESTVARS_EXTRA_VARS environment variable can be set in
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 (as opposed to adding
Which roles are included is determined in the following order, the first match wins:
- List of roles separated by colon specified in the
- List of roles specified in playbooks separated by colon specified in
- List of roles specified in playbook specified in
- List of roles specified in default playbook
- List of roles specified in old default playbook
- All roles in
rolesdirectory in project directory
Roles included in
TESTVARS_ROLES_INCLUDE will be included.
Roles blocked in
TESTVARS_ROLES_BLOCK won't be included.
You may want to include roles which are imported by task and not by playbook.
You can find the source code in the function
example: testing packer images
By specifying playbooks via
you are able to test your packer images with molecule.
Let's assume your local docker image is called
Then you can start this image within a molecule scenario:
platforms: - name: molecule-my-image image: packer_local/my_image
Make the group_vars (and host_vars, if you wish so) available to takeltest:
provisioner: name: ansible inventory: links: group_vars: ../../group_vars/
Now you don't want to run your playbook on that packer image again.
But you still want to have access to all the role defaults variables
of those roles defined in your playbook so that your tests will pass.
Either you do not run
molecule converge and
on your image or you set the
TESTVARS_ROLES_PLAYBOOKS environment variable:
verifier: name: testinfra env: TESTVARS_ROLES_PLAYBOOKS: ../../site.yml:../../my_layer.yml
Code examples are the
of the anarchism project.
molecule converge and
molecule idempotence has the
advantage that your pytests are automagically included.
Both scenarios achieve the same thing but they use different methods. The bitboard server happens to be built with the same role takel-anarchism whose unit tests are applied in this scenarios to the bitboard server docker image.
multitestvars and testvars are session scope fixtures
so they are configured 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, multitestvars 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-factsRun playbook to gather variables with
gather_facts: false. You won't be able to access
ansible_factsbut your tests will run faster.
testvars-no-gather-moleculeDo 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-varsDo not add extra variables specified in
TESTVARS_EXTRA_VARS. Ignores the environment variable.
Hopefully the multitestvars fixture allows fast test-driven development.
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
verifier: name: testinfra options: p: cacheprovider
You should use the takeltest 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::
Ansible Python API
The takeltest plugin provides four main pytest fixtures (and a couple of command line, environment variables and helper fixtures):
- testpass – exposes the ansible passwordstore plugin
- multitestvars – resolves and exposes ansible vars and facts of all molecule hosts
- testvars – resolves and exposes ansible vars and facts of one molecule host
- moleculebook – api to run playbooks against a molecule host
- moleculeplay – api to leverage the ansible python api
The multitestvars, 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_takeltest_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
for a real-world example where moleculebook is used
to avoid a molecule
which otherwise needs to be copied
to the project's molecule default scenario.