# Testing Embody includes a comprehensive automated test suite with **30 test suites** and **587 test methods** covering core externalization, MCP tools, TDN format, and server lifecycle. Tests run inside TouchDesigner using a custom test runner with sandbox isolation. ## Running Tests ### From TouchDesigner ```python # All tests, one per frame (non-blocking, default) op.unit_tests.RunTests() # Single suite op.unit_tests.RunTests(suite_name='test_path_utils') # Single test method op.unit_tests.RunTests(suite_name='test_path_utils', test_name='test_normalizePath_backslashes_converted') # All in one frame (blocks TD until complete) op.unit_tests.RunTestsSync() # One suite per frame op.unit_tests.RunTestsDeferred() # Get results results = op.unit_tests.GetResults() # Returns: {'total': 587, 'passed': 587, 'failed': 0, 'errors': 0, 'skipped': 0, 'results': [...]} ``` ### Via MCP Use the `run_tests` Envoy tool: ``` run_tests() # Run all suites run_tests(suite_name='test_path_utils') # Run one suite ``` ## Test Coverage ### Core Embody (14 suites) | Suite | Tests | Coverage | |-------|-------|----------| | `test_externalization` | 9 | Externalization lifecycle | | `test_crud_operators` | 21 | Create, read, update, delete operations | | `test_file_management` | 7 | File I/O, path handling, cleanup | | `test_tag_management` | 19 | Tagging operators for externalization | | `test_tag_lifecycle` | 17 | Tag application and removal | | `test_rename_move_lifecycle` | 25 | Rename and move tracking | | `test_delete_cleanup` | 16 | Deletion and file cleanup | | `test_duplicate_handling` | 4 | Duplicate operator handling | | `test_update_sync` | 7 | Sync between .toe and externalized files | | `test_path_utils` | 18 | Path normalization and utilities | | `test_param_tracker` | 13 | Parameter change tracking | | `test_operator_queries` | 6 | Operator discovery and queries | | `test_logging` | 10 | Logging system | | `test_custom_parameters` | 30 | Custom parameter behavior (Folder, Disable/Enable, Update, TDN controls, Logs, Envoy) | ### MCP Tools (11 suites) | Suite | Tests | Coverage | |-------|-------|----------| | `test_mcp_operators` | 20 | Create, delete, copy, rename, query, find | | `test_mcp_parameters` | 11 | Get/set parameters, modes, expressions | | `test_mcp_dat_content` | 9 | DAT text and table operations | | `test_mcp_connections` | 8 | Wiring operators together | | `test_mcp_annotations` | 19 | Creating and managing annotations | | `test_mcp_extensions` | 6 | Extension creation and setup | | `test_mcp_diagnostics` | 12 | Error checking, performance, info | | `test_mcp_flags_position` | 12 | Operator flags and positioning | | `test_mcp_code_execution` | 7 | Executing Python in TD | | `test_mcp_externalization` | 18 | Embody integration via MCP | | `test_mcp_performance` | 5 | Performance monitoring | ### TDN Format (4 suites) | Suite | Tests | Coverage | |-------|-------|----------| | `test_tdn_export_import` | 26 | Network export/import | | `test_tdn_helpers` | 25 | TDN utility functions | | `test_tdn_reconstruction` | 124 | Reconstruction round-trip fidelity | | `test_tdn_file_io` | 66 | TDN file output, per-comp splitting, stale cleanup | ### Infrastructure (1 suite) | Suite | Tests | Coverage | |-------|-------|----------| | `test_server_lifecycle` | 17 | Envoy MCP server start/stop | ## Execution Modes | Mode | Method | Behavior | |------|--------|----------| | Per-test deferred | `RunTests()` | One test per frame. Best for heavy suites. Non-blocking. **Default.** | | Per-suite deferred | `RunTestsDeferred()` | One suite per frame. Keeps TD responsive. | | Synchronous | `RunTestsSync()` | All tests in one frame. Blocks TD. Use for MCP. | ## Test Framework Features - **Sandbox isolation**: Each suite gets a fresh `baseCOMP` for test fixtures - **20+ assertion methods**: `assertEqual`, `assertTrue`, `assertIn`, `assertIsInstance`, etc. - **Lifecycle hooks**: `setUp`/`tearDown` per test, `setUpSuite`/`tearDownSuite` per suite - **Skip support**: `self.skip(reason)` to conditionally skip tests - **Results tracking**: Table DAT with pass/fail/error/skip counts and durations ## Writing New Tests Create a test file in the unit tests directory: ```python """Test suite: description of what this tests.""" # Base class is auto-injected by the test runner class TestMyFeature(EmbodyTestCase): def test_something(self): """Test description.""" # Create test fixtures in self.sandbox op = self.sandbox.create(baseCOMP, 'test_op') # Access Embody extension result = self.embody_ext.someMethod(op) # Assertions self.assertEqual(result, expected_value) self.assertTrue(op.valid) self.assertIn('foo', result) def setUp(self): """Called before each test (optional).""" pass def tearDown(self): """Called after each test (auto-destroys sandbox children).""" super().tearDown() ``` ### Available Objects | Object | Description | |--------|-------------| | `self.sandbox` | baseCOMP for creating temporary operators | | `self.embody` | Reference to `op.Embody` | | `self.embody_ext` | Direct access to `op.Embody.ext.Embody` | | `self.runner` | TestRunnerExt instance | | `op`, `parent`, `root` | All TD globals available | ## What Cannot Be Unit Tested Some areas require manual testing: - UI interactions (clicking, dragging, network editor) - Cross-session persistence (requires closing/reopening `.toe`) - Keyboard shortcuts (actual key press detection) - Modal dialogs (file pickers, prompts) - Undo/redo behavior - Graphics rendering (visual output validation) - Real-time performance (sustained load, frame-rate stability) - External hardware (MIDI, OSC, DMX, serial)