Skip to content

Commit

Permalink
FEATURE: Add more unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
do Carmo Lucas committed Jun 19, 2024
1 parent e79c7c9 commit 8a5d953
Show file tree
Hide file tree
Showing 10 changed files with 277 additions and 33 deletions.
27 changes: 9 additions & 18 deletions unittests/annotate_params_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import unittest
import requests
import mock
import argparse

from xml.etree import ElementTree as ET # no parsing, just data-structure manipulation
from defusedxml import ElementTree as DET # just parsing, no data-structure manipulation
Expand All @@ -35,7 +34,7 @@
from MethodicConfigurator.annotate_params import PARAM_DEFINITION_XML_FILE


class TestParamDocsUpdate(unittest.TestCase):
class TestParamDocsUpdate(unittest.TestCase): # pylint: disable=missing-class-docstring

def setUp(self):
# Create a temporary directory
Expand Down Expand Up @@ -558,16 +557,8 @@ def test_empty_parameter_file(self):

class AnnotateParamsTest(unittest.TestCase):

@patch('argparse.ArgumentParser.parse_args')
def test_arg_parser_valid_arguments(self, mock_parse_args):
test_args = ['--vehicle-type', 'ArduCopter', '--sort', 'none', '--target', 'parameters']
mock_parse_args.return_value = argparse.Namespace(
vehicle_type='ArduCopter',
sort='none',
target='parameters',
verbose=False,
max_line_length=100,
)
def test_arg_parser_valid_arguments(self):
test_args = ['annotate_params', '--vehicle-type', 'ArduCopter', '--sort', 'none', 'parameters']
with patch('sys.argv', test_args):
args = arg_parser()
self.assertEqual(args.vehicle_type, 'ArduCopter')
Expand All @@ -577,19 +568,19 @@ def test_arg_parser_valid_arguments(self, mock_parse_args):
self.assertEqual(args.max_line_length, 100)

def test_arg_parser_invalid_vehicle_type(self):
test_args = ['--vehicle-type', 'InvalidType', '--sort', 'none', '--target', 'parameters']
test_args = ['annotate_params', '--vehicle-type', 'InvalidType', '--sort', 'none', 'parameters']
with patch('sys.argv', test_args):
with self.assertRaises(SystemExit):
arg_parser()

def test_arg_parser_invalid_sort_option(self):
test_args = ['--vehicle-type', 'ArduCopter', '--sort', 'invalid', '--target', 'parameters']
test_args = ['annotate_params', '--vehicle-type', 'ArduCopter', '--sort', 'invalid', 'parameters']
with patch('sys.argv', test_args):
with self.assertRaises(SystemExit):
arg_parser()

