Unified Collection Testing Strategy - Kick off & Landscape Overview

Yep that’s correct, the files you’ve listed are pretty much the general gist. We run Invoke-PSScriptAnalyzer with a custom ruleset and use pwsh on the ansible-test “controller” host. Our docker images come with all this pre-configured but you can definitely still run it locally if you have the requirements all set up.

I think at a minimum being able to provide your own inventory for test targets is a must have. But the core-ci provided Windows hosts are pretty critical for all the ansible-collections based Windows collections. Without that we will be stuck with either offline CI and manual testing which is not ideal. While I don’t think the tool should hook into the core-ci API and provision these hosts I do think there is a conversation to be had to either expose a specific action in ansible-test or some other tool that uses the core-ci API to provision these hosts for the collections in ansible-collections. The core-ci API is also used to provision the RHEL, FreeBSD, and macOS targets so this functionality would certainly be useful there.

Yep and historically powershell.exe on Windows has been the only workable PowerShell target using Ansible. Ansible 2.21 has added the ability to target pwsh.exe on both Windows and Linux which will help cover a few of those gaps but testing will be very limited for a lot of the collections if you can’t run on Windows as a test target. While I’m certainly a fan of PowerShell based modules I’m sure most people will probably only really care about them when it comes to Windows and Window specific scenarios.

1 Like

No, I was referring to the feature in ansible-test that allows it to request ephemeral cloud resources (virtual machines, cloud credentials) from various cloud providers, including AWS and Azure, without the need for any credentials. This feature is supported for CI jobs on both Azure Pipelines and GitHub Actions. When not run in a CI pipeline on one of those systems, credentials are required.

1 Like

Keep in mind that this only covers what are referred to as unit tests in ansible-test, which are typically a tiny fraction of the tests used for collections, if they exist at all.

The majority of tests tend to be integration tests, which under ansible-test also support code coverage. That means tests written using roles, playbooks and ad-hoc commands are able to collect code coverage for both controller-side plugins and modules, even when run on a different host from the controller. In addition to supporting modules written in Python, coverage is also supported for PowerShell modules. I’m not aware of any tools other than ansible-test that support code coverage in any of these scenarios.

2 Likes

As mentioned in the chat recently, in community.openwrt we are using molecule tests for:

  • generic tests: our extensions/molecule/default scenario does not aim to thoroughly test any particular component, it just cherry picks from a bunch of different ones - it gives us a quick response on whether the collection is working (we have a custom machinery to handle the fact that our modules are in shell script instead of Python)
  • integration tests: per module/plugin, using the same directory structure prescribed by ansible-test integration - we can use it to test, but CI relies on molecule
  • roles tests: despite being placed within a collection, each role (only one right now) may have its own molecule directory, and our CI iterates over those cases and run them separately

Issues

The requirement for the collections to test against devel or milestone comes with a couple of glitches:

  • As consequence of the ansible-core release process: when a new release branch is created off devel, that version is now in a “gap” state. It was happening until a couple of days ago: 2.21 was branched off, but it had not been released yet. The CI workflow did not have that rc1 version in it, nor the b1, b2, b3 before it. In the meanwhile, devel was already numbered as 2.22.
  • ansible-core devel is supporting Python 3.15, which has not been released yet. In community.general (and I suppose most of others) the tests use ansible-test integration, which uses our own custom docker/VM images, for both controller and target. Specifiying the Python version is done in ansible-test’s CLI. In community.openwrt we run molecule in the standard images provided by GHA, and those do not support Python 3.15 yet. I have tried to set the CI to use one of the beta versions, but some dependency used by Sphinx do not support that Python version yet. Therefore, testing devel is incomplete and not directly in my hands.

I reckon these issues are not blockers to anything, we can (probably should) just ignore them as they are solved as soon as releases come out. But I felt it would be important to mention them nonetheless.

