9  Test with Data (Step 6)

The final step in our methodology—testing with a variety of data—is where we verify that our solution works correctly in all scenarios. This critical step ensures that our implementation meets the requirements and handles edge cases properly.

9.1 Comprehensive Testing Strategies

9.1.1 The Importance of Thorough Testing

Testing isn’t just about confirming that your code works—it’s about discovering where and how it might fail. Thorough testing:

  1. Validates correctness - Ensures the solution produces the expected results
  2. Reveals edge case issues - Identifies problems with unusual inputs
  3. Provides confidence - Gives you assurance that the solution is robust
  4. Documents behavior - Shows how the code responds to different inputs
  5. Prevents regressions - Helps maintain correctness as code evolves

Even with AI assistance in implementation, testing remains primarily a human responsibility. The most effective developers use a structured approach to testing rather than ad hoc verification.

9.1.2 Types of Test Cases

A comprehensive testing strategy includes several types of test cases:

9.1.2.1 Functional Tests

These verify that the code produces the correct output for standard inputs:

# Basic functionality tests
assert longest_palindromic_substring("babad") in ["bab", "aba"]
assert longest_palindromic_substring("cbbd") == "bb"

9.1.2.2 Edge Case Tests

These check how the code handles boundary conditions and unusual inputs:

# Edge case tests
assert longest_palindromic_substring("") == ""  # Empty string
assert longest_palindromic_substring("a") == "a"  # Single character
assert longest_palindromic_substring("aa") == "aa"  # Two identical characters

9.1.2.3 Stress Tests

These evaluate behavior with large or complex inputs:

# Stress test
long_input = "a" * 1000 + "b" + "a" * 1000  # Long palindrome
assert len(longest_palindromic_substring(long_input)) == 2001

random_input = "".join(random.choice("abcdef") for _ in range(1000))
# No assertion, just checking it doesn't crash or timeout
result = longest_palindromic_substring(random_input)

9.1.2.4 Performance Tests

These measure execution time and resource usage:

# Performance test
import time

start_time = time.time()
longest_palindromic_substring(long_input)
end_time = time.time()

assert end_time - start_time < 1.0  # Should complete in under 1 second

9.1.2.5 Regression Tests

These ensure that fixes for reported issues don’t break existing functionality:

# Regression test for a specific reported issue
# Issue: Function was returning incorrect results for strings with repeated characters
assert longest_palindromic_substring("aabaa") == "aabaa"

9.2 Edge Case Identification

Identifying all relevant edge cases is a crucial aspect of thorough testing.

9.2.1 Common Edge Case Categories

Different problems have different edge cases, but these categories are frequently relevant:

9.2.1.1 Input Size Edge Cases

  • Empty inputs (e.g., empty strings, empty arrays)
  • Minimal inputs (e.g., single-character strings, single-element arrays)
  • Very large inputs (testing performance and boundary limits)

9.2.1.2 Value Edge Cases

  • Zero values
  • Negative values
  • Extremely large or small values
  • Special values (e.g., NaN, infinity in floating-point operations)

9.2.1.3 Format Edge Cases

  • Valid but unusual formats
  • Malformed inputs
  • Mixed case or special characters in strings
  • Localization issues (e.g., different decimal separators)

9.2.1.4 Boundary Edge Cases

  • First/last elements of collections
  • Values at the exact boundary of allowed ranges
  • Off-by-one scenarios

9.2.1.5 Algorithm-Specific Edge Cases

For our palindrome example: - No palindromes longer than 1 character - Entire string is a palindrome - Multiple palindromes of the same maximum length - Palindromes at the start or end of the string - Even vs. odd-length palindromes

9.2.2 Using AI to Identify Edge Cases

AI assistants can help identify edge cases you might not have considered:

Human prompt:

I've implemented a function to find the longest palindromic substring in a string. What edge cases should I test to ensure it works correctly in all scenarios?

