cleanup: Migrate existing unit tests to pytest

Cleanup the existing unit tests and move them to pytest. By using pytest
it becomes easier to add new tests for existing functionality in the
collection.

Signed-off-by: Felix Matouschek <fmatouschek@redhat.com>
This commit is contained in:
Felix Matouschek
2024-04-12 09:26:49 +02:00
parent afb8d178c2
commit 9848f66036
5 changed files with 159 additions and 247 deletions

View File

@@ -1,4 +1,5 @@
pytest pytest
pytest-ansible pytest-ansible
pytest-mock
pytest-xdist pytest-xdist
tox-ansible tox-ansible

View File

@@ -1,44 +0,0 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import json
import sys
from io import BytesIO
import pytest
import ansible.module_utils.basic
from ansible.module_utils.six import string_types
from ansible.module_utils._text import to_bytes
from ansible.module_utils.common._collections_compat import MutableMapping
@pytest.fixture
def stdin(mocker, request):
old_args = ansible.module_utils.basic._ANSIBLE_ARGS
ansible.module_utils.basic._ANSIBLE_ARGS = None
old_argv = sys.argv
sys.argv = ["ansible_unittest"]
if isinstance(request.param, string_types):
args = request.param
elif isinstance(request.param, MutableMapping):
if "ANSIBLE_MODULE_ARGS" not in request.param:
request.param = {"ANSIBLE_MODULE_ARGS": request.param}
if "_ansible_remote_tmp" not in request.param["ANSIBLE_MODULE_ARGS"]:
request.param["ANSIBLE_MODULE_ARGS"]["_ansible_remote_tmp"] = "/tmp"
if "_ansible_keep_remote_files" not in request.param["ANSIBLE_MODULE_ARGS"]:
request.param["ANSIBLE_MODULE_ARGS"]["_ansible_keep_remote_files"] = False
args = json.dumps(request.param)
else:
raise Exception("Malformed data to the stdin pytest fixture")
fake_stdin = BytesIO(to_bytes(args, errors="surrogate_or_strict"))
mocker.patch("ansible.module_utils.basic.sys.stdin", mocker.MagicMock())
mocker.patch("ansible.module_utils.basic.sys.stdin.buffer", fake_stdin)
yield fake_stdin
ansible.module_utils.basic._ANSIBLE_ARGS = old_args
sys.argv = old_argv

View File