Thanks @mattclay - I’ve personally not used this functionality in ansible-test integration for code coverage. @felixfontein do you have an example of this for community.general? I took a look in Azure DevOps but couldn’t find a coverage report.

For community.windows we run with the --coverage option so

ansible-test windows-integration --coverage ...

The final coverage job collects all the coverage data per test matrix, combines it into one then uploads it to codecov.io

Doing it manually is like

# Clear out any existing coverage data
ansible-test coverage erase

# Run test(s) with the coverage collector, can also
# be done with windows-integration, units
ansible-test integration --coverage ping --docker

# View the console report, has a few extra args
ansible-test coverage report

# Combine all the various reports into one for publishing
# to codecov.io or other areas. File is created under
# tests/results/coverage/coverage
ansible-test coverage combine
1 Like

Coverage is only computed during the nightly CI runs. It’s uploaded to Codecov.

1 Like

Collections tested on Azure Pipelines typically run code coverage as part of their scheduled CI runs, uploading to Codecov.

Code coverage isn’t collected and reported for PRs on these collections unless requested (ci_coverage and ci_complete on the last commit message). Reporting on code coverage is really only effective when all tests are run, which is generally limited to schedule runs.

For anyone unfamiliar with this setup, it can give the appearance that code coverage isn’t used at all. Another common misconception is that only unit tests contribute to code coverage, which is something I briefly addressed in a previous comment.

1 Like

This initiative really makes me happy, thank you! I want to follow up on something I believe @kks was touching on earlier.

There are two major groups of Ansible users that are testing collections. Those are:

  1. vendors/communities focused on the python
  2. users/organizations that are focused on roles and playbooks

There are a lot of challenges with the testing for group one that I believe have been summarized already. The problem is even worse for the second group though. The process is not intuitive and there isn’t a good “right” way. The biggest challenges for group two are:

  1. Lack of relevant documentation and examples
  2. Information that is available assumes python knowledge or outdated (prior to collections)
  3. Lack of emphasis on molecule-plugins that provide an easy-button for testing
  4. Multiple layers of abstraction (/extensions/molecule/utils + /extensions/molecule/role + /tests/integration/X)
  5. Layers of helper tools that each have their own learning curve

I’d ask that we not lose sight of the second group of users and their needs in this effort. Over the years it feels like the testing tools have moved away from some of the principals laid out in The Zen of Ansible".

  • Ansible is not Python.
  • YAML sucks for coding.
  • Playbooks are not for programming.
  • Ansible users are (most likely) not programmers.
5 Likes

This is a very good point, indeed.

Hi @Jeff_Pullen - Thanks for your feedback here. Wanted to ask some follow up questions.

With group two as you explained it, are you referring to somebody testing Ansible content (such as a role or playbook) outside of a collection structure? E.g., a standalone role in a GitHub repo?

Lack of documentation and examples for what exactly? Do you have specific tooling or scenarios in mind here?

Is there a particular guide on docs.ansible.com that you’re referring to here?

I was discussing with @konstruktoid about molecule-plugins recently on Matrix. We were talking about how the docker driver with newer molecule versions doesn’t need to be installed anymore. The community.docker collection can be used to create a container for the scenario in the create sequence. See the example here which parses ansible-collection-hardening/extensions/molecule/delete_users_docker/molecule.yml at molecule-docker-scenario · dbrennand/ansible-collection-hardening · GitHub to create the containers.

This is what this point from the original post is alluding to:

1 Like

@dbrennand I appreciate the follow up. Lots of great questions, but these are going to get confusing smashing it all together. I’ll try my best.

Question 1:

With group two as you explained it, are you referring to somebody testing Ansible content (such as a role or playbook) outside of a collection structure? E.g., a standalone role in a GitHub repo?

I’m focused on testing ansible content as part of a collection. The process for testing stand-alone roles has extensive documentation and examples and the process has generally stayed the same for a very long time.

Question 2:

Lack of documentation and examples for what exactly? Do you have specific tooling or scenarios in mind here?

