Skip to content

Commit 8d8e574

Browse files
eblanco-ansyspyansys-ci-botpre-commit-ci[bot]
authored
DOCS: Add information about coverage and local_config (#6681)
Co-authored-by: pyansys-ci-bot <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 24e7c77 commit 8d8e574

File tree

7 files changed

+187
-38
lines changed

7 files changed

+187
-38
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add information about coverage and local_config

doc/source/Getting_started/Contributing.rst

Lines changed: 179 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -295,18 +295,18 @@ is an example of how to create a unit test for your extension:
295295

296296
.. code-block:: python
297297
298-
from unittest.mock import patch
299-
from ansys.aedt.core.extensions.project.my_extension import MyExtension, MyExtensionData
298+
from unittest.mock import patch
299+
from ansys.aedt.core.extensions.project.my_extension import MyExtension, MyExtensionData
300300
301-
@patch("ansys.aedt.core.extensions.misc.Desktop")
302-
def test_my_extension(mock_desktop):
303-
extension = MyExtension()
301+
@patch("ansys.aedt.core.extensions.misc.Desktop")
302+
def test_my_extension(mock_desktop):
303+
extension = MyExtension()
304304
305-
assert "My extension title" == extension.root.title()
306-
assert "light" == extension.root.theme
307-
assert "No active project" == extension.active_project_name
305+
assert "My extension title" == extension.root.title()
306+
assert "light" == extension.root.theme
307+
assert "No active project" == extension.active_project_name
308308
309-
extension.root.destroy()
309+
extension.root.destroy()
310310
311311
Step 3: Add system tests
312312
~~~~~~~~~~~~~~~~~~~~~~~~
@@ -318,28 +318,108 @@ behaves as expected when integrated into the AEDT environment.
318318

319319
.. code-block:: python
320320
321-
from ansys.aedt.core.extensions.project.my_extension import MyExtension, MyExtensionData
322-
from ansys.aedt.core import Hfss
323-
324-
def test_my_extension_system(add_app):
325-
326-
# Create some data in AEDT to test the extension
327-
aedt_app = add_app(application=Hfss, project_name="my_project", design_name="my_design")
328-
aedt_app["p1"] = "100mm"
329-
aedt_app["p2"] = "71mm"
330-
test_points = [["0mm", "p1", "0mm"], ["-p1", "0mm", "0mm"], ["-p1/2", "-p1/2", "0mm"], ["0mm", "0mm", "0mm"]]
331-
p = aedt_app.modeler.create_polyline(
332-
points=test_points, segment_type=PolylineSegment("Spline", num_points=4), name="spline_4pt"
333-
)
334-
335-
# Create the extension and set its data by clicking on the "Generate" button
336-
extension = MyExtension()
337-
extension.root.nametowidget("generate").invoke()
338-
339-
# Check that the extension logic executes correctly
340-
assert 2 == len(aedt_app.variable_manager.variables)
341-
assert main(extension.data)
342-
assert 7 == len(aedt_app.variable_manager.variables)
321+
from ansys.aedt.core.extensions.project.my_extension import MyExtension, MyExtensionData
322+
from ansys.aedt.core import Hfss
323+
324+
def test_my_extension_system(add_app):
325+
326+
# Create some data in AEDT to test the extension
327+
aedt_app = add_app(application=Hfss, project_name="my_project", design_name="my_design")
328+
aedt_app["p1"] = "100mm"
329+
aedt_app["p2"] = "71mm"
330+
test_points = [["0mm", "p1", "0mm"], ["-p1", "0mm", "0mm"], ["-p1/2", "-p1/2", "0mm"], ["0mm", "0mm", "0mm"]]
331+
p = aedt_app.modeler.create_polyline(
332+
points=test_points, segment_type=PolylineSegment("Spline", num_points=4), name="spline_4pt"
333+
)
334+
335+
# Create the extension and set its data by clicking on the "Generate" button
336+
extension = MyExtension()
337+
extension.root.nametowidget("generate").invoke()
338+
339+
# Check that the extension logic executes correctly
340+
assert 2 == len(aedt_app.variable_manager.variables)
341+
assert main(extension.data)
342+
assert 7 == len(aedt_app.variable_manager.variables)
343+
344+
Run tests in VSCode and PyCharm
345+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
346+
347+
This section explains how to run pytest unit and system tests in VSCode (Visual Studio Code) and PyCharm, and how to estimate coverage
348+
using the "Run with coverage" feature or pytest-cov.
349+
350+
Prerequisites
351+
~~~~~~~~~~~~~
352+
- Install pytest and pytest-cov in your environment if you haven't already:
353+
354+
.. code:: bash
355+
356+
pip install pytest pytest-cov
357+
358+
- Ensure your IDE is configured to use the Python interpreter where the packages are installed.
359+
360+
VSCode IDE
361+
~~~~~~~~~~~~~~~~~~~~~~~
362+
363+
1. Use the Test Explorer (Python extension) to discover and run tests:
364+
- Open the Testing side bar (beaker icon).
365+
- Run or debug individual tests, test files, or test suites from the UI.
366+
367+
.. image:: ../Resources/vscode_run_tests.png
368+
:alt: VSCode Test Explorer (placeholder)
369+
370+
2. Run tests and view coverage using the GUI
371+
- VSCode:
372+
- Click the "Run Test with Coverage" button in the Test Explorer toolbar to run one or more tests with coverage.
373+
- After the tests complete, a coverage summary appears in the Test Explorer (coverage % by file), and covered/uncovered lines are highlighted in the editor.
374+
375+
.. image:: ../Resources/coverage_vscode.png
376+
:alt: VSCode Test Explorer (placeholder)
377+
378+
Brief note
379+
~~~~~~~~~~
380+
You can also run tests with coverage from a terminal in VSCode using pytest-cov:
381+
.. code:: bash
382+
383+
pytest tests/unit --cov=src --cov-report=term-missing --cov-report=html
384+
385+
This is an example command to run unit tests with coverage. Adjust the path to your test files as needed.
386+
387+
PyCharm IDE
388+
~~~~~~~~~~~~~~~~~~~
389+
390+
1. Configure pytest as the test runner:
391+
- Settings -> Tools -> Python Integrated Tools -> Default test runner -> pytest
392+
393+
2. Run tests from the IDE:
394+
- Right-click a test file, folder, or test function and choose Run or Debug.
395+
- Use the dedicated test runner UI to run and inspect results.
396+
397+
.. image:: ../Resources/pycharm_run_tests.png
398+
:alt: PyCharm test runner (placeholder)
399+
400+
3. Run with coverage in PyCharm:
401+
- Right-click a test file or configuration and choose "Run 'pytest in' with Coverage".
402+
- PyCharm shows a coverage summary and highlights covered and uncovered lines in the editor.
403+
404+
4. Run with pytest-cov from a terminal in PyCharm (alternative):
405+
406+
.. code:: bash
407+
408+
pytest tests/unit --cov=src --cov-report=term-missing --cov-report=html
409+
410+
Interpreting coverage results
411+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
412+
413+
- Coverage percentage shows the portion of executed lines compared to total executable lines in the source files.
414+
- Higher coverage is generally better, but 100% coverage does not guarantee bug-free code. At least 85% coverage for all the new code added to the repository is required.
415+
- Use ``--cov-report=term-missing`` to see which lines are not covered directly in the terminal.
416+
- Use ``--cov-report=html`` and open ``htmlcov/index.html`` in a browser for an easy-to-navigate, per-file coverage report.
417+
- Run unit and system tests separately to estimate their individual contributions:
418+
419+
Best practices
420+
~~~~~~~~~~~~~~
421+
- Aim to keep unit tests fast and isolated; use mocks for external systems like AEDT.
422+
- Use the IDE "Run with coverage" feature for quick, visual feedback.
343423

344424
Step 4: Add the extension to the catalog
345425
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -373,3 +453,71 @@ Also, another card should be added to the
373453
``doc/source/User_guide/pyaedt_extensions_doc/project/index.rst`` file to link to the extension's documentation page.
374454
This ensures that the extension is discoverable in the documentation from the multiple pages that list all the
375455
extensions available in PyAEDT.
456+
457+
458+
Local testing parameters
459+
------------------------
460+
461+
Two configuration files control test behavior:
462+
463+
- ``tests/local_config.json``: Contains parameters intended for modification during local testing.
464+
These settings control test execution behavior such as desktop version, graphical mode, and feature flags.
465+
**This file does not exist by default and must be created manually** in the ``tests/`` directory.
466+
467+
- ``tests/pyaedt_settings.yaml``: Contains default PyAEDT settings applied to all tests.
468+
These settings are not intended for local modification.
469+
470+
Creating local_config.json
471+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
472+
473+
To customize local test execution, create a ``local_config.json`` file in the ``tests/`` directory
474+
at the top level of the repository. Below is an example configuration with descriptions of each parameter:
475+
476+
.. code-block:: json
477+
478+
{
479+
"desktopVersion": "2025.2",
480+
"NonGraphical": true,
481+
"NewThread": true,
482+
"skip_circuits": false,
483+
"use_grpc": true,
484+
"close_desktop": true,
485+
"use_local_example_data": false,
486+
"local_example_folder": "",
487+
"skip_modelithics": true
488+
}
489+
490+
Parameter descriptions:
491+
492+
- ``desktopVersion``: AEDT version to use for testing (for example, "2025.2," "2024.1").
493+
- ``NonGraphical``: When ``true``, runs AEDT in non-graphical mode (headless).
494+
- ``NewThread``: Opens AEDT in a new thread.
495+
- ``skip_circuits``: When ``true``, skips Circuit-related tests.
496+
- ``use_grpc``: When ``true``, uses gRPC API for communication with AEDT.
497+
- ``close_desktop``: When ``true``, closes AEDT after tests complete.
498+
- ``use_local_example_data``: When ``true``, uses local example data for tests.
499+
- ``local_example_folder``: Path to the local example data folder.
500+
- ``skip_modelithics``: When ``true``, skips Modelithics-related tests.
501+
502+
Replicating CI/CD environment
503+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
504+
505+
The CI/CD pipeline loads ``pyaedt_settings.yaml`` via the environment variable:
506+
507+
.. code:: bash
508+
509+
PYAEDT_LOCAL_SETTINGS_PATH='tests/pyaedt_settings.yaml'
510+
511+
To replicate the CI/CD environment locally, set this environment variable on your machine:
512+
513+
- **Windows (PowerShell)**:
514+
515+
.. code:: powershell
516+
517+
$env:PYAEDT_LOCAL_SETTINGS_PATH='tests/pyaedt_settings.yaml'
518+
519+
- **Linux (Bash)**:
520+
521+
.. code:: bash
522+
523+
export PYAEDT_LOCAL_SETTINGS_PATH='tests/pyaedt_settings.yaml'
54.3 KB
Loading
318 KB
Loading
24.9 KB
Loading

doc/styles/config/vocabularies/ANSYS/accept.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ PyAEDT
8686
Python
8787
Python.NET
8888
pyvista
89+
[Pp]y[Tt]est
90+
[Pp]y[Cc]harm
91+
[Vv]s[Cc]ode
8992
Q2D Extractor
9093
Q3D
9194
Q3D Extractor
@@ -121,6 +124,7 @@ jupyter
121124
toml
122125
Parasolid
123126
i.e.
127+
[I][Dd][Ee]
124128
solderballs
125129
pin_mapping
126130
refdes

tests/conftest.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@
5757
"NewThread": True,
5858
"use_grpc": True,
5959
"close_desktop": True,
60-
"remove_lock": False,
61-
"disable_sat_bounding_box": True,
6260
"use_local_example_data": False,
6361
"local_example_folder": None,
6462
"skip_circuits": False,
@@ -84,13 +82,11 @@
8482
new_thread = config.get("NewThread", DEFAULT_CONFIG.get("NewThread"))
8583
settings.use_grpc_api = config.get("use_grpc", DEFAULT_CONFIG.get("use_grpc"))
8684
close_desktop = config.get("close_desktop", DEFAULT_CONFIG.get("close_desktop"))
87-
remove_lock = config.get("remove_lock", DEFAULT_CONFIG.get("remove_lock"))
88-
settings.disable_bounding_box_sat = config.get(
89-
"disable_sat_bounding_box", DEFAULT_CONFIG.get("disable_sat_bounding_box")
90-
)
9185
settings.use_local_example_data = config.get("use_local_example_data", DEFAULT_CONFIG.get("use_local_example_data"))
9286
if settings.use_local_example_data:
93-
settings.local_example_folder = config.get("local_example_folder", DEFAULT_CONFIG.get("local_example_folder"))
87+
local_example_folder = config.get("local_example_folder", DEFAULT_CONFIG.get("local_example_folder"))
88+
if local_example_folder: # If empty string or None, keep it as is
89+
settings.local_example_folder = local_example_folder
9490

9591
logger = pyaedt_logger
9692
os.environ["PYAEDT_SCRIPT_VERSION"] = config.get("desktopVersion", DEFAULT_CONFIG.get("desktopVersion"))

0 commit comments

Comments
 (0)