add-test

GitHub

指导为EmbodiChain模块编写测试,涵盖functors、solvers等Python组件。提供pytest和Class两种风格示例及文件位置规范,支持纯逻辑与仿真依赖场景的测试用例生成。

.agents/skills/add-test/SKILL.md DexForce/EmbodiChain

Trigger Scenarios

用户要求添加测试 为新模块编写测试

Install

npx skills add DexForce/EmbodiChain --skill add-test -g -y
More Options

Non-standard path

npx skills add https://github.com/DexForce/EmbodiChain/tree/main/.agents/skills/add-test -g -y

Use without installing

npx skills use DexForce/EmbodiChain@add-test

指定 Agent (Claude Code)

npx skills add DexForce/EmbodiChain --skill add-test -a claude-code -g -y

安装 repo 全部 skill

npx skills add DexForce/EmbodiChain --all -g -y

预览 repo 内 skill

npx skills add DexForce/EmbodiChain --list

SKILL.md

Frontmatter
{
    "name": "add-test",
    "description": "Use when writing tests for EmbodiChain modules, including observation functors, reward functors, solvers, sensors, environments, or any Python module"
}

Add Test

Write tests following EmbodiChain's conventions and patterns.

When to Use

  • User asks to "add a test", "write tests for X", "test this module"
  • A new public module or function needs test coverage
  • PR checklist requires tests

Test File Location

Tests mirror the source tree under tests/:

embodichain/lab/sim/motion/solvers/pytorch_solver.py  →  tests/sim/motion/solvers/test_pytorch_solver.py
embodichain/lab/gym/envs/managers/rewards.py    →  tests/gym/envs/managers/test_reward_functors.py
embodichain/toolkits/graspkit/pg_grasp/foo.py   →  tests/toolkits/test_pg_grasp.py
extensions/embodichain_tasks/embodichain_tasks/manipulation/push_cube.py → tests/gym/envs/tasks/test_push_cube.py

Rules:

  • File name: test_<module>.py
  • Directory path mirrors embodichain/ structure under tests/
  • Create __init__.py files in new tests/ subdirectories if needed

Two Test Styles

pytest Style — For Pure-Python Logic (No Sim)

Use when: testing functors, utility functions, pure math, config validation — anything that doesn't need a SimulationManager.

# ----------------------------------------------------------------------------
# Copyright (c) 2021-2026 DexForce Technology Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# ...
# ----------------------------------------------------------------------------

from __future__ import annotations

import pytest
import torch

from embodichain.my_module import my_function


def test_expected_output():
    result = my_function(input_value)
    assert result == expected_value


def test_edge_case():
    result = my_function(edge_input)
    assert result is not None

Class Style — For Sim-Dependent or Ordered Tests

Use when: tests need SimulationManager, GPU setup, or must run in a specific order. Share state via setup_method/teardown_method.

# ----------------------------------------------------------------------------
# Copyright (c) 2021-2026 DexForce Technology Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# ...
# ----------------------------------------------------------------------------

from __future__ import annotations

import pytest
import torch

from embodichain.lab.sim import SimulationManager, SimulationManagerCfg


class TestMySimComponent:
    def setup_method(self):
        config = SimulationManagerCfg(headless=True, device="cpu")
        self.sim = SimulationManager(config)
        # ... setup ...

    def teardown_method(self):
        self.sim.destroy()
        SimulationManager.flush_cleanup_queue()

    def test_basic_behavior(self):
        result = self.sim.do_something()
        assert result == expected_result

    def test_raises_on_bad_input(self):
        with pytest.raises(ValueError):
            self.sim.do_something(bad_input)

Resource-Aware Test Classification

Use the narrowest test type that proves the behavior:

  • Prefer mocks or CPU-only inputs for pure logic and shape validation.
  • tests/conftest.py automatically classifies conventional CUDA, renderer, and real-simulation tests. Add @pytest.mark.gpu, @pytest.mark.slow, or @pytest.mark.requires_sim explicitly only when the test's node id/source cannot reveal that requirement (for example, a hidden CUDA helper or an end-to-end toolkit test).

GPU tests are skipped by default to keep normal test runs within the shared VRAM budget. Run them explicitly and serially:

# Default suite: GPU-marked tests are skipped.
pytest tests/

# Dedicated GPU suite. Do not add -n unless pytest-xdist is installed.
pytest tests/ --run-gpu -m gpu

For backend/device matrices, run the complete contract on one representative configuration and use small (one environment, low-resolution) smoke tests for the remaining configurations. Always destroy a real SimulationManager and flush its cleanup queue in teardown.

Task Program and task-configuration tests

Choose the test surface by ownership layer:

Changed boundary Primary test location
Language schema, strict decoder, AST/compiler tests/lab/task_program/
Semantic scene/profile/call/effect contracts tests/lab/task_program/semantics/
Configured integration, catalog, simulation assembly tests/gym/envs/task_program/
Gym bridge and episode completion tests/gym/envs/task_program/, tests/gym/envs/test_embodied_env_task_program.py
Packaged task components/deployments tests/test_task_program_package_data.py, task-layout/config tests
Lightweight RL environment/trainer routing tests/learning/

For JSON/YAML configuration, test through the same strict loader and component composition used by production. A yaml.safe_load() assertion alone does not prove closed fields, relative component paths, physical scene targets, contracts, catalog coverage, or program preflight.

Add negative coverage for the earliest ownership boundary being changed: unknown fields, missing component files, duplicate inline/component ownership, absent simulation_uid, contract mismatch, unknown Semantic Calls, or missing registered lowerers. Keep live-simulation qualification separate from provider-free decode/compiler tests.

For a complete configured Task Program deployment, use $add-task-program's read-only inspector as a focused smoke check before adding a heavier environment test.

Mocking Patterns for Functor Tests

Most functor tests don't need a live simulation. Use mock objects following the pattern in tests/gym/envs/managers/test_reward_functors.py:

from unittest.mock import MagicMock, Mock


class MockSim:
    """Mock simulation for functor tests."""

    def __init__(self, num_envs: int = 4):
        self.num_envs = num_envs
        self.device = torch.device("cpu")
        self._rigid_objects: dict = {}

    def get_rigid_object(self, uid: str):
        return self._rigid_objects.get(uid)

    def add_rigid_object(self, obj):
        self._rigid_objects[obj.uid] = obj


class MockEnv:
    """Mock environment for functor tests."""

    def __init__(self, num_envs: int = 4):
        self.num_envs = num_envs
        self.device = torch.device("cpu")
        self.sim = MockSim(num_envs)

Key points for mock objects:

  • Set num_envs and device attributes (functors use these)
  • Mock only the sim methods the functor actually calls
  • Use MagicMock(uid="...") for SceneEntityCfg parameters

Steps

1. Identify What to Test

Ask the user:

  1. Which module/function? — determines file path
  2. Does it need a live simulation? — determines test style
  3. Key behaviors to verify — happy path, edge cases, error cases

2. Determine Test File Path

Map the source path to test path:

embodichain/<subpath>/<module>.py  →  tests/<subpath>/test_<module>.py

Check if the test file already exists — append new test classes/functions if so.

3. Choose Test Style

digraph test_style {
    rankdir=LR;
    "Needs SimulationManager?" -> "Class style" [label="yes"];
    "Needs SimulationManager?" -> "pytest style" [label="no"];
    "Tests share state/order?" -> "Class style" [label="yes"];
    "Tests share state/order?" -> "pytest style" [label="no"];
}

4. Write the Test

Use the appropriate template (pytest or class style above).

Rules:

  • Apache 2.0 header — required on every test file
  • from __future__ import annotations — after header, before imports
  • No magic numbers — define expected values as named constants or comment their origin
  • Test function names — test_<scenario> (descriptive, not just test_foo)
  • One assertion concept per test — don't bundle unrelated checks

5. Add if __name__ == "__main__" Block

Include this for tests that support optional visual/interactive debugging:

if __name__ == "__main__":
    # For visual debugging: set is_visual=True when calling env methods
    test_obj = TestMyComponent()
    test_obj.setup_method()
    # ... manually run test logic ...

6. Run the Test

# Single file
pytest tests/<subpath>/test_<module>.py -v

