Ansible playbook testing with Molecule using TestInfra and Docker

  • Posted by: James Alford
  • Category: Ansible, DevOps, Python, TestInfra, Testing

I have created a GitHub repository that contains a working example on our organisations account. If you wish to see all of the code when following through this post then go to GitHub and checkout the repository
If you would like to understand more about TestInfra and how run tests before you start reading through this post then read our post

Installing dependencies for Molecule

Molecule is a python package so we can install it using pip. In the example below we create a python virtual environment called molecule-venv, we source the activate file to enable the virtual environment and then we install molecule and testinfra packages using pip. If you do not want to use a virtual environment then just run the pip commands however we recommend using a virtual environment because it keeps package management easy and clean.

$ python3 -m venv molecule-venv
$ source molecule-venv/bin/activate
(molecule-venv) $ pip install testinfra
(molecule-venv) $ pip install molecule
(molecule-venv) $ pip install docker

Now we have installed the packages we can test that they are installed using pip list and molecule commands

Jamess-MBP:ansible-role-molecule-example jamesalford$ pip list|egrep "molecule|testinfra|docker"
docker             4.2.0     
molecule           3.0.2     
testinfra          3.4.0     

(molecule-venv) Jamess-MBP:molecule jamesalford$ molecule --version
molecule 3.0.2
   ansible==2.9.5 python==3.7

How to initialize a new ansible role using molecule

To initialize a new ansible role with molecule, we use the molecule init role command and can pass in variables for the driver, verifier and the role name. In this scenario we use docker to build a container where we can run the ansible role and then run our tests.

(molecule-venv) Jamess-MBP:molecule jamesalford$ molecule init role --driver-name docker --verifier-name testinfra jamesalford.ansible-role-molecule-example
--> Initializing new role jamesalford.ansible-role-molecule-example...
Initialized role in /private/tmp/molecule/jamesalford.ansible-role-molecule-example successfully.

The command builds an ansible role, the main difference to a normal ansible role is the molecule folder that contains another folder called default. Molecule uses ansible-galaxy much like you would when generating a role to make the normal ansible role files/folders and then injects the molecule folder and its contents. The folder under molecule called default is the name of the test scenario and the default one is already created for us.

(molecule-venv) Jamess-MBP:molecule jamesalford$ ls jamesalford.ansible-role-molecule-example/molecule/default/
INSTALL.rst     converge.yml    molecule.yml    tests

Running a basic test

Once molecule is configured to run you can simply run molecule test from the ansible role directory to execute a test. When you run the test, molecule will output the test matrix which shows the test scenarios and the tests that will be run under each scenario.

(molecule-venv) Jamess-MBP:ansible-role-molecule-example jamesalford$ molecule test
--> Test matrix
└── default
    ├── dependency
    ├── lint
    ├── cleanup
    ├── destroy
    ├── syntax
    ├── create
    ├── prepare
    ├── converge
    ├── idempotence
    ├── side_effect
    ├── verify
    ├── cleanup
    └── destroy
--> Scenario: 'default'
--> Action: 'dependency'
Skipping, missing the requirements file.
--> Scenario: 'default'
--> Action: 'lint'

Adjusting the molecule test scenario to have lint enabled

When building a new role as we have done in the steps above, linting is not enabled so we need to enable it.
In earlier versions of molecule there were multiple places that you could specify linting which included a lint option on the provisioner and the verifier however it has since been adjusted to take one command under the main lint section in the yaml file molecule.yml. If we include the code below then we will perform yamllint, ansible-lint and python linting on the files in the role.

lint: |
  set -e
  yamllint .

Adjusting the scenario that is run by molecule

Inside the molecule.yml file, the scenario can be defined. Once defined, you can adjust the test sequence order or remove steps by removing or commenting out the line. You may wish to do this if there is a role that you know will fail a certain type of check such as lint or idempotence and wish to not test that.

  name: default
    - lint
    - destroy
    - dependency
    - syntax
    - create
    - prepare
    - converge
    - idempotence
    - side_effect
    - verify
    - destroy

Change the tests that TestInfra runs when molecule runs

Inside the molecule/default folder there is a folder called tests, this is where the test cases for TestInfra are stored. The default test file is called but any files in this folder that start with test_ and end with python extention .py will get executed as shown in below snippet.

--> Executing Testinfra tests found in /private/tmp/molecule/jamesalford.ansible-role-molecule-example/molecule/default/tests/...
    ============================= test session starts ==============================
    platform darwin -- Python 3.7.3, pytest-5.3.5, py-1.8.1, pluggy-0.13.1
    rootdir: /private/tmp/molecule/jamesalford.ansible-role-molecule-example/molecule/default
    plugins: testinfra-4.1.0
collected 4 items                                                              
    tests/ ..                                                 [ 50%]
    tests/ ..                                                       [100%]
    ============================== 4 passed in 3.71s ===============================
Verifier completed successfully.

Tests are defined inside each file and TestInfra will look through these test files and will try to find the tests to run. It is expected that there is a function per test case however you can run the same test case with multiple values using parameterize. So if you wanted to test a batch of ports are open, you would write a test that takes a variable of port number and then pass in an array of ports to test. TestInfra would then run one test per parameter.

If you wish to see how using arrays and tuples can help you run the same test with multiple values then please checkout my other post under the ‘Using variables in tests’ section

If you didn’t check it out already and you would like to see a working example of molecule then please clone our example repo from GitHub and have a look.

Once checked out, run molecule test inside the role directory and watch molecule execute the tests

Author: James Alford