Skip to content

Commit

Permalink
IMPROVEMENT: Add more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
amilcarlucas committed Jan 16, 2025
1 parent 08746e4 commit 5c63a0e
Show file tree
Hide file tree
Showing 6 changed files with 330 additions and 36 deletions.
7 changes: 6 additions & 1 deletion .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,13 @@ jobs:
run: |
cd tests
python -m coverage run -m unittest test_annotate_params.py
python -m coverage run -m unittest test_ardupilot_methodic_configurator.py
python -m coverage run -m unittest test_argparse_check_range.py
python -m coverage run -m unittest test_backend_filesystem.py
python -m coverage run -m unittest test_battery_cell_voltages.py
python -m coverage run -m unittest test_common_arguments.py
python -m coverage run -m unittest test_extract_param_defaults.py
python -m coverage run -m unittest test_internationalization.py
python -m coverage run -m unittest test_middleware_template_overview.py
python -m coverage run -m unittest test_param_pid_adjustment_update.py
python -m coverage run -m unittest version_test.py
python -m coverage html
67 changes: 67 additions & 0 deletions tests/test__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env python3

"""
Tests for the __main__.py file.
This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator
SPDX-FileCopyrightText: 2024 Amilcar do Carmo Lucas <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
"""

import argparse
import unittest
from unittest.mock import MagicMock, patch

from ardupilot_methodic_configurator.__main__ import argument_parser, component_editor, connect_to_fc_and_read_parameters


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) -> None:
args = argument_parser()
assert args.conn == "tcp:127.0.0.1:5760"
assert args.params == "params_dir"


class TestMainFunctions(unittest.TestCase):
@patch("ardupilot_methodic_configurator.__main__.FlightController")
def test_connect_to_fc_and_read_parameters(self, mock_flight_controller) -> None:
mock_fc = mock_flight_controller.return_value
mock_fc.connect.return_value = ""
mock_fc.info.vehicle_type = "quad"
mock_fc.info.flight_sw_version_and_type = "v1.0"
mock_fc.info.vendor = "vendor"
mock_fc.info.firmware_type = "type"

args = argparse.Namespace(device="test_device", vehicle_type="", reboot_time=5)
flight_controller, vehicle_type = connect_to_fc_and_read_parameters(args)
assert flight_controller == mock_fc
assert vehicle_type == "quad"

@patch("ardupilot_methodic_configurator.__main__.ComponentEditorWindow")
@patch("ardupilot_methodic_configurator.__main__.LocalFilesystem")
@patch("ardupilot_methodic_configurator.__main__.ProgramSettings")
def test_component_editor(self, mock_program_settings, mock_local_filesystem, mock_component_editor_window) -> None:
mock_program_settings = mock_program_settings.return_value
mock_program_settings.get_setting.return_value = True

mock_local_filesystem = mock_local_filesystem.return_value
mock_local_filesystem.doc_dict = {}

mock_fc = MagicMock()
mock_fc.fc_parameters = {"param1": "value1"}
mock_fc.info.flight_sw_version_and_type = "v1.0"
mock_fc.info.vendor = "vendor"
mock_fc.info.firmware_type = "type"

args = argparse.Namespace(skip_component_editor=False)
component_editor(args, mock_fc, "quad", mock_local_filesystem, None)
mock_component_editor_window.assert_called_once()


if __name__ == "__main__":
unittest.main()
35 changes: 0 additions & 35 deletions tests/test_ardupilot_methodic_configurator.py

This file was deleted.

91 changes: 91 additions & 0 deletions tests/test_argparse_check_range.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#!/usr/bin/python3

"""
Tests for the argparse_check_range.py file.
This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator
SPDX-FileCopyrightText: 2024-2025 Amilcar do Carmo Lucas <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
"""

import unittest
from argparse import ArgumentParser

import pytest

from ardupilot_methodic_configurator.argparse_check_range import CheckRange


class TestCheckRange(unittest.TestCase):
def setUp(self) -> None:
self.parser = ArgumentParser()