How to test ansible content (YAML) as part of a collection.

The most typical scenario that has to be out-of-the-box is running local podman/docker containers that include systemd for an automation developer to test as they’re writing the code and validating ansible-lint won’t fail a pipeline.

Question 3:

Information that is available assumes python knowledge or outdated (prior to collections)

The challenge is mostly that there is nearly a decade of content covering how to do this (books, videos, blogs, etc) that sort-of work, don’t work at all anymore, or take the person down some really frustrating paths.

I just reviewed the molecule documentation and see there have been some major reworks in the past year that I wasn’t tracking. This is a huge step in the right direction thanks to folks like @cidrblock . There is now enough information there for someone to get started, which is a big improvement. Assuming people are aware, we likely see more examples of it that can be used as a reference.

So much of the the information related to collection testing assumes that it is module focused. We don’t do a great job of differentiating role only collections from collections with modules. So users will encounter a long list of tools that seem required but may not be needed for their use-case.

Question / Comment 4:

I was discussing with @konstruktoid about molecule-plugins recently on Matrix. We were talking about how the docker driver with newer molecule versions doesn’t need to be installed anymore. The community.docker collection can be used to create a container for the scenario in the create sequence. See the example here which parses ansible-collection-hardening/extensions/molecule/delete_users_docker/molecule.yml at molecule-docker-scenario · dbrennand/ansible-collection-hardening · GitHub to create the containers.

This is my major point of contention with the direction of ansible content testing. It appears that flexibility is being prioritized over simplicity. The ‘ansible-native’ approach provides a tremendous amount of capability but adds a lot of complexity. Enough complexity that even having done it many times I still end up having to relearn and troubleshoot it every time to get things working (for example fighting all the relative paths in the molecule configs). It also takes a non-opinionated stance on the implementation so there are many ways to get it working that have the same effect but are very unique. This makes troubleshooting and maintenance more difficult.

The old way:

# molecule.yml
driver:
  name: docker

The new Ansible Native alternative:

# create.yml
---
- name: Create
  hosts: localhost
  gather_facts: false
  vars:
    molecule_inventory:
      all:
        children:
          molecule:
            hosts: {}
  tasks:
    - name: Create containers
      community.docker.docker_container:
        name: "{{ item.name }}"
        hostname: "{{ item.name }}"
        image: "{{ item.image }}"
        state: started
        command: "{{ item.command | default('sleep 1d') }}"
        privileged: "{{ item.privileged | default(false) }}"
        volumes: "{{ item.volumes | default(omit) }}"
        log_driver: json-file
      register: result
      loop: "{{ molecule_yml.platforms }}"
      loop_control:
        label: "{{ item.name }}"

    - name: Print container details
      ansible.builtin.debug:
        msg: "{{ result.results }}"

    - name: Fail if container is not running
      when: >
        item.container.State.ExitCode != 0 or not item.container.State.Running
      ansible.builtin.fail:
        msg: >-
          Container {{ item.container.Name }} failed to start properly.
          Exit Code: {{ item.container.State.ExitCode }}.
          Running: {{ item.container.State.Running }}.
      loop: "{{ result.results }}"
      loop_control:
        label: "{{ item.container.Name }}"

    - name: Add containers to molecule inventory
      vars:
        inventory_partial_yaml: |
          all:
            children:
              molecule:
                hosts:
                  "{{ item.name }}":
                    ansible_connection: community.docker.docker
                    ansible_python_interpreter: /usr/bin/python3
      ansible.builtin.set_fact:
        molecule_inventory: >-
          {{ molecule_inventory | combine(inventory_partial_yaml | from_yaml,
          recursive=true) }}
      loop: "{{ molecule_yml.platforms }}"
      loop_control:
        label: "{{ item.name }}"

    - name: Write molecule inventory
      ansible.builtin.copy:
        content: "{{ molecule_inventory | to_yaml }}"
        dest: "{{ molecule_ephemeral_directory }}/inventory/molecule_inventory.yml"
        mode: "0600"

    - name: Force inventory refresh
      ansible.builtin.meta: refresh_inventory

    - name: Fail if molecule group is missing
      ansible.builtin.assert:
        that: "'molecule' in groups"
        fail_msg: "molecule group was not found inside inventory groups: {{ groups }}"
      run_once: true

