Skip to content

Commit a56275f

Browse files
committed
FEATURE: Add more unit tests
1 parent e79c7c9 commit a56275f

10 files changed

+277
-33
lines changed

unittests/annotate_params_test.py

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import unittest
1717
import requests
1818
import mock
19-
import argparse
2019

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

3736

38-
class TestParamDocsUpdate(unittest.TestCase):
37+
class TestParamDocsUpdate(unittest.TestCase): # pylint: disable=missing-class-docstring
3938

4039
def setUp(self):
4140
# Create a temporary directory
@@ -558,16 +557,8 @@ def test_empty_parameter_file(self):
558557

559558
class AnnotateParamsTest(unittest.TestCase):
560559

561-
@patch('argparse.ArgumentParser.parse_args')
562-
def test_arg_parser_valid_arguments(self, mock_parse_args):
563-
test_args = ['--vehicle-type', 'ArduCopter', '--sort', 'none', '--target', 'parameters']
564-
mock_parse_args.return_value = argparse.Namespace(
565-
vehicle_type='ArduCopter',
566-
sort='none',
567-
target='parameters',
568-
verbose=False,
569-
max_line_length=100,
570-
)
560+
def test_arg_parser_valid_arguments(self):
561+
test_args = ['annotate_params', '--vehicle-type', 'ArduCopter', '--sort', 'none', 'parameters']
571562
with patch('sys.argv', test_args):
572563
args = arg_parser()
573564
self.assertEqual(args.vehicle_type, 'ArduCopter')
@@ -577,19 +568,19 @@ def test_arg_parser_valid_arguments(self, mock_parse_args):
577568
self.assertEqual(args.max_line_length, 100)
578569

579570
def test_arg_parser_invalid_vehicle_type(self):
580-
test_args = ['--vehicle-type', 'InvalidType', '--sort', 'none', '--target', 'parameters']
571+
test_args = ['annotate_params', '--vehicle-type', 'InvalidType', '--sort', 'none', 'parameters']
581572
with patch('sys.argv', test_args):
582573
with self.assertRaises(SystemExit):
583574
arg_parser()
584575

585576
def test_arg_parser_invalid_sort_option(self):
586-
test_args = ['--vehicle-type', 'ArduCopter', '--sort', 'invalid', '--target', 'parameters']
577+
test_args = ['annotate_params', '--vehicle-type', 'ArduCopter', '--sort', 'invalid', 'parameters']
587578
with patch('sys.argv', test_args):
588579
with self.assertRaises(SystemExit):
589580
arg_parser()
590581