def test_init_with_both_min_and_inf(self) -> None:
with pytest.raises(ValueError, match="either min or inf, but not both"):
self.parser.add_argument("--test", action=CheckRange, min=0, inf=0)

def test_init_with_both_max_and_sup(self) -> None:
with pytest.raises(ValueError, match="either max or sup, but not both"):
self.parser.add_argument("--test", action=CheckRange, max=10, sup=10)

def test_interval_with_min_and_max(self) -> None:
action = CheckRange(option_strings=["--test"], dest="test", min=0, max=10)
assert action.interval() == "valid range: [0, 10]"

def test_interval_with_inf_and_sup(self) -> None:
action = CheckRange(option_strings=["--test"], dest="test", inf=0, sup=10)
assert action.interval() == "valid range: (0, 10)"

def test_interval_with_no_bounds(self) -> None:
action = CheckRange(option_strings=["--test"], dest="test")
assert action.interval() == "valid range: (-infinity, +infinity)"

def test_call_with_non_number_value(self) -> None:
self.parser.add_argument("--test", action=CheckRange, min=0, max=10)
with pytest.raises(SystemExit) as excinfo:
self.parser.parse_args(["--test", "non-number"])
assert str(excinfo.value) == "2"

def test_call_with_value_out_of_range(self) -> None:
self.parser.add_argument("--test", action=CheckRange, min=0, max=10)
with pytest.raises(SystemExit) as excinfo:
self.parser.parse_args(["--test", "11"])
assert str(excinfo.value) == "2"

def test_call_with_value_in_range(self) -> None:
self.parser.add_argument("--test", action=CheckRange, min=0, max=10)
args = self.parser.parse_args(["--test", "5"])
assert args.test == 5

def test_call_with_value_equal_to_min(self) -> None:
self.parser.add_argument("--test", action=CheckRange, min=0, max=10)
args = self.parser.parse_args(["--test", "0"])
assert args.test == 0

def test_call_with_value_equal_to_max(self) -> None:
self.parser.add_argument("--test", action=CheckRange, min=0, max=10)
args = self.parser.parse_args(["--test", "10"])
assert args.test == 10

def test_call_with_value_equal_to_inf(self) -> None:
self.parser.add_argument("--test", action=CheckRange, inf=0, sup=10)
with pytest.raises(SystemExit) as excinfo:
self.parser.parse_args(["--test", "0"])
assert str(excinfo.value) == "2"

def test_call_with_value_equal_to_sup(self) -> None:
self.parser.add_argument("--test", action=CheckRange, inf=0, sup=10)
with pytest.raises(SystemExit) as excinfo:
self.parser.parse_args(["--test", "10"])
assert str(excinfo.value) == "2"

def test_call_with_negative_value(self) -> None:
self.parser.add_argument("--test", action=CheckRange, min=-10, max=10)
args = self.parser.parse_args(["--test", "-5"])
assert args.test == -5


if __name__ == "__main__":
unittest.main()
118 changes: 118 additions & 0 deletions tests/test_frontend_tkinter_template_overview.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#!/usr/bin/python3

"""
Tests for the frontend_tkinter_template_overview.py file.
This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator
SPDX-FileCopyrightText: 2024-2025 Amilcar do Carmo Lucas <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
"""

import argparse
import tkinter as tk

import pytest

from ardupilot_methodic_configurator.backend_filesystem_program_settings import ProgramSettings
from ardupilot_methodic_configurator.frontend_tkinter_template_overview import TemplateOverviewWindow, argument_parser, main


@pytest.fixture
def root() -> tk.Tk:
root = tk.Tk()
yield root
try:
if root.winfo_exists():
root.destroy()
except tk.TclError:
pass


def test_template_overview_window_init(root: tk.Tk) -> None:
window = TemplateOverviewWindow(root=root)
root.update_idletasks()
root.update()
assert (
window.root.title()
== f"Amilcar Lucas's - ArduPilot methodic configurator {window.__version__} - Template Overview and selection"
)
assert window.root.geometry() == "1200x600"
window.root.destroy()
root.destroy() # Ensure the root window is closed


