Files
bradsec-ComfyUI_StringEssen…/.planning/codebase/TESTING.md
T
2026-03-03 20:13:36 +10:00

252 lines
9.3 KiB
Markdown

# 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:**
```bash
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.py` in 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:**
```python
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:
```python
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 removal
- `test_strip_multiple_strings`: Multiple removal patterns (multiline input)
- `test_strip_case_sensitivity`: Case-insensitive vs case-sensitive modes
- `test_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 pair
- `test_replace_multiple_pairs`: Multiple replacement pairs from multiline input
- `test_replace_case_sensitivity`: Case-insensitive vs case-sensitive modes
- `test_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 present
- `test_append_string_found`: No append when string already present
- `test_append_beginning`: Position flag "beginning" vs "end"
- `test_append_case_insensitive`: Case-insensitive search
- `test_append_case_sensitive`: Case-sensitive search
- `test_append_custom_separator`: Custom separator handling
- `test_append_no_separator`: Empty string separator
- `test_append_multiple_strings`: Multiple check strings with mixed found/not found
- `test_append_multiple_all_found`: All strings already present (no append)
**StringContainsAnyNode Tests:**
- `test_contains_any_found`: Basic found case
- `test_contains_any_not_found`: Basic not found case
- `test_contains_any_multiple_first_match`: Returns first match from multiple options
- `test_contains_any_case_insensitive`: Default case-insensitive behavior
- `test_contains_any_case_sensitive`: Case-sensitive search
- `test_contains_any_case_sensitive_found`: Case-sensitive with matching case
- `test_contains_any_empty_input`: Empty input string handling
- `test_contains_any_empty_substrings`: Empty substrings input
- `test_contains_any_whitespace_substrings`: Whitespace-only lines are ignored
- `test_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:
```python
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:
```python
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:**
```bash
# 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_functionality` tests `string_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:**
```python
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:**
```python
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*