@@ -6,11 +6,9 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
import unittest import pytest
from unittest.mock import patch, ANY from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils import basic
from ansible_collections.kubernetes.core.plugins.module_utils.k8s import runner from ansible_collections.kubernetes.core.plugins.module_utils.k8s import runner
from ansible_collections.kubevirt.core.plugins.modules import kubevirt_vm from ansible_collections.kubevirt.core.plugins.modules import kubevirt_vm
from ansible_collections.kubevirt.core.tests.unit.utils.ansible_module_mock import ( from ansible_collections.kubevirt.core.tests.unit.utils.ansible_module_mock import (
@@ -19,47 +17,55 @@ from ansible_collections.kubevirt.core.tests.unit.utils.ansible_module_mock impo
exit_json, exit_json,
fail_json, fail_json,
set_module_args, set_module_args,
get_api_client,
) )
FIXTURE1 = {
"apiVersion": "kubevirt.io/v1", @pytest.fixture(scope="module")
"kind": "VirtualMachine", def vm_definition():
"metadata": { return {
"name": "testvm", "apiVersion": "kubevirt.io/v1",
"namespace": "default", "kind": "VirtualMachine",
"labels": {"environment": "staging", "service": "loadbalancer"}, "metadata": {
}, "name": "testvm",
"spec": { "namespace": "default",
"running": True, "labels": {"environment": "staging", "service": "loadbalancer"},
"instancetype": {"name": "u1.medium"}, },
"preference": {"name": "fedora"}, "spec": {
"dataVolumeTemplates": [ "running": True,
{ "instancetype": {"name": "u1.medium"},
"metadata": {"name": "testdv"}, "preference": {"name": "fedora"},
"spec": { "dataVolumeTemplates": [
"source": { {
"registry": { "metadata": {"name": "testdv"},
"url": "docker://quay.io/containerdisks/fedora:latest" "spec": {
"source": {
"registry": {
"url": "docker://quay.io/containerdisks/fedora:latest"
},
},
"storage": {
"accessModes": ["ReadWriteOnce"],
"resources": {"requests": {"storage": "5Gi"}},
}, },
}, },
"storage": { }
"accessModes": ["ReadWriteOnce"], ],
"resources": {"requests": {"storage": "5Gi"}}, "template": {
}, "metadata": {
"labels": {"environment": "staging", "service": "loadbalancer"}
},
"spec": {
"domain": {"devices": {}},
"terminationGracePeriodSeconds": 180,
}, },
}
],
"template": {
"metadata": {
"labels": {"environment": "staging", "service": "loadbalancer"}
}, },
"spec": {"domain": {"devices": {}}, "terminationGracePeriodSeconds": 180},
}, },
}, }
}
METADATA = """apiVersion: kubevirt.io/v1
@pytest.fixture(scope="module")
def vm_manifest():
return """apiVersion: kubevirt.io/v1
kind: VirtualMachine kind: VirtualMachine
metadata: metadata:
name: "testvm" name: "testvm"
@@ -97,121 +103,96 @@ spec:
terminationGracePeriodSeconds: 180 terminationGracePeriodSeconds: 180
""" """
FIXTURE2 = {
"name": "testvm", @pytest.fixture(scope="module")
"namespace": "default", def module_params_create():
"state": "present", return {
"labels": {"service": "loadbalancer", "environment": "staging"}, "name": "testvm",
"instancetype": {"name": "u1.medium"}, "namespace": "default",
"preference": {"name": "fedora"}, "state": "present",
"data_volume_templates": [ "labels": {"service": "loadbalancer", "environment": "staging"},
{ "instancetype": {"name": "u1.medium"},
"metadata": {"name": "testdv"}, "preference": {"name": "fedora"},
"spec": { "data_volume_templates": [
"source": { {
"registry": { "metadata": {"name": "testdv"},
"url": "docker://quay.io/containerdisks/fedora:latest" "spec": {
"source": {
"registry": {
"url": "docker://quay.io/containerdisks/fedora:latest"
},
},
"storage": {
"accessModes": ["ReadWriteOnce"],
"resources": {"requests": {"storage": "5Gi"}},
}, },
}, },
"storage": {
"accessModes": ["ReadWriteOnce"],
"resources": {"requests": {"storage": "5Gi"}},
},
},
}
],
"spec": {"domain": {"devices": {}}, "terminationGracePeriodSeconds": 180},
"api_version": "kubevirt.io/v1",
"running": True,
"wait": False,
"wait_sleep": 5,
"wait_timeout": 120,
"force": False,
"generate_name": None,
"annotations": None,
"kubeconfig": None,
"context": None,
"host": None,
"api_key": None,
"username": None,
"password": None,
"validate_certs": None,
"ca_cert": None,
"client_cert": None,
"client_key": None,
"proxy": None,
"no_proxy": None,
"proxy_headers": None,
"persist_config": None,
"impersonate_user": None,
"impersonate_groups": None,
"delete_options": None,
"resource_definition": METADATA,
"wait_condition": {"type": "Ready", "status": True},
}
class TestCreateVM(unittest.TestCase):
def setUp(self):
self.mock_module_helper = patch.multiple(
basic.AnsibleModule, exit_json=exit_json, fail_json=fail_json
)
self.mock_module_helper.start()
self.mock_runner = patch.multiple(runner, get_api_client=get_api_client)
self.mock_runner.start()
# Stop the patch after test execution
# like tearDown but executed also when the setup failed
self.addCleanup(self.mock_module_helper.stop)
self.addCleanup(self.mock_runner.stop)
def test_module_fail_when_required_args_missing(self):
with self.assertRaises(AnsibleFailJson):
set_module_args({})
kubevirt_vm.main()
def test_create(self):
set_module_args(
{
"name": "testvm",
"namespace": "default",
"state": "present",
"labels": {"service": "loadbalancer", "environment": "staging"},
"instancetype": {"name": "u1.medium"},
"preference": {"name": "fedora"},
"data_volume_templates": [
{
"metadata": {"name": "testdv"},
"spec": {
"source": {
"registry": {
"url": "docker://quay.io/containerdisks/fedora:latest"
},
},
"storage": {
"accessModes": ["ReadWriteOnce"],
"resources": {"requests": {"storage": "5Gi"}},
},
},
}
],
"spec": {
"domain": {"devices": {}},
"terminationGracePeriodSeconds": 180,
},
} }
) ],
with patch.object(runner, "perform_action") as mock_run_command: "spec": {
mock_run_command.return_value = { "domain": {"devices": {}},
"method": "create", "terminationGracePeriodSeconds": 180,
"changed": True, },
"result": "success", }
} # successful execution
with self.assertRaises(AnsibleExitJson) as result:
kubevirt_vm.main() @pytest.fixture(scope="module")
mock_run_command.assert_called_once_with( def k8s_module_params_create(module_params_create, vm_manifest):
ANY, return module_params_create | {
FIXTURE1, "api_version": "kubevirt.io/v1",
FIXTURE2, "running": True,
) "wait": False,
"wait_sleep": 5,
"wait_timeout": 120,
"force": False,
"generate_name": None,
"annotations": None,
"kubeconfig": None,
"context": None,
"host": None,
"api_key": None,
"username": None,
"password": None,
"validate_certs": None,
"ca_cert": None,
"client_cert": None,
"client_key": None,
"proxy": None,
"no_proxy": None,
"proxy_headers": None,
"persist_config": None,
"impersonate_user": None,
"impersonate_groups": None,
"delete_options": None,
"resource_definition": vm_manifest,
"wait_condition": {"type": "Ready", "status": True},
}
def test_module_fails_when_required_args_missing(monkeypatch):
monkeypatch.setattr(AnsibleModule, "fail_json", fail_json)
with pytest.raises(AnsibleFailJson):
set_module_args({})
kubevirt_vm.main()
def test_module_create(
monkeypatch, mocker, module_params_create, k8s_module_params_create, vm_definition
):
monkeypatch.setattr(AnsibleModule, "exit_json", exit_json)
monkeypatch.setattr(runner, "get_api_client", lambda _: None)
set_module_args(module_params_create)
perform_action = mocker.patch.object(runner, "perform_action")
perform_action.return_value = {
"method": "create",
"changed": True,
"result": "success",
}
with pytest.raises(AnsibleExitJson):
kubevirt_vm.main()
perform_action.assert_called_once_with(
mocker.ANY, vm_definition, k8s_module_params_create
)

