Skip to content

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

# 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:

"""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)