def test_arg_parser_invalid_target_option(self):
test_args = ['--vehicle-type', 'ArduCopter', '--sort', 'none', '--target', 'invalid']
def test_arg_parser_invalid_line_length_option(self):
test_args = ['annotate_params', '--vehicle-type', 'ArduCopter', '--sort', 'none', '-m', 'invalid', 'parameters']
with patch('sys.argv', test_args):
with self.assertRaises(SystemExit):
arg_parser()
Expand Down Expand Up @@ -618,7 +609,7 @@ def test_main_ioerror(
with self.assertRaises(SystemExit) as cm:
main()

self.assertEqual(cm.exception.code, 2)
self.assertIn(cm.exception.code, [1, 2])

@patch('annotate_params.arg_parser')
@patch('annotate_params.get_xml_url')
Expand All @@ -641,7 +632,7 @@ def test_main_oserror(
with self.assertRaises(SystemExit) as cm:
main()

self.assertEqual(cm.exception.code, 2)
self.assertIn(cm.exception.code, [1, 2])

@patch('annotate_params.get_xml_url')
def test_get_xml_url_exception(self, mock_get_xml_url):
Expand Down
2 changes: 1 addition & 1 deletion unittests/ardupilot_methodic_configurator_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from MethodicConfigurator.ardupilot_methodic_configurator import argument_parser


class TestArgumentParser(unittest.TestCase):
class TestArgumentParser(unittest.TestCase): # pylint: disable=missing-class-docstring
@patch('argparse.ArgumentParser.parse_args',
return_value=argparse.Namespace(conn='tcp:127.0.0.1:5760', params='params_dir'))
def test_argument_parser(self, mock_args):
Expand Down
65 changes: 65 additions & 0 deletions unittests/battery_cell_voltages_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/usr/bin/env python3

'''
This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator
SPDX-FileCopyrightText: 2024 Amilcar do Carmo Lucas <amilcar.lucas@iav.de>
SPDX-License-Identifier: GPL-3.0-or-later
'''

import unittest
from math import nan

from MethodicConfigurator.battery_cell_voltages import battery_cell_voltages
from MethodicConfigurator.battery_cell_voltages import BatteryCell


class TestBatteryCell(unittest.TestCase): # pylint: disable=missing-class-docstring

def test_chemistries(self):
expected_chemistries = ['LiIon', 'LiIonSS', 'LiIonSSHV', 'Lipo', 'LipoHV', 'LipoHVSS']
chemistries = BatteryCell.chemistries()
self.assertEqual(chemistries, expected_chemistries)

def test_limit_max_voltage(self):
self.assertEqual(BatteryCell.limit_max_voltage('LiIon'), 4.1)
self.assertEqual(BatteryCell.limit_max_voltage('LipoHV'), 4.35)
self.assertEqual(BatteryCell.limit_max_voltage('NonExistentChemistry'), 4.45)

def test_limit_min_voltage(self):
self.assertEqual(BatteryCell.limit_min_voltage('LiIon'), 2.5)
self.assertEqual(BatteryCell.limit_min_voltage('LipoHV'), 3.0)
self.assertEqual(BatteryCell.limit_min_voltage('NonExistentChemistry'), 2.4)

def test_recommended_max_voltage(self):
self.assertEqual(BatteryCell.recommended_max_voltage('LiIon'), 4.1)
self.assertIs(BatteryCell.recommended_max_voltage('NonExistentChemistry'), nan)

def test_recommended_low_voltage(self):
self.assertEqual(BatteryCell.recommended_low_voltage('LiIon'), 3.1)
self.assertIs(BatteryCell.recommended_low_voltage('NonExistentChemistry'), nan)

def test_recommended_crit_voltage(self):
self.assertEqual(BatteryCell.recommended_crit_voltage('LiIon'), 2.8)
self.assertIs(BatteryCell.recommended_crit_voltage('NonExistentChemistry'), nan)

def test_voltage_monoticity(self):
for chemistry in BatteryCell.chemistries():
with self.subTest(chemistry=chemistry):
self.assertEqual(BatteryCell.limit_max_voltage(chemistry),
battery_cell_voltages[chemistry].get('absolute_max'))
self.assertEqual(BatteryCell.limit_min_voltage(chemistry),
battery_cell_voltages[chemistry].get('absolute_min'))
self.assertGreaterEqual(BatteryCell.limit_max_voltage(chemistry),
BatteryCell.recommended_max_voltage(chemistry))
self.assertGreaterEqual(BatteryCell.recommended_max_voltage(chemistry),
BatteryCell.recommended_low_voltage(chemistry))
self.assertGreaterEqual(BatteryCell.recommended_low_voltage(chemistry),
BatteryCell.recommended_crit_voltage(chemistry))
self.assertGreaterEqual(BatteryCell.recommended_crit_voltage(chemistry),
BatteryCell.limit_min_voltage(chemistry))


if __name__ == '__main__':
unittest.main()
46 changes: 46 additions & 0 deletions unittests/common_arguments_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env python3

'''
This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator
SPDX-FileCopyrightText: 2024 Amilcar do Carmo Lucas <amilcar.lucas@iav.de>
SPDX-License-Identifier: GPL-3.0-or-later
'''

import unittest
from unittest.mock import MagicMock
from argparse import ArgumentParser

from MethodicConfigurator import common_arguments


class TestCommonArguments(unittest.TestCase):

def test_add_common_arguments_and_parse_loglevel(self):
# Test that loglevel choices are added correctly
parser = ArgumentParser()
parser.parse_args = MagicMock(return_value=MagicMock(loglevel='INFO'))

updated_parser = common_arguments.add_common_arguments_and_parse(parser)

# This will raise an error if loglevel is not an argument
# or if the choices are not set up correctly.
updated_parser.parse_args(['--loglevel', 'INFO'])
updated_parser.parse_args.assert_called_with(['--loglevel', 'INFO'])

def test_version_argument(self):
# Test that version argument displays correct version
parser = ArgumentParser()
# Mock the parse_args to just print the version string
parser.parse_args = MagicMock()
common_arguments.VERSION = "1.0.0"
updated_parser = common_arguments.add_common_arguments_and_parse(parser)

# We assume the call to parse_args with --version should print the version
# Since we cannot capture stdout here easily, we'll just confirm the method was called with --version
updated_parser.parse_args(['--version'])
updated_parser.parse_args.assert_called_with(['--version'])

if __name__ == '__main__':
unittest.main()
21 changes: 18 additions & 3 deletions unittests/extract_param_defaults_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,23 @@
#
# SPDX-FileCopyrightText: 2024 Amilcar do Carmo Lucas <amilcar.lucas@iav.de>
#
#SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-License-Identifier: GPL-3.0-or-later

PYTHONPATH=../MethodicConfigurator python3 -m coverage run -m unittest extract_param_defaults_test.py
python3 -m coverage html
REQUIRED_PKGS=("coverage" "mock")

is_installed() {
pip show "$1" > /dev/null 2>&1
}

for pkg in "${REQUIRED_PKGS[@]}"; do
if ! is_installed "$pkg"; then
echo "Installing $pkg..."
pip install "$pkg"
else
echo "$pkg is already installed."
fi
done

PYTHONPATH=../MethodicConfigurator python -m coverage run -m unittest extract_param_defaults_test.py
python -m coverage html
firefox htmlcov/extract_param_defaults_py.html
2 changes: 1 addition & 1 deletion unittests/frontend_tkinter_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from MethodicConfigurator.frontend_tkinter_base import show_tooltip


class TestShowErrorMessage(unittest.TestCase):
class TestShowErrorMessage(unittest.TestCase): # pylint: disable=missing-class-docstring
@patch('tkinter.messagebox.showerror')
@patch('tkinter.Tk')
@patch('tkinter.ttk.Style') # Mock the ttk.Style class
Expand Down
83 changes: 83 additions & 0 deletions unittests/middleware_template_overview_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env python3

'''
This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator
SPDX-FileCopyrightText: 2024 Amilcar do Carmo Lucas <amilcar.lucas@iav.de>
SPDX-License-Identifier: GPL-3.0-or-later
'''

import unittest
from MethodicConfigurator.middleware_template_overview import TemplateOverview


class TestTemplateOverview(unittest.TestCase): # pylint: disable=missing-class-docstring

def setUp(self):
# Define sample data to be used in tests
self.sample_data = {
'Flight Controller': {
'Product': {
'Manufacturer': 'ArduPilot',
'Model': 'Pixhawk4'
}
},
'Frame': {
'Specifications': {
'TOW max Kg': '5'
}
},
# ... add other components as per your structure
}

def test_template_overview_initialization(self):
# Initialize the TemplateOverview with sample data
template_overview = TemplateOverview(self.sample_data)

# Check if attributes are set correctly
self.assertEqual(template_overview.fc_manufacturer, 'ArduPilot')
self.assertEqual(template_overview.fc_model, 'Pixhawk4')
self.assertEqual(template_overview.tow_max_kg, '5')
# .. similarly test other attributes

def test_template_overview_column_labels(self):
# Check if the column labels match the required order
# pylint: disable=duplicate-code
expected_columns = (
"FC\nManufacturer",
"FC\nModel",
"TOW Max\n[KG]",
"Prop Diameter\n[inches]",
"RC\nProtocol",
"Telemetry\nModel",
"ESC\nProtocol",
"GNSS\nModel",
"GNSS\nConnection",
)
# pylint: enable=duplicate-code
self.assertEqual(TemplateOverview.columns(), expected_columns)

def test_template_overview_attributes_method(self):
# Initialize the TemplateOverview with the sample data
template_overview = TemplateOverview(self.sample_data)

# Fetch the instance attribute keys
attribute_keys = template_overview.attributes()

# Check if the attribute keys match the expected set of attributes
expected_attributes = {
'fc_manufacturer',
'fc_model',
'tow_max_kg',
'prop_diameter_inches',
'rc_protocol',
'telemetry_model',
'esc_protocol',
'gnss_model',
'gnss_connection',
}
self.assertEqual(expected_attributes, set(attribute_keys))

if __name__ == '__main__':
unittest.main()
14 changes: 7 additions & 7 deletions unittests/param_pid_adjustment_update_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,15 +243,15 @@ def test_all_parameters_present(self):
update_pid_adjustment_params(self.test_dir, os.path.basename(self.optimized_param_file), 0.5)

# Assert that the error message is as expected
self.assertEqual(cm.exception.args[0], "Parameter PARAM2 is not present in test_directory/00_default.param")
self.assertEqual(cm.exception.args[0], f"Parameter PARAM2 is not present in {os.path.join('test_directory','00_default.param')}")

def test_parameter_missing_from_default_file(self):
# A parameter is missing from the default parameter file
with open(self.default_param_file, 'w', encoding='utf-8') as f:
f.write('PARAM1,1.0\nPARAM3,3.0\n')
with self.assertRaises(SystemExit) as cm:
update_pid_adjustment_params(self.test_dir, os.path.basename(self.optimized_param_file), 0.5)
self.assertEqual(cm.exception.args[0], "Parameter PARAM2 is not present in test_directory/00_default.param")
self.assertEqual(cm.exception.args[0], f"Parameter PARAM2 is not present in {os.path.join('test_directory','00_default.param')}")

def test_parameter_missing_from_optimized_file(self):
# A parameter is missing from the optimized parameter file
Expand All @@ -260,7 +260,7 @@ def test_parameter_missing_from_optimized_file(self):
with self.assertRaises(SystemExit) as cm:
update_pid_adjustment_params(self.test_dir, os.path.basename(self.optimized_param_file), 0.5)
self.assertEqual(cm.exception.args[0],
"Parameter PARAM2 is not present in test_directory/optimized_parameter_file.param")
f"Parameter PARAM2 is not present in {os.path.join('test_directory','optimized_parameter_file.param')}")

def test_empty_files(self):
# Both the default and optimized parameter files are empty
Expand All @@ -270,15 +270,15 @@ def test_empty_files(self):
pass
with self.assertRaises(SystemExit) as cm:
update_pid_adjustment_params(self.test_dir, os.path.basename(self.optimized_param_file), 0.5)
self.assertEqual(cm.exception.args[0], "Failed to load default parameters from test_directory/00_default.param")
self.assertEqual(cm.exception.args[0], f"Failed to load default parameters from {os.path.join('test_directory','00_default.param')}")

def test_empty_default_file(self):
# Create an empty default parameter file
with open(self.default_param_file, 'w', encoding='utf-8') as f: # noqa F841
pass
with self.assertRaises(SystemExit) as cm:
update_pid_adjustment_params(self.test_dir, os.path.basename(self.optimized_param_file), 0.5)
self.assertEqual(cm.exception.args[0], "Failed to load default parameters from test_directory/00_default.param")
self.assertEqual(cm.exception.args[0], f"Failed to load default parameters from {os.path.join('test_directory','00_default.param')}")

def test_empty_optimized_file(self):
# Create an empty optimized parameter file
Expand All @@ -287,7 +287,7 @@ def test_empty_optimized_file(self):
with self.assertRaises(SystemExit) as cm:
update_pid_adjustment_params(self.test_dir, os.path.basename(self.optimized_param_file), 0.5)
self.assertEqual(cm.exception.args[0],
"Failed to load optimized parameters from test_directory/optimized_parameter_file.param")
f"Failed to load optimized parameters from {os.path.join('test_directory','optimized_parameter_file.param')}")

def test_empty_adjustment_file(self):
# Create an empty adjustment parameter file
Expand All @@ -296,7 +296,7 @@ def test_empty_adjustment_file(self):
with self.assertRaises(SystemExit) as cm:
update_pid_adjustment_params(self.test_dir, os.path.basename(self.optimized_param_file), 0.5)
self.assertEqual(cm.exception.args[0],
"Failed to load PID adjustment parameters from test_directory/16_pid_adjustment.param")
f"Failed to load PID adjustment parameters from {os.path.join('test_directory','16_pid_adjustment.param')}")

def test_zero_default_value(self):
# Set a parameter in the default parameter file to zero
Expand Down
Loading

0 comments on commit 8a5d953

Please sign in to comment.