591-
def test_arg_parser_invalid_target_option(self):
592-
test_args = ['--vehicle-type', 'ArduCopter', '--sort', 'none', '--target', 'invalid']
582+
def test_arg_parser_invalid_line_length_option(self):
583+
test_args = ['annotate_params', '--vehicle-type', 'ArduCopter', '--sort', 'none', '-m', 'invalid', 'parameters']
593584
with patch('sys.argv', test_args):
594585
with self.assertRaises(SystemExit):
595586
arg_parser()
@@ -618,7 +609,7 @@ def test_main_ioerror(
618609
with self.assertRaises(SystemExit) as cm:
619610
main()
620611

621-
self.assertEqual(cm.exception.code, 2)
612+
self.assertIn(cm.exception.code, [1, 2])
622613

623614
@patch('annotate_params.arg_parser')
624615
@patch('annotate_params.get_xml_url')
@@ -641,7 +632,7 @@ def test_main_oserror(
641632
with self.assertRaises(SystemExit) as cm:
642633
main()
643634

644-
self.assertEqual(cm.exception.code, 2)
635+
self.assertIn(cm.exception.code, [1, 2])
645636

646637
@patch('annotate_params.get_xml_url')
647638
def test_get_xml_url_exception(self, mock_get_xml_url):

unittests/ardupilot_methodic_configurator_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from MethodicConfigurator.ardupilot_methodic_configurator import argument_parser
1919

2020

21-
class TestArgumentParser(unittest.TestCase):
21+
class TestArgumentParser(unittest.TestCase): # pylint: disable=missing-class-docstring
2222
@patch('argparse.ArgumentParser.parse_args',
2323
return_value=argparse.Namespace(conn='tcp:127.0.0.1:5760', params='params_dir'))
2424
def test_argument_parser(self, mock_args):
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#!/usr/bin/env python3
2+
3+
'''
4+
This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator
5+
6+
SPDX-FileCopyrightText: 2024 Amilcar do Carmo Lucas <[email protected]>
7+
8+
SPDX-License-Identifier: GPL-3.0-or-later
9+
'''
10+
11+
import unittest
12+
from math import nan
13+
14+
from MethodicConfigurator.battery_cell_voltages import battery_cell_voltages
15+
from MethodicConfigurator.battery_cell_voltages import BatteryCell
16+
17+
18+
class TestBatteryCell(unittest.TestCase): # pylint: disable=missing-class-docstring
19+
20+
def test_chemistries(self):
21+
expected_chemistries = ['LiIon', 'LiIonSS', 'LiIonSSHV', 'Lipo', 'LipoHV', 'LipoHVSS']
22+
chemistries = BatteryCell.chemistries()
23+
self.assertEqual(chemistries, expected_chemistries)
24+
25+
def test_limit_max_voltage(self):
26+
self.assertEqual(BatteryCell.limit_max_voltage('LiIon'), 4.1)
27+
self.assertEqual(BatteryCell.limit_max_voltage('LipoHV'), 4.35)
28+
self.assertEqual(BatteryCell.limit_max_voltage('NonExistentChemistry'), 4.45)
29+
30+
def test_limit_min_voltage(self):
31+
self.assertEqual(BatteryCell.limit_min_voltage('LiIon'), 2.5)
32+
self.assertEqual(BatteryCell.limit_min_voltage('LipoHV'), 3.0)
33+
self.assertEqual(BatteryCell.limit_min_voltage('NonExistentChemistry'), 2.4)
34+
35+
def test_recommended_max_voltage(self):
36+
self.assertEqual(BatteryCell.recommended_max_voltage('LiIon'), 4.1)
37+
self.assertIs(BatteryCell.recommended_max_voltage('NonExistentChemistry'), nan)
38+
39+
def test_recommended_low_voltage(self):
40+
self.assertEqual(BatteryCell.recommended_low_voltage('LiIon'), 3.1)
41+
self.assertIs(BatteryCell.recommended_low_voltage('NonExistentChemistry'), nan)
42+
43+
def test_recommended_crit_voltage(self):
44+
self.assertEqual(BatteryCell.recommended_crit_voltage('LiIon'), 2.8)
45+
self.assertIs(BatteryCell.recommended_crit_voltage('NonExistentChemistry'), nan)
46+
47+
def test_voltage_monoticity(self):
48+
for chemistry in BatteryCell.chemistries():
49+
with self.subTest(chemistry=chemistry):
50+
self.assertEqual(BatteryCell.limit_max_voltage(chemistry),
51+
battery_cell_voltages[chemistry].get('absolute_max'))
52+
self.assertEqual(BatteryCell.limit_min_voltage(chemistry),
53+
battery_cell_voltages[chemistry].get('absolute_min'))
54+
self.assertGreaterEqual(BatteryCell.limit_max_voltage(chemistry),
55+
BatteryCell.recommended_max_voltage(chemistry))
56+
self.assertGreaterEqual(BatteryCell.recommended_max_voltage(chemistry),
57+
BatteryCell.recommended_low_voltage(chemistry))
58+
self.assertGreaterEqual(BatteryCell.recommended_low_voltage(chemistry),
59+
BatteryCell.recommended_crit_voltage(chemistry))
60+
self.assertGreaterEqual(BatteryCell.recommended_crit_voltage(chemistry),
61+
BatteryCell.limit_min_voltage(chemistry))
62+
63+
64+
if __name__ == '__main__':
65+
unittest.main()

unittests/common_arguments_test.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env python3
2+
3+
'''
4+
This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator
5+
6+
SPDX-FileCopyrightText: 2024 Amilcar do Carmo Lucas <[email protected]>
7+
8+
SPDX-License-Identifier: GPL-3.0-or-later
9+
'''
10+
11+
import unittest
12+
from unittest.mock import MagicMock
13+
from argparse import ArgumentParser
14+
15+
from MethodicConfigurator import common_arguments
16+
17+
18+
class TestCommonArguments(unittest.TestCase): # pylint: disable=missing-class-docstring
19+
20+
def test_add_common_arguments_and_parse_loglevel(self):
21+
# Test that loglevel choices are added correctly
22+
parser = ArgumentParser()
23+
parser.parse_args = MagicMock(return_value=MagicMock(loglevel='INFO'))
24+
25+
updated_parser = common_arguments.add_common_arguments_and_parse(parser)
26+
27+
# This will raise an error if loglevel is not an argument
28+
# or if the choices are not set up correctly.
29+
updated_parser.parse_args(['--loglevel', 'INFO'])
30+
updated_parser.parse_args.assert_called_with(['--loglevel', 'INFO'])
31+
32+
def test_version_argument(self):
33+
# Test that version argument displays correct version
34+
parser = ArgumentParser()
35+
# Mock the parse_args to just print the version string
36+
parser.parse_args = MagicMock()
37+
common_arguments.VERSION = "1.0.0"
38+
updated_parser = common_arguments.add_common_arguments_and_parse(parser)
39+
40+
# We assume the call to parse_args with --version should print the version
41+
# Since we cannot capture stdout here easily, we'll just confirm the method was called with --version
42+
updated_parser.parse_args(['--version'])
43+
updated_parser.parse_args.assert_called_with(['--version'])
44+
45+
if __name__ == '__main__':
46+
unittest.main()

unittests/extract_param_defaults_test.sh

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,23 @@
22
#
33
# SPDX-FileCopyrightText: 2024 Amilcar do Carmo Lucas <[email protected]>
44
#
5-
#SPDX-License-Identifier: GPL-3.0-or-later
5+
# SPDX-License-Identifier: GPL-3.0-or-later
66

7-
PYTHONPATH=../MethodicConfigurator python3 -m coverage run -m unittest extract_param_defaults_test.py
8-
python3 -m coverage html
7+
REQUIRED_PKGS=("coverage" "mock")
8+
9+
is_installed() {
10+
pip show "$1" > /dev/null 2>&1
11+
}
12+
13+
for pkg in "${REQUIRED_PKGS[@]}"; do
14+
if ! is_installed "$pkg"; then
15+
echo "Installing $pkg..."
16+
pip install "$pkg"
17+
else
18+
echo "$pkg is already installed."
19+
fi
20+
done
21+
22+
PYTHONPATH=../MethodicConfigurator python -m coverage run -m unittest extract_param_defaults_test.py
23+
python -m coverage html
924
firefox htmlcov/extract_param_defaults_py.html

unittests/frontend_tkinter_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from MethodicConfigurator.frontend_tkinter_base import show_tooltip
1818

1919

20-
class TestShowErrorMessage(unittest.TestCase):
20+
class TestShowErrorMessage(unittest.TestCase): # pylint: disable=missing-class-docstring
2121
@patch('tkinter.messagebox.showerror')
2222
@patch('tkinter.Tk')
2323
@patch('tkinter.ttk.Style') # Mock the ttk.Style class
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#!/usr/bin/env python3
2+
3+
'''
4+
This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator
5+
6+
SPDX-FileCopyrightText: 2024 Amilcar do Carmo Lucas <[email protected]>
7+
8+
SPDX-License-Identifier: GPL-3.0-or-later
9+
'''
10+
11+
import unittest
12+
from MethodicConfigurator.middleware_template_overview import TemplateOverview
13+
14+
15+
class TestTemplateOverview(unittest.TestCase): # pylint: disable=missing-class-docstring
16+
17+
def setUp(self):
18+
# Define sample data to be used in tests
19+
self.sample_data = {
20+
'Flight Controller': {
21+
'Product': {
22+
'Manufacturer': 'ArduPilot',
23+
'Model': 'Pixhawk4'
24+
}
25+
},
26+
'Frame': {
27+
'Specifications': {
28+
'TOW max Kg': '5'
29+
}
30+
},
31+
# ... add other components as per your structure
32+
}
33+
34+
def test_template_overview_initialization(self):
35+
# Initialize the TemplateOverview with sample data
36+
template_overview = TemplateOverview(self.sample_data)
37+
38+
# Check if attributes are set correctly
39+
self.assertEqual(template_overview.fc_manufacturer, 'ArduPilot')
40+
self.assertEqual(template_overview.fc_model, 'Pixhawk4')
41+
self.assertEqual(template_overview.tow_max_kg, '5')
42+
# .. similarly test other attributes
43+
44+
def test_template_overview_column_labels(self):
45+
# Check if the column labels match the required order
46+
# pylint: disable=duplicate-code
47+
expected_columns = (
48+
"FC\nManufacturer",
49+
"FC\nModel",
50+
"TOW Max\n[KG]",
51+
"Prop Diameter\n[inches]",
52+
"RC\nProtocol",
53+
"Telemetry\nModel",
54+
"ESC\nProtocol",
55+
"GNSS\nModel",
56+
"GNSS\nConnection",
57+
)
58+
# pylint: enable=duplicate-code
59+
self.assertEqual(TemplateOverview.columns(), expected_columns)
60+
61+
def test_template_overview_attributes_method(self):
62+
# Initialize the TemplateOverview with the sample data
63+
template_overview = TemplateOverview(self.sample_data)
64+
65+
# Fetch the instance attribute keys
66+
attribute_keys = template_overview.attributes()
67+
68+
# Check if the attribute keys match the expected set of attributes
69+
expected_attributes = {
70+
'fc_manufacturer',
71+
'fc_model',
72+
'tow_max_kg',
73+
'prop_diameter_inches',
74+
'rc_protocol',
75+
'telemetry_model',
76+
'esc_protocol',
77+
'gnss_model',
78+
'gnss_connection',
79+
}
80+
self.assertEqual(expected_attributes, set(attribute_keys))
81+
82+
if __name__ == '__main__':
83+
unittest.main()

unittests/param_pid_adjustment_update_test.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -243,15 +243,15 @@ def test_all_parameters_present(self):
243243
update_pid_adjustment_params(self.test_dir, os.path.basename(self.optimized_param_file), 0.5)
244244

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

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

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

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

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

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

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

301301
def test_zero_default_value(self):
302302
# Set a parameter in the default parameter file to zero

0 commit comments

Comments
 (0)