Example — Integrating ModuleTester into your project ===================================================== This guide walks you through integrating ModuleTester into an existing Python project, step by step. It uses the **Example Calculator** shipped in the ``example/`` directory of the ModuleTester repository as a reference implementation. By the end, you will have a fully functional ModuleTester setup with manual GUI tests, automated unit tests with coverage, and qualification scripts. .. contents:: Steps :local: :depth: 1 Overview of the example project -------------------------------- The Example Calculator is a minimal Python package with a Qt GUI that demonstrates the three test categories ModuleTester supports: - **Manual GUI Tests** — launch the application and follow step-by-step instructions displayed in ModuleTester. - **Unit Tests** — wrapper scripts that run ``pytest`` with ``coverage`` and generate HTML coverage reports. - **Qualification Tests** — standalone scripts that verify numerical precision and performance against reference values. .. code-block:: text example/ ├── pyproject.toml ├── README.md └── example_calculator/ ├── __init__.py ├── app.py # Qt GUI (QMainWindow) ├── operations.py # Arithmetic functions ├── converter.py # Unit conversion functions ├── moduletester.ini # ModuleTester configuration └── tests/ ├── __init__.py ├── moduletester_launcher.py # Launches ModuleTester GUI ├── templates/ # Export templates and assets ├── processing/ # Actual pytest test files │ ├── test_operations.py │ └── test_converter.py └── Test Plan/ ├── Manual GUI Tests/ │ ├── test-001.py │ ├── test-002.py │ └── test-003.py ├── Unit Tests/ │ ├── 001-operations.py │ └── 002-converter.py └── Qualification Tests/ ├── 001-precision.py └── 002-performance.py Step 1 — Make your package importable -------------------------------------- ModuleTester discovers tests by importing your package and scanning its sub-modules. Your package must be importable from the Python environment where ModuleTester runs. For the example project, install it in editable mode: .. code-block:: console $ cd example $ pip install -e ".[test]" .. tip:: If your project is already installed in your environment (via ``pip install -e .`` or similar), you can skip this step. Step 2 — Organise your tests ------------------------------ Create a ``tests/`` sub-package inside your main package. ModuleTester scans this sub-package recursively and groups tests by directory. Use the ``# guitest:`` directive at the top of each Python file to control how ModuleTester treats it: .. list-table:: :header-rows: 1 :widths: 25 75 * - Directive - Effect * - ``# guitest: show`` - The script appears in the ModuleTester tree view. Use this for all test files that should be visible to testers. * - ``# guitest: skip`` - The script is completely ignored during discovery. Use this for utility modules, ``pytest`` files, and launchers. * - ``# guitest: hide`` - The script is discovered but hidden from the default "visible" category. Useful for batch-only tests. In the example, the directory structure under ``Test Plan/`` determines how tests are grouped in the tree view. You are free to choose any directory names — ModuleTester uses them as-is. Step 3 — Write manual GUI tests --------------------------------- Manual GUI tests launch your application and display step-by-step instructions to the tester. ModuleTester renders the module docstring as HTML, so write it in reStructuredText with a ``.. list-table::`` describing actions and expected results. Here is an annotated example from the calculator project: .. code-block:: python """ Example Calculator — Manual GUI Test TEST-001: Application startup This test verifies that the application starts correctly. .. list-table:: Test steps :header-rows: 1 :widths: 50 50 * - Action - Expected result * - Launch the application. - The main window appears with the title "Example Calculator". * - Verify that both tabs ("Operations" and "Converter") are present. - Both tabs are visible and selectable. * - Close the application. - The application closes without errors. """ # guitest: show import example_calculator.app as app if __name__ == "__main__": app.run() Key points: - The docstring must come **before** the ``# guitest: show`` directive. - The ``if __name__ == "__main__":`` guard is required — ModuleTester executes each test in a subprocess. - Your application must expose a ``run()`` function (or equivalent) that starts the Qt event loop. Step 4 — Write unit test wrappers with coverage ------------------------------------------------- You could add ``# guitest: show`` and a ``if __name__`` block directly to your existing ``pytest`` files, but a cleaner approach is to keep them untouched and create thin **wrapper scripts** instead. This way your test files remain standard ``pytest`` modules — the only change is adding ``# guitest: skip`` so ModuleTester ignores them during discovery. The wrappers call ``pytest.main()`` and optionally collect code coverage. Wrapper example (``Unit Tests/001-operations.py``): .. code-block:: python """ UT-001: Arithmetic operations (pytest + coverage) .. list-table:: Test steps :header-rows: 1 :widths: 50 50 * - Action - Expected result * - Launch the test script. - Unit test results and a coverage report are generated. """ # guitest: show import os from datetime import datetime import coverage import pytest if __name__ == "__main__": current_dir = os.path.dirname(os.path.abspath(__file__)) # Navigate to the project root project_root = os.path.dirname( os.path.dirname(os.path.dirname(os.path.dirname(current_dir))) ) test_dir = os.path.join(project_root, "example_calculator", "tests") cov = coverage.Coverage( include=["*/example_calculator/operations.py"], ) cov.start() pytest.main([ os.path.join(test_dir, "processing", "test_operations.py"), "-v", ]) cov.stop() cov.save() cov.report(show_missing=False) cov.html_report(directory=os.path.join( project_root, "TestPlan", "reports", datetime.now().strftime("%Y-%m-%d"), "operations", )) The actual ``pytest`` file (``processing/test_operations.py``) must start with ``# guitest: skip`` to stay hidden from ModuleTester: .. code-block:: python # guitest: skip import pytest from example_calculator.operations import add, divide class TestAdd: def test_positive_numbers(self): assert add(2, 3) == 5 def test_negative_numbers(self): assert add(-1, -2) == -3 class TestDivide: def test_divide_by_zero(self): with pytest.raises(ZeroDivisionError): divide(1, 0) Step 5 — Write qualification scripts -------------------------------------- Qualification tests are standalone scripts that run computations, compare results against reference values, and generate reports. They are useful for performance benchmarks, numerical accuracy checks, or any test that does not fit the ``pytest`` model. Because ModuleTester executes every test as a **subprocess**, it is entirely agnostic to what the script does internally. This makes it straightforward to integrate any custom test script your project already has — numerical simulations, hardware-in-the-loop checks, data-processing pipelines, etc. — without modifying them beyond adding the ``# guitest: show`` directive and a docstring. This approach even extends to **non-Python projects** (C++, C#, web applications, …): the wrapper script just needs to call the external tool via ``subprocess.run()`` or equivalent. The only requirement is that the wrapper itself is a ``.py`` file so ModuleTester can discover it. Example (``Qualification Tests/001-precision.py``): .. code-block:: python """ QUAL-001: Arithmetic precision verification .. list-table:: Test steps :header-rows: 1 :widths: 50 50 * - Action - Expected result * - Launch the qualification script. - The script displays results and saves a report. """ # guitest: show import os from example_calculator import operations REFERENCE_DATA = [ ("add(0.1, 0.2)", operations.add, (0.1, 0.2), 0.3, 1e-15), # ... more test cases ] def run(mode="print", save_path=None): results = [] for desc, func, args, expected, tol in REFERENCE_DATA: computed = func(*args) error = abs(computed - expected) results.append((desc, computed, expected, error, error <= tol)) # ... build and save report if __name__ == "__main__": run("print_save", save_path="TestPlan/reports/precision") Step 6 — Customise export templates -------------------------------------- ModuleTester uses **Jinja2 templates** to generate reports. The default templates are shipped in ``moduletester/default_templates/`` — copy them into your project's ``tests/templates/`` directory so you can customise them. Two templates control the exported documents: - ``test_list_template.j2`` — the **test list** report (test catalogue with descriptions only, no results). - ``test_results_template.j2`` — the **test results** report (full campaign output with descriptions, statuses, comments, images, and a summary table). Both templates have access to the ``doc_obj`` context object, which exposes the full ``test_suite`` — including the package description, grouped tests, results, and execution dates. **Typical customisations:** - **Project description tracking** — edit the ``