From 6768e0f5f9d29c8d353b4dac56b4a5bd486bef8e Mon Sep 17 00:00:00 2001 From: Daniel McKnight <34697904+NeonDaniel@users.noreply.github.com> Date: Wed, 8 Nov 2023 12:14:02 -0800 Subject: [PATCH] Skill Test Class (#5) * Add common base class for skill unit tests * Refactor skill test case * Refactor setup and add `tearDownClass` method * Handle default behavior for GH Actions compat * Troubleshoot type error * Refactor skill tests to allow for setting envvar in skill test module * Update GitHub release automation (#6) Co-authored-by: Daniel McKnight * Increment Version to 0.0.1a5 * Update GitHub release automation (#7) Co-authored-by: Daniel McKnight * Increment Version to 0.0.1a6 * Increment Version to 0.0.1 * Update Changelog * Loosen pyyaml dependency for ovos-core compat. * Document skill unit tests --------- Co-authored-by: Daniel McKnight Co-authored-by: NeonDaniel --- README.md | 24 +++++++ neon_minerva/tests/skill_unit_test_base.py | 77 ++++++++++++++++++++++ requirements/requirements.txt | 3 +- 3 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 neon_minerva/tests/skill_unit_test_base.py diff --git a/README.md b/README.md index 297b728..a1e969b 100644 --- a/README.md +++ b/README.md @@ -36,3 +36,27 @@ To test that skill intents match as expected for all supported languages, the skill's root directory > - is a relative or absolute path to the resource test file, usually `test_intents.yaml` > - The `--padacioso` flag can be added to test with Padacioso instead of Padatious for relevant intents + +## Advanced Usage +In addition to convenient CLI methods, this package also provides test cases that +may be extended. + +### Skill Unit Tests +`neon_minerva.tests.skill_unit_test_base` provides `SkillTestCase`, a class +that supplies boilerplate setup/teardown/mocking for testing a skill. An example +skill test implementation could look like: + +```python +from os import environ +from neon_minerva.tests.skill_unit_test_base import SkillTestCase + +environ['TEST_SKILL_ENTRYPOINT'] = "my_skill.test" + +class MySkillTest(SkillTestCase): + def test_skill_init(self): + self.assertEqual(self.skill.skill_id, "my_skill.test") + ... +``` + +Be sure to review the base class for mocked methods and test paths as these may +change in the future. \ No newline at end of file diff --git a/neon_minerva/tests/skill_unit_test_base.py b/neon_minerva/tests/skill_unit_test_base.py new file mode 100644 index 0000000..c20dd8d --- /dev/null +++ b/neon_minerva/tests/skill_unit_test_base.py @@ -0,0 +1,77 @@ +# NEON AI (TM) SOFTWARE, Software Development Kit & Application Framework +# All trademark and other rights reserved by their respective owners +# Copyright 2008-2022 Neongecko.com Inc. +# Contributors: Daniel McKnight, Guy Daniels, Elon Gasper, Richard Leeds, +# Regina Bloomstine, Casimiro Ferreira, Andrii Pernatii, Kirill Hrymailo +# BSD-3 License +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import unittest +import shutil + +from os import environ, getenv +from os.path import dirname, join +from unittest.mock import Mock +from ovos_utils.messagebus import FakeBus + +from neon_minerva.skill import get_skill_object + + +class SkillTestCase(unittest.TestCase): + # Define test directories + test_fs = join(dirname(__file__), "skill_fs") + data_dir = join(test_fs, "data") + conf_dir = join(test_fs, "config") + environ["XDG_DATA_HOME"] = data_dir + environ["XDG_CONFIG_HOME"] = conf_dir + + # Define static parameters + bus = FakeBus() + bus.run_forever() + test_skill_id = 'test_skill.test' + + skill = None + + @classmethod + def setUpClass(cls) -> None: + # Get test skill + skill_entrypoint = getenv("TEST_SKILL_ENTRYPOINT") + if not skill_entrypoint: + from ovos_plugin_manager.skills import find_skill_plugins + skill_entrypoints = list(find_skill_plugins().keys()) + assert len(skill_entrypoints) == 1 + skill_entrypoint = skill_entrypoints[0] + + cls.skill = get_skill_object(skill_entrypoint=skill_entrypoint, + skill_id=cls.test_skill_id, bus=cls.bus) + # Override speak and speak_dialog to test passed arguments + cls.skill.speak = Mock() + cls.skill.speak_dialog = Mock() + + def setUp(self): + self.skill.speak.reset_mock() + self.skill.speak_dialog.reset_mock() + + @classmethod + def tearDownClass(cls) -> None: + shutil.rmtree(cls.test_fs) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 55f1e1d..7193ac9 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -5,4 +5,5 @@ ovos-workshop~=0.0.12 fann2==1.0.7 padatious~=0.4.8 padacioso~=0.1 -pyyaml~=6.0 \ No newline at end of file +pyyaml>=5.4,<7.0 +# PyYaml 5.4 support left for ovos-core 0.0.7 compat \ No newline at end of file