- name: Validate inventory
  hosts: molecule
  gather_facts: false
  tasks:
    - name: Check container access
      ansible.builtin.raw: /bin/true
      changed_when: false

There may be valid reasons to move away from molecule drivers, but I think its worth considering how we can still live up to the goal of “radically simple IT automation”.

3 Likes

Hi @Jeff_Pullen

Thank you for your detailed answers :slightly_smiling_face:

From my own experience, molecule is well suited to this scenario. I was reviewing the molecule documentation and there is Systemd container - Ansible Molecule. Do you think this guide would benefit from being a full user guide? For example, showing the create.yml playbook example and the configuration of the container under platforms in molecule.yml to support systemd inside a container using docker or podman as the container engine?

Thank you for your feedback here. I think there is an opportunity here for us to add documentation in the future specifically around role testing. Right now, when you navigate to Developers | Ansible documentation | Ansible documentation there is no guide about role testing.

Another short update that Bug: Molecule package missing from Tox Integration Environment install_deps · Issue #549 · ansible/tox-ansible · GitHub is now closed. :slightly_smiling_face:

Issue Documentation: User Guide References Non Existent GitHub Action Workflow · Issue #548 · ansible/tox-ansible · GitHub is now closed. Fixed in chore: fix CI/CD pipeline section and add skip functionality docs by dbrennand · Pull Request #564 · ansible/tox-ansible · GitHub :slightly_smiling_face:

feat: auto-discover and install collection requirements for unit/integration tests by dbrennand · Pull Request #567 · ansible/tox-ansible · GitHub also merged and fixes Bug: Ansible Integration and Unit Test Collection dependencies are not discovered automatically · Issue #550 · ansible/tox-ansible · GitHub :rocket: :tada:

Should automation developers be starting with role-only collections?

I genuinely cannot answer that, and following up on my earlier posts about group two (the folks testing roles and playbooks rather than Python modules), what surprised me digging in is that there does not seem to be an official answer either. The guidance across docs.ansible.com is not consistent about it, and I could not find an authoritative position anywhere on whether a role or playbook author should be working inside a collection at all.

That unanswered question matters because the tooling has quietly answered it for us. The modern, supported, CI-ready testing path (ansible-creator scaffolding, pytest-ansible, tox-ansible, antsibull-nox) has converged on the extensions/molecule/ layout, which only exists inside a collection. So the moment you want supported testing, you are in collection territory whether or not you ever decided a collection was the right unit for your work.

Getting reliable information along the way is harder than it should be too. In an earlier post I said standalone role testing was stable and well documented. Having gone through the current state, I have to walk that back. The examples are abundant, but most of them predate Molecule’s “ansible-native” rework (v25.9, September 2025), which changed how scenarios are configured, and they teach a model the project now treats as legacy. Those older resources are still what you find first: a search for how to test a role surfaces the old driver:/platforms: style, some of it very recent (I found a tutorial dated April 2026, tested against Molecule 26.4.0, still teaching the deprecated config). Meanwhile Molecule’s current docs no longer include a getting-started guide specifically for a role, only playbooks and collections, and the official guidance is not consistent about which shape it assumes (the Dev Tools testing page documents only a role within a collection, while the Sample Ansible setup page assumes bare roles and playbooks). To be clear, the recent Molecule docs rework by @cidrblock and team is a genuine and much-needed improvement. The problem now is discoverability: the good current material is not what a search turns up.