View File

@@ -6,8 +6,7 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
from unittest import TestCase import pytest
from unittest.mock import patch
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import ( from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import (
@@ -19,12 +18,10 @@ from ansible_collections.kubevirt.core.plugins.modules import (
from ansible_collections.kubevirt.core.tests.unit.utils.ansible_module_mock import ( from ansible_collections.kubevirt.core.tests.unit.utils.ansible_module_mock import (
AnsibleExitJson, AnsibleExitJson,
exit_json, exit_json,
fail_json,
set_module_args, set_module_args,
get_api_client,
) )
FIXTURE1 = { FIND_ARGS_DEFAULT = {
"kind": "VirtualMachine", "kind": "VirtualMachine",
"api_version": "kubevirt.io/v1", "api_version": "kubevirt.io/v1",
"name": None, "name": None,
@@ -37,7 +34,7 @@ FIXTURE1 = {
"condition": {"type": "Ready", "status": True}, "condition": {"type": "Ready", "status": True},
} }
FIXTURE2 = { FIND_ARGS_NAME_NAMESPACE = {
"kind": "VirtualMachine", "kind": "VirtualMachine",
"api_version": "kubevirt.io/v1", "api_version": "kubevirt.io/v1",
"name": "testvm", "name": "testvm",
@@ -51,43 +48,26 @@ FIXTURE2 = {
} }
class TestDescribeVM(TestCase): @pytest.mark.parametrize(
def setUp(self): "module_args,find_args",
self.mock_module_helper = patch.multiple( [
AnsibleModule, exit_json=exit_json, fail_json=fail_json ({}, FIND_ARGS_DEFAULT),
) ({"name": "testvm", "namespace": "default"}, FIND_ARGS_NAME_NAMESPACE),
self.mock_module_helper.start() ],
)
def test_module(monkeypatch, mocker, module_args, find_args):
monkeypatch.setattr(AnsibleModule, "exit_json", exit_json)
monkeypatch.setattr(kubevirt_vm_info, "get_api_client", lambda _: None)
self.mock_main = patch.multiple(kubevirt_vm_info, get_api_client=get_api_client) set_module_args(module_args)
self.mock_main.start()
# Stop the patch after test execution find = mocker.patch.object(K8sService, "find")
# like tearDown but executed also when the setup failed find.return_value = {
self.addCleanup(self.mock_module_helper.stop) "api_found": True,
self.addCleanup(self.mock_main.stop) "failed": False,
"resources": [],
}
def run_module(self, fixture): with pytest.raises(AnsibleExitJson):
with patch.object(K8sService, "find") as mock_find_command: kubevirt_vm_info.main()
mock_find_command.return_value = { find.assert_called_once_with(**find_args)
"api_found": True,
"failed": False,
"resources": [],
} # successful execution
with self.assertRaises(AnsibleExitJson):
kubevirt_vm_info.main()
mock_find_command.assert_called_once_with(
**fixture,
)
def test_describe_without_args(self):
set_module_args({})
self.run_module(FIXTURE1)
def test_describe_with_args(self):
set_module_args(
{
"name": "testvm",
"namespace": "default",
}
)
self.run_module(FIXTURE2)

View File

@@ -2,7 +2,8 @@
# Copyright: (c) 2021, Ansible Project # Copyright: (c) 2021, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# This module maock the AnsibleModule class for more information please visite # This file allows to run modules in unit tests.
# It was taken from:
# https://docs.ansible.com/ansible/latest/dev_guide/testing_units_modules.html#module-argument-processing # https://docs.ansible.com/ansible/latest/dev_guide/testing_units_modules.html#module-argument-processing
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
@@ -23,13 +24,11 @@ def set_module_args(args):
class AnsibleExitJson(Exception): class AnsibleExitJson(Exception):
"""Exception class to be raised by module.exit_json and caught by the test case""" """Exception class to be raised by module.exit_json and caught by the test case"""
pass pass
class AnsibleFailJson(Exception): class AnsibleFailJson(Exception):
"""Exception class to be raised by module.fail_json and caught by the test case""" """Exception class to be raised by module.fail_json and caught by the test case"""
pass pass
@@ -44,8 +43,3 @@ def fail_json(*args, **kwargs):
"""function to patch over fail_json; package return data into an exception""" """function to patch over fail_json; package return data into an exception"""
kwargs["failed"] = True kwargs["failed"] = True
raise AnsibleFailJson(kwargs) raise AnsibleFailJson(kwargs)
def get_api_client(*args, **kwargs):
"""function to patch over get_api_client"""
pass