9.3 KiB
9.3 KiB
Testing Patterns
Analysis Date: 2026-03-03
Test Framework
Runner:
- unittest (Python standard library)
- Config: None (uses default unittest discovery)
- No pytest.ini, setup.cfg, or tox.ini configuration detected
Assertion Library:
- unittest.TestCase assertions (built-in)
Run Commands:
python -m unittest test_string_nodes.py # Run all tests
python -m unittest test_string_nodes.TestStringNodes # Run specific test class
python -m unittest test_string_nodes.TestStringNodes.test_<name> # Run specific test
Test File Location:
test_string_nodes.pyin project root alongside source files (/home/mark/Code/ComfyUI_StringEssentials/test_string_nodes.py)- Co-located with source code (not in separate tests directory)
Test File Organization
Location:
- Co-located pattern: test file in same directory as source modules
Naming:
- Test file:
test_string_nodes.py - Test class:
TestStringNodes(PascalCase with Test prefix) - Test methods:
test_<functionality>format
Structure:
test_string_nodes.py
├── TestStringNodes (class)
│ ├── setUp() - Initialize node instances
│ ├── test_strip_* - Tests for StringStripNode
│ ├── test_replace_* - Tests for StringMultiReplaceNode
│ ├── test_append_* - Tests for StringConditionalAppendNode
│ └── test_contains_any_* - Tests for StringContainsAnyNode
└── if __name__ == '__main__': unittest.main()
Test Structure
Suite Organization:
class TestStringNodes(unittest.TestCase):
def setUp(self):
self.strip_node = StringStripNode()
self.replace_node = StringMultiReplaceNode()
self.append_node = StringConditionalAppendNode()
self.contains_any_node = StringContainsAnyNode()
Patterns:
- Setup pattern:
setUp()method instantiates all node instances as instance variables for reuse across test methods - No teardown: No
tearDown()method (no cleanup needed) - Assertion pattern: Direct method calls on node instances with parameter unpacking:
result = self.strip_node.string_strip( input_string=input_string, strings_to_remove=strings_to_remove, match_case=False, match_whole_string=True, preserve_punctuation=True, remove_extra_spaces=True )[0] self.assertEqual(result, expected_output)
Test Method Characteristics:
- Each test is independent and focused on a single behavior
- Tests use explicit parameter names (keyword arguments) for clarity
- Return value tuple unpacking:
[0]for single return, tuple unpacking for multiple returns - Clear arrange-act-assert flow (input setup → method call → assertion)
Test Coverage by Node
StringStripNode Tests:
test_strip_basic_functionality: Single string removaltest_strip_multiple_strings: Multiple removal patterns (multiline input)test_strip_case_sensitivity: Case-insensitive vs case-sensitive modestest_strip_punctuation_preserved: Punctuation preservation (Issue #6)test_strip_punctuation_removed: Legacy punctuation removal behavior- Extra spaces handling covered in
test_extra_spaces_handling
StringMultiReplaceNode Tests:
test_replace_basic_functionality: Single replacement pairtest_replace_multiple_pairs: Multiple replacement pairs from multiline inputtest_replace_case_sensitivity: Case-insensitive vs case-sensitive modestest_replace_custom_delimiter: Custom delimiter support (not just::test_replace_punctuation_preserved: Punctuation preservation (Issue #6)test_replace_prompt_syntax_preserved: Bracket syntax preservation (e.g.,[word1:word2:.5])test_replace_with_space: Trailing space preservation in replacements (Issue #8)test_replace_with_leading_space: Leading space in replacement strings (Issue #8)test_replace_with_whitespace_only: Multiple spaces in replacements (Issue #8)test_replace_with_tab: Tab character in replacements (Issue #8)
StringConditionalAppendNode Tests:
test_append_string_not_found: Appending when string not presenttest_append_string_found: No append when string already presenttest_append_beginning: Position flag "beginning" vs "end"test_append_case_insensitive: Case-insensitive searchtest_append_case_sensitive: Case-sensitive searchtest_append_custom_separator: Custom separator handlingtest_append_no_separator: Empty string separatortest_append_multiple_strings: Multiple check strings with mixed found/not foundtest_append_multiple_all_found: All strings already present (no append)
StringContainsAnyNode Tests:
test_contains_any_found: Basic found casetest_contains_any_not_found: Basic not found casetest_contains_any_multiple_first_match: Returns first match from multiple optionstest_contains_any_case_insensitive: Default case-insensitive behaviortest_contains_any_case_sensitive: Case-sensitive searchtest_contains_any_case_sensitive_found: Case-sensitive with matching casetest_contains_any_empty_input: Empty input string handlingtest_contains_any_empty_substrings: Empty substrings inputtest_contains_any_whitespace_substrings: Whitespace-only lines are ignoredtest_contains_any_partial_match: Substring matching (anime matches animation)
Mocking
Framework: None - unittest provides basic mocking but not used in this codebase
Patterns:
- No mock objects used
- No external dependencies mocked
- Tests use real node instances with direct method calls
- All test data is embedded in test methods (no external fixtures)
What to Mock:
- No mocking required; all node classes are stateless and dependency-free
What NOT to Mock:
- Don't mock the node classes - test them directly with real instances
Fixtures and Factories
Test Data:
- All test data embedded directly in test methods
- No separate fixture files or factory classes
- Input strings created inline:
input_string = "This is a test string with test word" strings_to_remove = "test"
Location:
- Test data is co-located with test methods (no separate fixtures directory)
- Issue-specific test data clearly labeled with issue references:
def test_strip_punctuation_preserved(self): # Test Issue #6 fix: punctuation should be preserved input_string = "Let's meet, Grandma!"
Coverage
Requirements: None enforced (no coverage configuration detected)
View Coverage:
# Using unittest's built-in capabilities (limited):
python -m unittest discover -v
# Better coverage with coverage.py (not currently installed):
pip install coverage
coverage run -m unittest test_string_nodes.py
coverage report
Observed Coverage:
- StringStripNode: 100% - All major code paths tested (basic, multiple, case sensitivity, punctuation handling, spaces)
- StringMultiReplaceNode: 100% - All code paths tested (basic, multiple, case sensitivity, custom delimiter, punctuation, whitespace preservation)
- StringConditionalAppendNode: 100% - All code paths tested (found/not found, position, case sensitivity, separator options, multiple strings)
- StringContainsAnyNode: 100% - All code paths tested (found/not found, case sensitivity, empty inputs, whitespace handling, partial matching)
- StringPreviewNode: 0% - No tests (trivial pass-through logic)
- StringTextboxNode: 0% - No tests (trivial pass-through logic)
Test Types
Unit Tests:
- Scope: Individual node class methods
- Approach: Direct method invocation with arranged inputs, assert on single output
- Example:
test_strip_basic_functionalitytestsstring_strip()method with specific parameters - No test doubles or stubs - real objects used
Integration Tests:
- Not present in codebase (no multi-component interaction testing)
E2E Tests:
- Not present (ComfyUI-specific E2E would require full ComfyUI runtime)
Issue-Driven Tests:
- Tests explicitly tied to GitHub issues in comments
- Issue #6: Punctuation preservation tests
- Issue #8: Whitespace preservation in replacements tests
- Issue #9: StringContainsAnyNode feature tests
- Pattern: When fixing a bug, issue number added as comment and test created to verify fix
Common Patterns
Async Testing:
- Not applicable - Python standard library code without async/await
Error Testing:
- No explicit error/exception testing
- Edge cases tested: empty strings, whitespace-only input, multiple spaces
- Validation through return values rather than exception handling
Testing Multiline Input:
strings_to_remove = """test
More Text"""
result = self.strip_node.string_strip(
input_string=input_string,
strings_to_remove=strings_to_remove,
match_case=False,
match_whole_string=True,
preserve_punctuation=True,
remove_extra_spaces=True
)[0]
Testing Multiple Parameters:
result, was_appended = self.append_node.conditional_append(
input_string=input_string,
strings_to_check=strings_to_check,
position="end",
match_case=False,
separator=", "
)
self.assertEqual(result, expected_string)
self.assertTrue(was_appended)
Testing Boolean Returns:
- StringConditionalAppendNode returns
(string, boolean)tuple - StringContainsAnyNode returns
(boolean, string)tuple - Tests unpack both return values and assert on each
Testing analysis: 2026-03-03