AI response: > Here’s a comprehensive list of edge cases you should test for your longest palindromic substring function: > > ### Basic Edge Cases > > 1. Empty string - Should return empty string > 2. Single character - Should return that character > 3. Two identical characters - Should return both characters > 4. Two different characters - Should return the first character > > ### Palindrome Pattern Edge Cases > > 5. No palindromes longer than 1 character - “abc” should return “a” (or “b” or “c”) > 6. Odd-length palindromes only - “aba” should return “aba” > 7. Even-length palindromes only - “abba” should return “abba” > 8. Mix of odd and even palindromes - “abbac” should return “abba” > 9. Palindrome at the beginning - “abbax” should return “abba” > 10. Palindrome at the end - “xabba” should return “abba” > 11. Palindrome in the middle - “xabbay” should return “abba” > 12. Entire string is a palindrome - “racecar” should return “racecar” > > ### Multiple Palindromes Edge Cases > > 13. Multiple palindromes of different lengths - “aaaabcbaaaaa” should return “aaaaa” > 14. Multiple palindromes of same maximum length - “aabaa” and “baab” in “aabaabaab” (should return either) > 15. Overlapping palindromes - “aaaa” has multiple overlapping palindromes > > ### Character Edge Cases > > 16. Case sensitivity - “Madam” is not a palindrome if case-sensitive > 17. Special characters - “a#a” should return “a#a” > 18. Numbers and mixed content - “12321” or “a123a” > 19. Unicode characters - Test with non-ASCII characters like “租赁” > > ### Performance Edge Cases > > 20. Very long strings - Test with strings of thousands of characters > 21. Long palindrome within long string - e.g., 1000 ‘a’s + ’b’ + 1000 ’a’s > 22. Repeated characters - “aaaaaaaaaa” (helps test for optimization issues) > > A well-tested function should handle all these cases correctly and efficiently.

9.3 AI-Assisted Test Generation

AI can be particularly helpful in generating both test cases and test code.

9.3.1 Generating Test Cases

Ask AI to generate diverse test inputs:

Human prompt:

Could you generate 10 diverse test cases for the longest palindromic substring function? For each test case, provide the input string and the expected output.

AI response: > Here are 10 diverse test cases for the longest palindromic substring function: > > | Test Case | Input String | Expected Output | Description | > |———–|————-|—————-|————-| > | 1 | “” | “” | Empty string | > | 2 | “a” | “a” | Single character | > | 3 | “abcd” | “a” | No palindromes longer than 1 (any single character is valid) | > | 4 | “babad” | “bab” or “aba” | Multiple valid answers of same length | > | 5 | “cbbd” | “bb” | Even-length palindrome | > | 6 | “racecar” | “racecar” | Entire string is a palindrome | > | 7 | “aacabdkacaa” | “aca” | Multiple palindromes, should find longest | > | 8 | “aaaaa” | “aaaaa” | Repeated characters | > | 9 | “civilservice” | “civic” | Palindrome at beginning | > | 10 | “abcdeedcba” | “abcdeedcba” | Even-length palindrome (entire string) | > > These test cases cover empty strings, single characters, no long palindromes, multiple valid answers, even and odd-length palindromes, whole-string palindromes, multiple palindromes of different lengths, repeated characters, and palindromes at different positions in the string.

9.3.2 Generating Test Code

AI can also help generate the actual test code:

Human prompt:

Could you create a Python unit test class using the unittest framework for testing the longest_palindromic_substring function with the test cases you suggested?