# Single test function
pytest tests/<subpath>/test_<module>.py::test_expected_output -v

# GPU-specific test
pytest tests/<subpath>/test_<module>.py --run-gpu -m gpu -v

# Single test class method
pytest tests/<subpath>/test_<module>.py::TestMyClass::test_basic_behavior -v

7. Run black

black tests/<subpath>/test_<module>.py

Conventions Summary

Convention Rule
File header Apache 2.0 copyright block (same 15 lines as source)
File naming test_<module>.py
Function naming test_<scenario>
from __future__ Required after header
Magic numbers Define as named constants with explanatory comments
Simulation tests Initialize/teardown in setup_method/teardown_method
CUDA coverage Use @pytest.mark.gpu; run with --run-gpu -m gpu
Long integration Use @pytest.mark.slow; keep it out of normal PR runs
Pure-logic tests Use mock objects, no real sim
SceneEntityCfg Use MagicMock(uid="...") in tests
Assertions assert, pytest.approx, torch.allclose, pytest.raises
Entry block if __name__ == "__main__" for visual debugging support

Common Mistakes

Mistake Fix
Missing Apache header on test file Copy the 15-line copyright block
Using real SimulationManager for functor tests Use MockEnv/MockSim — much faster, no GPU needed
Hardcoded numbers without explanation Define as EXPECTED_DISTANCE = 0.5 # cube at origin, target at (0.5, 0, 0)
Testing multiple concepts in one function Split into separate test_<scenario> functions
Forgetting cleanup Call self.sim.destroy() and SimulationManager.flush_cleanup_queue() in teardown
Using full matrices Use one full representative case and low-resource smoke coverage elsewhere
Not running black on test file CI checks all files including tests

Quick Reference

Action Command
Run default tests pytest tests/
Run GPU tests pytest tests/ --run-gpu -m gpu
Run single file pytest tests/<path>/test_<name>.py -v
Run single test pytest tests/<path>::test_<name> -v
Run with print output pytest -s tests/<path>/test_<name>.py
Format black tests/<path>/test_<name>.py

Version History

  • 6611467 Current 2026-10-19 21:36
  • 6f20b06 2026-09-23 01:29
  • 131e55b 2026-08-28 22:44
  • 12178b3 2026-08-20 11:23

Same Skill Collection

.agents/skills/add-atomic-action/SKILL.md
.agents/skills/add-embodiment-component/SKILL.md
.agents/skills/add-functor/SKILL.md
.agents/skills/add-robot/SKILL.md
.agents/skills/add-semantic-call/SKILL.md
.agents/skills/add-solver/SKILL.md
.agents/skills/add-task-env/SKILL.md
.agents/skills/add-task-program/SKILL.md
.agents/skills/benchmark/SKILL.md
.agents/skills/implement-performance/SKILL.md
.agents/skills/pr/SKILL.md
.agents/skills/pre-commit-check/SKILL.md
.agents/skills/project-dev-context/SKILL.md
.agents/skills/release/SKILL.md
.agents/skills/review-pr/SKILL.md
.agents/skills/update-api-docs/SKILL.md
.claude/skills/add-atomic-action/SKILL.md
.claude/skills/add-embodiment-component/SKILL.md
.claude/skills/add-functor/SKILL.md
.claude/skills/add-robot/SKILL.md
.claude/skills/add-semantic-call/SKILL.md
.claude/skills/add-solver/SKILL.md
.claude/skills/add-task-env/SKILL.md
.claude/skills/add-task-program/SKILL.md
.claude/skills/add-test/SKILL.md
.claude/skills/benchmark/SKILL.md
.claude/skills/pr/SKILL.md
.claude/skills/pre-commit-check/SKILL.md
.claude/skills/project-dev-context/SKILL.md
.claude/skills/release/SKILL.md
.claude/skills/review-pr/SKILL.md
.claude/skills/update-api-docs/SKILL.md
.claude/skills/implement-performance/SKILL.md

Metadata

Files
0
Version
6611467
Hash
b0c8226a
Indexed
2026-08-20 11:23

inicio - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-10-25 09:27
浙ICP备14020137号-1