Even past the outdated information, the structural complexity is real, and it spikes as soon as content moves into a collection. This is where I think we under-serve role-only users:

  • A wave of additional tools arrives at once. Beyond Molecule you are now looking at ansible-creator, ansible-test, pytest-ansible, and one of tox-ansible or antsibull-nox, plus the galaxy.yml and namespace and versioning that come with the collection format itself. Each looks optional in isolation, but they interlock.
  • Documentation and examples for role-only collections are thin. Most collection-testing material assumes there are modules in the collection, so a role-only author has to work out which of that long list actually applies to them.
  • Each of those tools brings its own configuration, and that is where the hidden complexity lives:
    • Where the config even lives. Molecule alone splits across a shared extensions/molecule/config.yml, a per-scenario molecule.yml, and an inventory.yml, and then tox-ansible or antsibull-nox adds a tox.ini or a noxfile.py plus antsibull-nox.toml on top.
    • Redirection and inheritance between those files. The shared base config is inherited and overridden per scenario, and one tool invokes another (for example tox-ansible runs Molecule through pytest-ansible), so tracing what actually ran, and why, means chasing config across several files.
    • The config is python-centric. A noxfile.py is literally Python, and the pytest integration test is a Python file. Someone whose whole world is YAML is now editing Python to run a test.
    • None of it is familiar. nox, tox, and pytest are conventions from the Python developer world, not something a YAML automation author has any reason to already know.

So I think we need to definitively answer this question first, should a role or playbook author be starting in a collection? If the answer is yes, then the role-only, content-focused case needs to be tailored to automation developers writing YAML rather than python. It needs to be first-class and out of the box, with its own documentation and examples and not something an author reverse-engineers from module-oriented tooling.

3 Likes

Proposal: A standard arrangement for Molecule scenarios

Molecule can already test a role, a playbook, or a collection. What it lacks is a standard arrangement for how a scenario is put together, so that is left to each author, and there is nothing shared to lint or validate against. The current lack of a unifying standard has resulted in a learning cliff and conflicting examples. The point of a standard is a path an ordinary role author can follow without building that machinery. I want to propose one.

A scenario is three separable things:

  • Machinery: how a test host is created and destroyed, plus the boilerplate that wires the run together.
  • Inventory: which images and providers you test against.
  • Content: the converge that runs your code and the verify that checks it.

Only the content should differ between scenarios. Today Molecule makes you copy all three into every one. The standard I am proposing is that machinery and inventory live once, and a scenario holds only its content. Nothing here needs new tooling; every piece already exists, and we need to agree on how they are arranged.

Machinery: the platform collection owns create/destroy. A platform collection ships its provisioning once, as ansible-native playbooks callable by fully-qualified name:

containers.podman/
└── playbooks/
   ├── molecule_create.yml
   └── molecule_destroy.yml

Every scenario then looks the same, whether it tests a role or a collection:

# role:        <role>/molecule/default/
# collection:  <collection>/extensions/molecule/default/
# identical below the parent either way:
    ├── molecule.yml
    ├── create.yml     # import_playbook: containers.podman.molecule_create
    ├── destroy.yml    # import_playbook: containers.podman.molecule_destroy
    ├── converge.yml   # yours
    └── verify.yml     # yours

containers.podman owns the podman create/destroy, amazon.aws owns the EC2 one. You import it by name instead of copying it. (david_igou.molecule_provisioners already implements this pattern; it needs to be a standard, not one person’s collection.) The create.yml/destroy.yml files are one-line imports; they are separate files only because Molecule resolves these by filename, and lifting that so molecule.yml can name the playbook directly is a small enhancement worth making later.

Inventory: which images you test is data, not config. Today the image and provider set lives inside each scenario’s molecule.yml, or in shared config that authors inherit through non-standard tricks. The first means editing every scenario to change the set; the second is the kind of unofficial inheritance that has to be excluded from ansible-lint and schema validation, because there is no standard shape for it.