AI response: > Here’s a comprehensive unit test class using Python’s unittest framework to test the longest palindromic substring function with the test cases I suggested: > > python > import unittest > > # Import your function > from palindrome import longest_palindromic_substring > > class TestLongestPalindromicSubstring(unittest.TestCase): > > def test_empty_string(self): > """Test with an empty string.""" > self.assertEqual(longest_palindromic_substring(""), "") > > def test_single_character(self): > """Test with a single character.""" > self.assertEqual(longest_palindromic_substring("a"), "a") > > def test_no_long_palindromes(self): > """Test with a string that has no palindromes longer than 1 character.""" > result = longest_palindromic_substring("abcd") > # Any single character is a valid answer > self.assertTrue(result in ["a", "b", "c", "d"]) > self.assertEqual(len(result), 1) > > def test_multiple_valid_answers(self): > """Test with a string that has multiple valid answers of the same length.""" > result = longest_palindromic_substring("babad") > self.assertTrue(result in ["bab", "aba"]) > self.assertEqual(len(result), 3) > > def test_even_length_palindrome(self): > """Test with a string containing an even-length palindrome.""" > self.assertEqual(longest_palindromic_substring("cbbd"), "bb") > > def test_entire_string_is_palindrome(self): > """Test with a string that is entirely a palindrome.""" > self.assertEqual(longest_palindromic_substring("racecar"), "racecar") > > def test_multiple_palindromes(self): > """Test with a string containing multiple palindromes of different lengths.""" > self.assertEqual(longest_palindromic_substring("aacabdkacaa"), "aca") > > def test_repeated_characters(self): > """Test with a string of repeated characters.""" > self.assertEqual(longest_palindromic_substring("aaaaa"), "aaaaa") > > def test_palindrome_at_beginning(self): > """Test with a palindrome at the beginning of the string.""" > self.assertEqual(longest_palindromic_substring("civilservice"), "civic") > > def test_even_length_entire_string(self): > """Test with an even-length palindrome that is the entire string.""" > self.assertEqual(longest_palindromic_substring("abcdeedcba"), "abcdeedcba") > > def test_performance_with_long_string(self): > """Test performance with a very long string containing a long palindrome.""" > long_input = "a" * 1000 + "b" + "a" * 1000 > result = longest_palindromic_substring(long_input) > self.assertEqual(len(result), 2001) > self.assertEqual(result, long_input) > > if __name__ == '__main__': > unittest.main() > > > This test class includes: > > 1. Tests for all the cases I mentioned earlier > 2. Clear test method names that describe what’s being tested > 3. Docstrings explaining each test > 4. Appropriate assertions for each case > 5. A performance test with a large input > > To use this, save it as test_palindrome.py and make sure your implementation is in a file named palindrome.py with the function longest_palindromic_substring. Then run it with: > > > python -m unittest test_palindrome.py > > > If you’re using a different file structure or function name, you’ll need to adjust the import statement accordingly.

9.4 Learning from Test Failures

Test failures provide valuable learning opportunities that can improve both your current solution and your general understanding.

9.4.1 Debugging with AI Assistance

When tests fail, AI can help analyze the failure:

Human prompt:

My test case for "aacabdkacaa" is failing. The expected output is "aca" but my function returns "aa". Here's my implementation:

```python
def longest_palindromic_substring(s):
    if not s:
        return ""
    
    start = 0
    max_length = 1
    
    for i in range(len(s)):
        # Check odd length palindromes
        left, right = i, i
        while left >= 0 and right < len(s) and s[left] == s[right]:
            if right - left + 1 > max_length:
                max_length = right - left + 1
                start = left
            left -= 1
            right += 1
        
        # Check even length palindromes
        left, right = i, i + 1
        if right < len(s):
            while left >= 0 and right < len(s) and s[left] == s[right]:
                if right - left + 1 > max_length:
                    max_length = right - left + 1
                    start = left
                left -= 1
                right += 1
    
    return s[start:start + max_length]

Can you help me identify what’s wrong?


**AI response:**
> I see the issue in your implementation when testing with "aacabdkacaa". Let me walk through this step by step.
> 
> In the string "aacabdkacaa", the palindromes are:
> - "aa" at the beginning (length 2)
> - "a" (multiple single characters, length 1)
> - "aca" in the middle (length 3)
> - "acaa" at the end (length 4) ← Wait, this isn't a palindrome!
> 
> Ah, I found the issue! The string "acaa" is not a palindrome (it would read "aaca" backwards). Let me trace through your code to see where it's going wrong.
> 
> When checking the string "aacabdkacaa", your function should find "aca" as the longest palindrome, which has length 3. But it's returning "aa" which has length 2.
> 
> Let me check the expected output again... the expected output is "aca" but your function returns "aa". There must be a bug in your code or an error in my analysis.
> 
> Let's carefully trace through the string "aacabdkacaa":
> - Palindromes at position 0 ('a'): "a", "aa"
> - Palindromes at position 1 ('a'): "a", "aa", "aaca" (wait, this isn't a palindrome!)
> 
> I think I've found the bug! In your implementation, when you're expanding around a center, you're updating `max_length` and `start` *before* confirming the substring is actually a palindrome.
> 
> The issue is in both of your while loops. Let's focus on the even-length check. When you check for even-length palindromes starting at position i, you immediately enter the while loop if s[i] == s[i+1]. But then you update `max_length` and `start` before checking if the next characters also match.
> 
> Here's the correction needed:
> 
> ```python
> def longest_palindromic_substring(s):
>     if not s:
>         return ""
>     
>     start = 0
>     max_length = 1
>     
>     for i in range(len(s)):
>         # Check odd length palindromes
>         left, right = i, i
>         while left >= 0 and right < len(s) and s[left] == s[right]:
>             current_length = right - left + 1
>             if current_length > max_length:
>                 max_length = current_length
>                 start = left
>             left -= 1
>             right += 1
>         
>         # Check even length palindromes
>         left, right = i, i + 1
>         while left >= 0 and right < len(s) and s[left] == s[right]:
>             current_length = right - left + 1
>             if current_length > max_length:
>                 max_length = current_length
>                 start = left
>             left -= 1
>             right += 1
>     
>     return s[start:start + max_length]
> ```
> 
> The key change is adding a `current_length` variable to calculate the length inside the while loop. This ensures you're using the correct length for comparison.
> 
> This bug would cause your function to sometimes return shorter palindromes when longer ones exist, especially in cases with repeated characters.

### Test-Driven Improvements

Use test results to drive improvements to your solution:

1. **Identify patterns in failures** - Look for commonalities in failing tests
2. **Fix one issue at a time** - Address each problem separately and retest
3. **Add regression tests** - Create specific tests for fixed issues
4. **Consider alternate approaches** - If fixing proves difficult, reconsider your algorithm

## Practical Exercise: Testing Our Solution

Let's test our longest palindromic substring implementation:

### Test Suite Implementation

```python
import unittest
import time

# Import our implementation
from palindrome import longest_palindromic_substring