def test_adjust_treeview_column_widths(root) -> None:
window = TemplateOverviewWindow(root=root)
root.update_idletasks()
root.update()
window._adjust_treeview_column_widths() # noqa: SLF001
for col in window.tree["columns"]:
assert window.tree.column(col, option="width") > 0
window.root.destroy()
root.destroy() # Ensure the root window is closed


def test_on_row_selection_change(root) -> None:
window = TemplateOverviewWindow(root=root)
root.update_idletasks()
root.update()
window.tree.insert("", "end", text="test_path", values=("test_value",))
window.tree.selection_set(window.tree.get_children()[0])
window._on_row_selection_change(None) # noqa: SLF001
assert ProgramSettings.get_recently_used_dirs()[0] == "test_path"
window.root.destroy()
root.destroy() # Ensure the root window is closed


def test_on_row_double_click(root) -> None:
window = TemplateOverviewWindow(root=root)
root.update_idletasks()
root.update()
window.tree.insert("", "end", text="test_path", values=("test_value",))
event = tk.Event()
event.y = 0
window._on_row_double_click(event) # noqa: SLF001
assert ProgramSettings.get_recently_used_dirs()[0] == "test_path"
window.root.destroy()
root.destroy() # Ensure the root window is closed


def test_sort_by_column(root) -> None:
window = TemplateOverviewWindow(root=root)
root.update_idletasks()
root.update()
window.tree.insert("", "end", text="test_path", values=("1",))
window.tree.insert("", "end", text="test_path2", values=("2",))
window._sort_by_column("test_value", reverse=False) # noqa: SLF001
assert window.tree.get_children()[0] == "test_path"
window.root.destroy()
root.destroy() # Ensure the root window is closed


def test_display_vehicle_image(root) -> None:
window = TemplateOverviewWindow(root=root)
root.update_idletasks()
root.update()
window._display_vehicle_image("test_path") # noqa: SLF001
assert window.image_label.cget("text") == "No 'vehicle.jpg' image file in the vehicle directory."
window.root.destroy()
root.destroy() # Ensure the root window is closed


def test_argument_parser(mocker) -> None:
mocker.patch("sys.exit") # Mock sys.exit to prevent the test from exiting
args = argument_parser()
assert args is not None


def test_main(mocker) -> None:
mock_template_overview_window = mocker.Mock()
mocker.patch("argparse.ArgumentParser.parse_args", return_value=argparse.Namespace(loglevel="DEBUG"))
mocker.patch(
"ardupilot_methodic_configurator.frontend_tkinter_template_overview.TemplateOverviewWindow",
mock_template_overview_window,
)
main()
assert mock_template_overview_window.called
48 changes: 48 additions & 0 deletions tests/test_internationalization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/python3

"""
Tests for the internationalization.py file.
This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator
SPDX-FileCopyrightText: 2024-2025 Amilcar do Carmo Lucas <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
"""

import unittest

from ardupilot_methodic_configurator.internationalization import LANGUAGE_CHOICES, identity_function, load_translation


class TestInternationalization(unittest.TestCase):
def test_default_language_is_english(self) -> None:
assert LANGUAGE_CHOICES[0] == "en"

def test_load_translation_default(self) -> None:
translation_function = load_translation()
assert translation_function == identity_function

def test_identity_function(self) -> None:
test_string = "test"
assert identity_function(test_string) == test_string

def test_load_translation_with_language(self) -> None:
translation_function = load_translation("de")
assert translation_function != identity_function

def test_load_translation_with_invalid_language(self) -> None:
translation_function = load_translation("invalid")
assert translation_function == identity_function

def test_load_translation_fallback(self) -> None:
translation_function = load_translation("zh_CN")
assert translation_function != identity_function

def test_language_choices(self) -> None:
expected_languages = ["en", "zh_CN", "pt", "de"]
assert expected_languages == LANGUAGE_CHOICES


if __name__ == "__main__":
unittest.main()

0 comments on commit 5c63a0e

Please sign in to comment.