Skip to main content

test

Write, review, or split PyMechanical tests. Use when writing pytest files under tests/ or tests/embedding/, reviewing existing tests for modularity, splitting monolithic tests, or adding test coverage for embedding, remote session, or CLI features. Covers test structure, assert style, fixture use, and pytest markers.

Source facts

Repository
ansys/pymechanical
Last source activity
August 31, 2026 at 21:08
Detected SKILL.md language
English
Stars
93
Forks
38

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
test
description
Write, review, or split PyMechanical tests. Use when writing pytest files under tests/ or tests/embedding/, reviewing existing tests for modularity, splitting monolithic tests, or adding test coverage for embedding, remote session, or CLI features. Covers test structure, assert style, fixture use, and pytest markers.
# PyMechanical Test Scripts Tests live under `tests/` (remote, CLI) and `tests/embedding/` (embedding). They use `pytest` with custom markers and fixtures defined in `tests/conftest.py`. --- ## Test Categories ### Embedding — in-process (`@pytest.mark.embedding`) Run inside the same Python process using a shared `embedded_app` fixture. Use for testing `App` methods, API behaviour, and object manipulation. ```python @pytest.mark.embedding def test_feature_name(embedded_app): """Test brief description.""" result = embedded_app.some_method() assert result == expected ``` ### Embedding — subprocess (`@pytest.mark.embedding_scripts`) Run a separate Python script via `subprocess`. Use for scenarios requiring process isolation (pythonnet warnings, global importer edge cases, gallery building). ```python @pytest.mark.embedding_scripts def test_script_behavior(run_subprocess, rootdir, tmp_path): """Test brief description.""" script = rootdir / "tests" / "scripts" / "my_script.py" result = run_subprocess(script) assert result.returncode == 0 ``` ### Remote session (`@pytest.mark.remote_session_connect`) Validate the `ansys.mechanical.core.Mechanical` gRPC client against a running Mechanical server. ```python @pytest.mark.remote_session_connect def test_feature_name(mechanical): """Test brief description.""" result = mechanical.run_python_script("2+3") assert result == "5" ``` --- ## One Behaviour Per Test Each test function must verify exactly one behaviour. If a test does more than one logical thing, split it into separate functions. **Wrong** — multiple concerns in one test: ```python def test_license_manager(embedded_app): assert len(embedded_app.license_manager.get_all_licenses()) > 0 embedded_app.license_manager.set_license_status("Ansys Mechanical Premium", False) assert embedded_app.license_manager.get_license_status(...) == ...Disabled embedded_app.license_manager.move_to_index("Ansys Mechanical Premium", 0) assert embedded_app.license_manager.get_all_licenses().index(...) == 0 ``` **Right** — one concern per function: ```python TEST_LICENSE = "Ansys Mechanical Premium" def test_get_all_licenses(embedded_app): """Test that at least one license is available.""" assert len(embedded_app.license_manager.get_all_licenses()) > 0 def test_set_license_status(embedded_app): """Test enabling and disabling a license.""" lm = embedded_app.license_manager lm.set_license_status(TEST_LICENSE, False) assert lm.get_license_status(TEST_LICENSE) == lm._license_status.Disabled lm.set_license_status(TEST_LICENSE, True) assert lm.get_license_status(TEST_LICENSE) == lm._license_status.Enabled def test_move_to_index(embedded_app): """Test moving a license to position zero.""" lm = embedded_app.license_manager original_index = lm.get_all_licenses().index(TEST_LICENSE) assert original_index > 0 lm.move_to_index(TEST_LICENSE, 0) assert lm.get_all_licenses().index(TEST_LICENSE) == 0 ``` --- ## Asserts - **One assert concept per test** where possible. Multiple asserts are fine when they form a single logical check (for example, enable → assert enabled, disable → assert disabled). - **No inline assert messages** unless the failure would be genuinely ambiguous without one. pytest already shows the values on failure. - **Never assert against hardcoded environment-specific values** (list indices, version strings, file paths). Capture the value first and assert relative to it. ```python # Wrong — hardcoded index assert license_list.index(TEST_LICENSE) == 1 # Right — relative assertion original = license_list.index(TEST_LICENSE) assert original > 0 lm.move_to_index(TEST_LICENSE, 0) assert lm.get_all_licenses().index(TEST_LICENSE) == 0 lm.reset_preference() assert lm.get_all_licenses().index(TEST_LICENSE) != 0 ``` --- ## Comments - **No inline comments** inside test bodies. The test name and docstring provide context. - **No section dividers** (`# Enable and disable specific license`, etc.). - A single one-line module docstring is sufficient for the file. - Each test function has a one-line docstring describing what it verifies. --- ## Naming - `test_<thing>_<condition>` — for example `test_session_license_invalid_type`. - Use a module-level constant for repeated literal values: ```python TEST_LICENSE = "Ansys Mechanical Premium" ``` --- ## File Structure (in order) 1. **Module docstring** — short one-line description, e.g. `"""License manager tests."""` 2. **Imports** — stdlib → pytest → `ansys.mechanical.core` modules 3. **Module-level constants** — repeated literal values 4. **Test functions** — each decorated with the appropriate marker --- ## Import Patterns ### Embedding tests ```python import os from pathlib import Path import subprocess import sys import pytest from ansys.mechanical.core.embedding.app import App from ansys.mechanical.core.embedding.background import BackgroundApp ``` ### Remote tests ```python import json import os from pathlib import Path import pytest import ansys.mechanical.core as pymechanical ``` Import only what is needed. Group: stdlib → pytest → ansys modules. --- ## Available Fixtures | Fixture | Scope | Purpose | |---------|-------|---------| | `embedded_app` | session | Shared `App` instance (embedding tests) | | `mechanical` | session | Shared remote `Mechanical` session instance | | `tmp_path` | function | Temporary directory unique to each test | | `assets` | session | Path to `tests/assets/` directory | | `rootdir` | session | Path to repository root | | `run_subprocess` | function | Helper to run scripts in isolated subprocess | | `pytestconfig` | session | Access to pytest configuration and CLI options | | `capsys` | function | Capture stdout/stderr — add only when needed | - Prefer the narrowest fixture scope that still works. - Do not call `embedded_app.new()` manually — the `make_app_reset` autouse fixture resets it. - Add `capsys` only to tests that capture stdout. --- ## Markers - `@pytest.mark.embedding` — all in-process embedding tests. - `@pytest.mark.embedding_scripts` — subprocess-isolated embedding tests. - `@pytest.mark.remote_session_connect` — remote gRPC session tests. - `@pytest.mark.minimum_version(261)` — tests requiring a minimum Mechanical version. --- ## Common Assertion Patterns ### Embedding — App repr ```python app_repr_lines = repr(embedded_app).splitlines() assert app_repr_lines[0].startswith("Ansys Mechanical") assert app_repr_lines[1].startswith("Product Version") ``` ### Embedding — save and open ```python from tempfile import NamedTemporaryFile project_file = tmp_path / f"{NamedTemporaryFile().name}.mechdat" embedded_app.save_as(str(project_file)) assert project_file.exists() ``` ### Embedding — version check ```python version = embedded_app.version assert version is not None ``` ### Embedding — tree output ```python tree_output = embedded_app.print_tree() assert "Project" in tree_output ``` ### Embedding — deprecation warning ```python with pytest.warns(UserWarning): deprecated_object.old_property ``` ### Remote — run Python script ```python result = mechanical.run_python_script("2+3") assert result == "5" ``` ### Remote — run Python script returning object ```python result = mechanical.run_python_script("ExtAPI.DataModel.Project.Name") assert result != "" ``` ### Remote — upload file ```python mechanical.upload(file_name=str(assets / "geometry.agdb"), file_location_destination=".") ``` ### Remote — download file ```python mechanical.download("file.mechdat", target_dir=str(tmp_path)) assert (tmp_path / "file.mechdat").exists() ``` ### Remote — multi-line script ```python script = """ import json model = ExtAPI.DataModel.Project.Model output = json.dumps({"name": model.Name, "bodies": model.Geometry.Bodies.Count}) output """ result = mechanical.run_python_script(script) data = json.loads(result) assert data["bodies"] > 0 ``` --- ## Background App Testing For `BackgroundApp` tests use the subprocess pattern. Helper scripts go in `tests/scripts/`. ```python @pytest.mark.embedding_scripts def test_background_app(run_subprocess, rootdir): """Test background app lifecycle.""" script = rootdir / "tests" / "scripts" / "background_app_test.py" result = run_subprocess(script) assert result.returncode == 0 assert "error" not in result.stderr.lower() ``` --- ## Pytest Custom Flags (embedding) Pass `--log-embedding=<level>` to enable the Mechanical embedding logger without changing any test code: ```bash pytest -m embedding -k test_set_license_status --ansys-version=261 --log-embedding=debug ``` Accepted levels: `debug`, `info`, `warning`, `error`. Omitting the flag leaves the embedding logger unconfigured. --- ## Anti-patterns to Avoid | Anti-pattern | Fix | |---|---| | Monolithic test covering all public methods | One test per behaviour | | `assert x == 1` where `1` is environment-specific | Capture value; assert relative | | Inline comments explaining each step | Self-explaining test name + docstring | | `assert condition, "message"` when pytest output is clear | Plain `assert condition` | | Catching broad exceptions to avoid test failure | Let the exception propagate | | Calling `embedded_app.new()` inside a test | Rely on `make_app_reset` autouse fixture |
View on GitHub