class TestLongestPalindromicSubstring(unittest.TestCase):
    def test_basic_functionality(self):
        """Test basic functionality with standard inputs."""
        self.assertEqual(longest_palindromic_substring("babad") in ["bab", "aba"], True)
        self.assertEqual(longest_palindromic_substring("cbbd"), "bb")
    
    def test_edge_cases(self):
        """Test edge cases."""
        # Empty string
        self.assertEqual(longest_palindromic_substring(""), "")
        
        # Single character
        self.assertEqual(longest_palindromic_substring("a"), "a")
        
        # Two identical characters
        self.assertEqual(longest_palindromic_substring("aa"), "aa")
        
        # No palindromes longer than 1
        result = longest_palindromic_substring("abcd")
        self.assertTrue(len(result) == 1)
        self.assertTrue(result in ["a", "b", "c", "d"])
    
    def test_comprehensive_cases(self):
        """Test a variety of palindrome patterns."""
        # Entire string is a palindrome
        self.assertEqual(longest_palindromic_substring("racecar"), "racecar")
        
        # Palindrome at the beginning
        self.assertEqual(longest_palindromic_substring("abbaxyz"), "abba")
        
        # Palindrome at the end
        self.assertEqual(longest_palindromic_substring("xyzabba"), "abba")
        
        # Palindrome in the middle
        self.assertEqual(longest_palindromic_substring("xyzabbauvw"), "abba")
        
        # Multiple palindromes
        self.assertEqual(longest_palindromic_substring("aacabdkacaa"), "aca")
    
    def test_multiple_palindromes(self):
        """Test strings with multiple palindromes."""
        # Multiple palindromes of different lengths
        self.assertEqual(longest_palindromic_substring("abcbaxyzzyxabc"), "xyzzyxa")
        
        # Multiple palindromes of same length
        result = longest_palindromic_substring("abacababacab")
        self.assertTrue(result in ["abacaba", "bacabab"])
    
    def test_performance(self):
        """Test performance with large inputs."""
        # Long string with long palindrome
        long_input = "a" * 500 + "b" + "a" * 500
        start_time = time.time()
        result = longest_palindromic_substring(long_input)
        end_time = time.time()
        
        self.assertEqual(len(result), 1001)
        self.assertTrue(end_time - start_time < 1.0)  # Should complete in under 1 second

if __name__ == "__main__":
    unittest.main()

9.4.2 Test Execution and Results

Running our tests might reveal issues we hadn’t anticipated:

.F...F
======================================================================
FAIL: test_comprehensive_cases (__main__.TestLongestPalindromicSubstring)
Test a variety of palindrome patterns.
----------------------------------------------------------------------
...
AssertionError: 'aaca' != 'aca'
...
======================================================================
FAIL: test_performance (__main__.TestLongestPalindromicSubstring)
Test performance with large inputs.
----------------------------------------------------------------------
...
AssertionError: 999 != 1001
...

9.4.3 Fixing Issues Based on Test Results

Analyzing the failures and updating our implementation:

def longest_palindromic_substring(s):
    """
    Find the longest palindromic substring in a given string.
    
    Args:
        s (str): The input string to process
        
    Returns:
        str: The longest palindromic substring
    """
    if not s:
        return ""
    
    start = 0
    max_length = 1
    
    def expand_around_center(left, right):
        """
        Expand around center while maintaining palindrome property.
        Returns the length of the palindrome.
        """
        while left >= 0 and right < len(s) and s[left] == s[right]:
            left -= 1
            right += 1
        
        # left+1 and right-1 are the actual palindrome boundaries
        # because we've gone one step too far in the while loop
        return left + 1, right - 1 - left
    
    for i in range(len(s)):
        # Check odd length palindromes
        odd_start, odd_length = expand_around_center(i, i)
        if odd_length > max_length:
            start = odd_start
            max_length = odd_length
        
        # Check even length palindromes
        if i < len(s) - 1:
            even_start, even_length = expand_around_center(i, i + 1)
            if even_length > max_length:
                start = even_start
                max_length = even_length
    
    return s[start:start + max_length]

9.4.4 Rerunning Tests

After fixing the issues, we run the tests again to verify the solution:

......
----------------------------------------------------------------------
Ran 6 tests in 0.123s

OK

9.5 Key Takeaways

  • Thorough testing is essential, even with AI-assisted implementation
  • A comprehensive testing strategy includes functional tests, edge cases, stress tests, performance tests, and regression tests
  • AI can help identify edge cases and generate test code
  • Test failures provide valuable learning opportunities
  • The combination of human oversight and AI assistance leads to more robust solutions

9.6 Moving Forward

With our solution thoroughly tested, we’ve completed the six-step methodology. This approach has helped us develop a correct and robust solution while maintaining human understanding and control throughout the process.

In the next section of the book, we’ll explore specific patterns and practices for intentional prompting, beginning with Chapter 10’s exploration of intentional prompting patterns—techniques for guiding AI through complex programming tasks.