The set should instead be data the create playbook consumes. This falls out of the create/destroy contract below. Updates to what images to test can be centrally maintained in a single inventory. It is flat data you can open and read, not config merged from up the tree.

Content: converge and verify are all that differ. Once machinery and inventory are standardized, a scenario carries only the code that runs the thing under test and the assertions that check it.

What the standard removes.

  • One scenario format across roles, playbooks, and collections, all under the molecule directory. No separate tests/integration/targets/ tree to keep in sync.
  • The image inventory lives in one data file, instead of being repeated in every molecule.yml or inherited through non-standard config.
  • Shared files can be linted and schema-validated, because they have a standard shape instead of being ad hoc.
  • The silent zero-host success (a run that matches no hosts still reports pass) is closed by the create contract.
  • There is a blessed pattern to point people at, instead of the deprecated driver:/platforms: examples that are currently the most discoverable.

What I am asking this group to ratify. Four things:

  1. The location and names of a collection’s provisioning playbooks and the inventory: playbooks/molecule_<stage>.yml.
  2. One scenario format shared by a role, a playbook, and a collection.
  3. The convention that the image and provider inventory is data the create playbook consumes, not config repeated per scenario.
  4. A small create/destroy input/output contract: instances come from inventory, destroy loops the same group, and create fails loudly instead of reporting success over zero hosts. That contract is what makes provisioners interchangeable across platforms and closes the silent-failure gap. I have worked it out and can bring it as a starting point.

This is a light lift for collections. A collection that manages a thing generally already starts and stops it to test itself, so shipping molecule_create/molecule_destroy is mostly naming work, given clear standards and copy-ready examples. It stays optional: a collection that ships nothing loses nothing. Users / teams are also empowered to create and share their own “providers” using their existing collection distribution methods.

What deserves its own thread. A real standard still has to settle: whether create/destroy ships as a playbook or a role and how it surfaces in Galaxy alongside modules and roles; how inputs are expressed and validated (argument specs, defaults); and who owns the contract and how it is versioned without breaking consumers. I will open that thread and bring the fuller contract if there is appetite here.

… plugins and modules.

That was proposed by me (among other things) in 2023 https://github.com/ansible/molecule/issues/3919
This was before the molecule “redesign” and issue is closed now. So I suppose this is rejected. But I strongly believe that having explicit playbooks that have to be copied from one molecule scenario to another is a bad thing (and molecule still forces one to do that)

See my proposal in the link above - molecule playbooks to provision/delete/etc should be in separate collection, not just separate files.

Why it should be a convention? There are plenty of scenarios where this kind of complexity is not required. But idea is good. What is stopping you from removing platform section from molecule.yml today - nothing. Just make necessary adjustments to your create / destroy / etc playbooks so they get inventory from any place you like.

I completely agree that there is a lot of complexity around how to use molecule for running tests and this complexity has been created in part by the fact that proper examples and documentation are missing. But that would be very hard to resolve this situation by adding more complexity and creating any standard is a complex endeavour.

1 Like

@kks Great feedback and points.

I just want to address your final point to clarify my position. If the specifics of my proposal are adding more complexity than that is not intentional and something that I would want to iron out / eliminate.

My goal isn’t really to add on, but rather standardize around a “happy path” that will meet the needs of most automation devs (YAML). Except for the molecule enhancement to skip the one task import_playbook, everything else works “as-is” today. Its just leaning into a particular standard that users can reference and defend their decisions with. In fact the majority of the specifics are exactly what we have for examples in different places but never assembled into a single comprehensive package that automation devs can easily find. Worse, some of the docs conflict because they are addressing the other Ansible Dev (Python) group (for example using /tests/integration/targets).

Your suggestion from 2023 of just having stand alone collections for the molecule playbooks/roles for importing may be the better answer. I checked last night and only 20 out of 100 collections bundled with ‘ansible’ have any roles included. Making the create/destroy content may not be all that difficult, but it is still extra work the maintainers of those existing collections may not want.