From test-engineering
This skill provides the write-agent with knowledge and heuristics to detect and prevent redundant test scenario generation. The skill defines equivalence classes for test inputs, classification rules for mapping values to classes, and a detection algorithm to identify when a proposed test covers the same scenario as an existing test.
npx claudepluginhub issacchaos/local-marketplace --plugin test-engineeringThis skill uses the workspace's default tool permissions.
This skill provides the write-agent with knowledge and heuristics to detect and prevent redundant test scenario generation. The skill defines equivalence classes for test inputs, classification rules for mapping values to classes, and a detection algorithm to identify when a proposed test covers the same scenario as an existing test.
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
This skill provides the write-agent with knowledge and heuristics to detect and prevent redundant test scenario generation. The skill defines equivalence classes for test inputs, classification rules for mapping values to classes, and a detection algorithm to identify when a proposed test covers the same scenario as an existing test.
Key Concepts:
Goal: Block generation of redundant tests while allowing all unique scenarios and edge cases, providing clear explanations when blocking.
1, 5, 42, 1001, 10, 99, 1001, 25, 100101, 500, 10000, 999999200, 5000, 1000000150, 10000, Integer.MAX_VALUE / 2-1, -5, -42, -100-1, -10, -99, -100-1, -25, -100-101, -500, -10000, -999999-200, -5000, -1000000-150, -10000, Integer.MIN_VALUE / 20, 0.00, 0.00, 0L, 0.00.1, 0.2, 3.14159, 1.23e-100.1, 0.2, Math.PI, 1.50.1f, 0.2, Math.PI, 1.5dsys.maxsize, float('inf')Number.MAX_SAFE_INTEGER, Number.MAX_VALUE, InfinityInteger.MAX_VALUE, Long.MAX_VALUE, Double.MAX_VALUE-sys.maxsize - 1, float('-inf')Number.MIN_SAFE_INTEGER, Number.MIN_VALUE, -InfinityInteger.MIN_VALUE, Long.MIN_VALUE, Double.MIN_VALUE"", ''"", '', ``"""hello", "world", "test123", "a", "very long string...""hello", "world", "test123", "a""hello", "world", "test123", "a"[], (), {}, set()[], {}, new Set(), new Map()new ArrayList<>(), new int[0], Collections.emptyList()[1, 2, 3], [4, 5, 6], {"key": "value"}[1, 2, 3], [4, 5, 6], {key: "value"}Arrays.asList(1, 2, 3), new int[]{4, 5, 6}Nonenull, undefinednullnilnullnullptr, NULLTrue, Falsetrue, falsetrue, falsedivide(10, 0)add(MAX_INT, 1)subtract(MIN_INT, 1)parse_date("invalid")array[999999]add("string", 123) in typed languageslock() → unlock()open_file() → close_file()start_service() → stop_service()connect() → disconnect()setup() → teardown()"🎉", "\n\t", "中文"[1, 2, 3, ..., 1000000]parallel_access()read_while_writing()allocate_huge_memory()When analyzing a test, extract input values and classify each according to these rules. Multiple classifications may apply to a single test (e.g., a test with inputs 5 and 0 has both POSITIVE_SMALL and ZERO).
Literal Value Extraction (highest confidence):
# Function call arguments
result = add(5, 10) # Extract: 5 (POSITIVE_SMALL), 10 (POSITIVE_SMALL)
output = process("hello") # Extract: "hello" (NON_EMPTY_STRING)
# Assert statements
assert divide(10, 0) # Extract: 10 (POSITIVE_SMALL), 0 (ZERO + ERROR_CONDITION)
assert calculate([1, 2, 3]) == 6 # Extract: [1, 2, 3] (NON_EMPTY_COLLECTION)
# Variable assignments
a = 100
b = 200
result = add(a, b) # Extract: 100 (POSITIVE_LARGE), 200 (POSITIVE_LARGE)
Framework Patterns:
def test_function_name(): assert function(args) == expectedself.assertEqual(function(args), expected)test_* functionsClassification:
"" → EMPTY_STRING[], (), {}, set() → EMPTY_COLLECTIONNone → NULL_UNDEFINEDTrue, False → BOOLEANLiteral Value Extraction:
// Jest expect patterns
expect(add(5, 10)).toBe(15); // Extract: 5 (POSITIVE_SMALL), 10 (POSITIVE_SMALL)
expect(divide(10, 0)).toThrow(); // Extract: 10 (POSITIVE_SMALL), 0 (ZERO + ERROR_CONDITION)
// Function calls in describe/it blocks
it('should process string', () => {
const result = process("hello"); // Extract: "hello" (NON_EMPTY_STRING)
});
// Variable assignments
const a = 100;
const b = 200;
const result = add(a, b); // Extract: 100 (POSITIVE_LARGE), 200 (POSITIVE_LARGE)
Framework Patterns:
expect(...).toBe(), expect(...).toThrow(), expect(...).toEqual()assert.equal(), expect().to.equal()describe() and it() blocksClassification:
"", '', `` → EMPTY_STRING[] → EMPTY_COLLECTIONnull, undefined → NULL_UNDEFINEDtrue, false → BOOLEANLiteral Value Extraction:
// JUnit assertEquals patterns
assertEquals(15, add(5, 10)); // Extract: 5 (POSITIVE_SMALL), 10 (POSITIVE_SMALL)
assertThrows(ArithmeticException.class, () -> divide(10, 0)); // Extract: 0 (ZERO + ERROR_CONDITION)
// Variable assignments
int a = 100;
int b = 200;
int result = add(a, b); // Extract: 100 (POSITIVE_LARGE), 200 (POSITIVE_LARGE)
// Method calls
String result = process("hello"); // Extract: "hello" (NON_EMPTY_STRING)
Framework Patterns:
@Test annotations, assertEquals(), assertNull(), assertTrue()@Test, assertEquals(), assertThrows(), assertAll()test* or *Test methodsClassification:
"" → EMPTY_STRINGnull → NULL_UNDEFINEDtrue, false → BOOLEANLiteral Value Extraction:
// NUnit/xUnit patterns
Assert.AreEqual(15, Add(5, 10)); // Extract: 5 (POSITIVE_SMALL), 10 (POSITIVE_SMALL)
Assert.Throws<DivideByZeroException>(() => Divide(10, 0)); // Extract: 0 (ZERO + ERROR_CONDITION)
// Variable assignments
int a = 100;
int b = 200;
int result = Add(a, b); // Extract: 100 (POSITIVE_LARGE), 200 (POSITIVE_LARGE)
Framework Patterns:
[Test] attribute, Assert.AreEqual(), Assert.Throws()[Fact], [Theory], Assert.Equal(), Assert.Throws()[TestMethod], Assert.AreEqual()Classification: Same as Java (C# and Java have similar type systems)
Literal Value Extraction:
// testing package patterns
result := add(5, 10) // Extract: 5 (POSITIVE_SMALL), 10 (POSITIVE_SMALL)
if result != 15 {
t.Errorf("Expected 15, got %d", result)
}
// Error testing
_, err := divide(10, 0) // Extract: 0 (ZERO + ERROR_CONDITION)
if err == nil {
t.Error("Expected error, got nil")
}
Framework Patterns:
func TestXxx(t *testing.T), t.Errorf(), t.Fatalf()assert.Equal(), assert.NoError()Classification:
"" → EMPTY_STRINGnil → NULL_UNDEFINEDtrue, false → BOOLEANLiteral Value Extraction:
// Google Test patterns
EXPECT_EQ(15, add(5, 10)); // Extract: 5 (POSITIVE_SMALL), 10 (POSITIVE_SMALL)
EXPECT_THROW(divide(10, 0), std::runtime_error); // Extract: 0 (ZERO + ERROR_CONDITION)
// Variable assignments
int a = 100;
int b = 200;
int result = add(a, b); // Extract: 100 (POSITIVE_LARGE), 200 (POSITIVE_LARGE)
Framework Patterns:
TEST(), EXPECT_EQ(), ASSERT_EQ(), EXPECT_THROW()TEST_CASE(), REQUIRE(), CHECK()BOOST_AUTO_TEST_CASE(), BOOST_CHECK_EQUAL()Classification:
"" → EMPTY_STRINGnullptr, NULL → NULL_UNDEFINEDtrue, false → BOOLEANBoundary Values:
Error Conditions:
Precision Issues:
State Transitions:
Other Special Scenarios:
Happy Path Variations (different values in same equivalence class):
test_add(5, 10) vs test_add(100, 200) → BOTH are POSITIVE (redundant)test_subtract(-5, -3) vs test_subtract(-100, -50) → BOTH are NEGATIVE (redundant)test_process("hello") vs test_process("world") → BOTH are NON_EMPTY_STRING (redundant)test_sum([1, 2, 3]) vs test_sum([4, 5, 6]) → BOTH are NON_EMPTY_COLLECTION (redundant)Key Principle: If the only difference is the specific value (not the category), and no edge case class is present, the test is likely redundant.
Examples of Non-Edge Cases:
Follow this algorithm when generating each test to determine if it's redundant:
Actions:
test_* patternit() or test() blocks@Test annotation[Test], [Fact], or [TestMethod] attributesTest* patternTEST() or TEST_CASE() macrosOutput: List of existing tests with their equivalence classes
Actions:
Output: Proposed test with its equivalence classes and edge case flag
Actions:
Normalization Rationale: POSITIVE_SMALL (5) and POSITIVE_LARGE (500) are both positive numbers. The distinction helps identify them initially, but for redundancy detection they normalize to the same category.
Output: Match found (yes/no) with reference to matching existing test
Edge Case Override Logic:
Happy Path Logic:
Output: Allow or block decision
If Redundant (match found + no edge case override):
If Unique or Edge Case:
Use Message Template (see Message Templates section):
Output: Formatted redundancy message
Principle: When in doubt, allow the test. False negatives (allowing redundant tests) are better than false positives (blocking valid tests).
Apply Conservative Logic:
Target Metrics:
Use this three-part format when blocking a redundant test:
❌ **Test Generation Blocked: Redundant Test Scenario**
**Problem**: The proposed test '{proposed_test_name}' is redundant with existing test coverage.
**Explanation**: Both tests exercise '{function_name}' with {equivalence_class_description}. This scenario is already covered by '{existing_test_name}'.
**Suggestion**:
- Remove '{proposed_test_name}', OR
- Modify to test true edge cases: {alternative_suggestions}
Example edge cases for {function_name}:
- Zero values (0)
- Negative numbers (if not tested)
- Null/empty inputs (if applicable)
- Boundary conditions (MAX_INT, MIN_INT)
- Error conditions (overflow, underflow, division by zero)
{proposed_test_name}: Name of the proposed test being blocked{function_name}: Name of the function under test{equivalence_class_description}: Plain language explanation of equivalence classes (e.g., "positive numbers", "non-empty strings"){existing_test_name}: Name of the existing test that covers this scenario{alternative_suggestions}: Specific edge cases that could be tested instead❌ **Test Generation Blocked: Redundant Test Scenario**
**Problem**: The proposed test 'test_add_large_numbers' is redundant with existing test coverage.
**Explanation**: Both tests exercise 'add' with positive numbers. This scenario is already covered by 'test_add_positive_numbers'.
**Suggestion**:
- Remove 'test_add_large_numbers', OR
- Modify to test true edge cases: zero values, negative numbers, overflow conditions
Example edge cases for add:
- Zero values (0 + 5)
- Negative numbers (-5 + -3)
- Null/empty inputs (if applicable)
- Boundary conditions (MAX_INT + 1)
- Error conditions (overflow)
❌ **Test Generation Blocked: Redundant Test Scenario**
**Problem**: The proposed test 'test_process_another_string' is redundant with existing test coverage.
**Explanation**: Both tests exercise 'process' with non-empty strings. This scenario is already covered by 'test_process_string'.
**Suggestion**:
- Remove 'test_process_another_string', OR
- Modify to test true edge cases: empty string, null/undefined, special characters
Example edge cases for process:
- Zero values (not applicable for strings)
- Negative numbers (not applicable for strings)
- Null/empty inputs ("", null, undefined)
- Boundary conditions (very long strings)
- Error conditions (invalid encoding, special characters like emojis)
❌ **Test Generation Blocked: Redundant Test Scenario**
**Problem**: The proposed test 'testSumDifferentNumbers' is redundant with existing test coverage.
**Explanation**: Both tests exercise 'sum' with non-empty collections of positive numbers. This scenario is already covered by 'testSumPositiveNumbers'.
**Suggestion**:
- Remove 'testSumDifferentNumbers', OR
- Modify to test true edge cases: empty collection, null, negative numbers
Example edge cases for sum:
- Zero values (collection with zeros)
- Negative numbers ([-1, -2, -3])
- Null/empty inputs (null, empty list)
- Boundary conditions (very large collections)
- Error conditions (null elements in collection)
Use these suggestions based on what's already tested:
When POSITIVE is already tested:
When NON_EMPTY_STRING is already tested:
When NON_EMPTY_COLLECTION is already tested:
When NEGATIVE is already tested:
When BOOLEAN is already tested:
Step 1: Load Context and Plan
skills/redundancy-detection/SKILL.md at the start of test generation workflowStep 4: Generate Individual Test Methods
In Generation Summary: Add a "Redundant Tests Blocked" section showing:
Example Format:
## Test Generation Summary
**Tests Generated**: 8
**Tests Blocked**: 3 (redundant)
### Redundant Tests Blocked
- `test_add_large_numbers`: Redundant with test_add_positive_numbers (both test positive + positive)
- `test_process_another_string`: Redundant with test_process_string (both test non-empty strings)
- `test_sum_different_numbers`: Redundant with test_sum_positive_numbers (both test non-empty collections of positives)
### Tests Generated
1. test_add_positive_numbers ✓
2. test_add_with_zero ✓ (edge case: boundary value)
3. test_add_negative_numbers ✓
4. test_divide_by_zero ✓ (edge case: error condition)
5. test_process_string ✓
6. test_process_empty_string ✓ (edge case: boundary value)
7. test_sum_positive_numbers ✓
8. test_sum_empty_collection ✓ (edge case: boundary value)
No changes to Steps 1-3 (Load Context, Understand Source, Generate Structure)
Modified Step 4:
### Step 4: Generate Individual Test Methods
For each test scenario in test plan:
a. **Check redundancy** (apply redundancy-detection skill):
- Read target test file (if exists) to identify existing tests
- Classify proposed test inputs using equivalence class rules
- Compare against existing tests for same function
- Apply edge case override if proposed test is boundary/error condition
- If redundant: Skip generation, add to blocked tests list with message
- If unique or edge case: Proceed to generation
b. Generate test code using template and patterns
c. Apply mocking strategies if needed
d. Validate syntax and conventions
Modified Generation Summary (Step 7):
Null/Undefined Mapping:
None → NULL_UNDEFINEDnull, undefined → NULL_UNDEFINEDnull → NULL_UNDEFINEDnil → NULL_UNDEFINEDnull → NULL_UNDEFINEDnullptr, NULL → NULL_UNDEFINEDEmpty String Mapping:
"" → EMPTY_STRING'', `` → EMPTY_STRINGEmpty Collection Mapping:
[], (), {}, set() → EMPTY_COLLECTION[], {}, new Set(), new Map() → EMPTY_COLLECTIONnew ArrayList<>(), new int[0], Collections.emptyList() → EMPTY_COLLECTIONnil slice, empty slice []type{} → EMPTY_COLLECTIONnew List<T>(), new int[0] → EMPTY_COLLECTIONstd::vector<T>(), empty array {} → EMPTY_COLLECTIONBoundary Max Mapping:
sys.maxsize, float('inf') → BOUNDARY_MAXNumber.MAX_SAFE_INTEGER, Number.MAX_VALUE, Infinity → BOUNDARY_MAXInteger.MAX_VALUE, Long.MAX_VALUE, Double.MAX_VALUE → BOUNDARY_MAXmath.MaxInt64, math.Inf(1) → BOUNDARY_MAXint.MaxValue, long.MaxValue, double.MaxValue → BOUNDARY_MAXINT_MAX, LONG_MAX, DBL_MAX → BOUNDARY_MAXBoundary Min Mapping:
-sys.maxsize - 1, float('-inf') → BOUNDARY_MINNumber.MIN_SAFE_INTEGER, -Infinity → BOUNDARY_MINInteger.MIN_VALUE, Long.MIN_VALUE, Double.MIN_VALUE → BOUNDARY_MINmath.MinInt64, math.Inf(-1) → BOUNDARY_MINint.MinValue, long.MinValue, double.MinValue → BOUNDARY_MININT_MIN, LONG_MIN, DBL_MIN → BOUNDARY_MINTest Function/Method Naming:
def test_function_name():def test_function_name(self):it('should do something', () => {}) or test('description', () => {})@Test public void testFunctionName() or @Test void functionName()[Test] public void TestFunctionName()[Fact] public void FunctionName()func TestFunctionName(t *testing.T)TEST(TestSuiteName, TestName)Assertion Patterns:
assert function(args) == expectedself.assertEqual(function(args), expected)expect(function(args)).toBe(expected)assertEquals(expected, function(args))Assert.AreEqual(expected, function(args))Assert.Equal(expected, function(args))if got != expected { t.Errorf(...) }EXPECT_EQ(expected, function(args))Python:
assert statementsself.assert* methodstest_* naming conventionJavaScript/TypeScript:
expect() with matchers (.toBe(), .toEqual(), .toThrow())assert.*() or expect().to.* syntaxdescribe()/it() blocksJava:
@Test annotation, assert*() methods@Test, improved assertions like assertAll(), assertThrows()C#:
[Test] attribute, Assert.*() methods[Fact] attribute, Assert.*() methods[TestMethod] attribute, Assert.*() methodsGo:
Test* function naming, t.Error*() methodsassert.*() methodsC++:
TEST() macro, EXPECT_*() and ASSERT_*() macrosTEST_CASE() macro, REQUIRE() and CHECK() macrosBOOST_AUTO_TEST_CASE(), BOOST_CHECK_*() macrosWhen uncertain about language-specific behavior:
None vs JavaScript's null and undefined - if uncertain whether they're equivalent in a specific context, allow both testsDocument language-specific edge cases:
NaN, Python's -0), treat as EDGE_CASE_OTHERExisting Test:
def test_add_positive_numbers():
result = add(2, 3)
assert result == 5
add2 (POSITIVE_SMALL), 3 (POSITIVE_SMALL)Proposed Test:
def test_add_large_numbers():
result = add(100, 200)
assert result == 300
add100 (POSITIVE_LARGE), 200 (POSITIVE_LARGE)Existing Test:
def test_add_positive_numbers():
result = add(2, 3)
assert result == 5
Proposed Test:
def test_add_with_zero():
result = add(0, 5)
assert result == 5
add0 (ZERO), 5 (POSITIVE_SMALL)Existing Test:
def test_divide_positive_numbers():
result = divide(10, 2)
assert result == 5
Proposed Test:
def test_divide_by_zero():
with pytest.raises(ZeroDivisionError):
divide(10, 0)
divide10 (POSITIVE_SMALL), 0 (ZERO + ERROR_CONDITION)Existing Test:
def test_add_positive_numbers():
result = add(2, 3)
assert result == 5
Proposed Test:
def test_add_negative_numbers():
result = add(-5, -3)
assert result == -8
add-5 (NEGATIVE_SMALL), -3 (NEGATIVE_SMALL)Existing Test:
it('should process string', () => {
const result = process("hello");
expect(result).toBe("HELLO");
});
process"hello" (NON_EMPTY_STRING)Proposed Test:
it('should process another string', () => {
const result = process("world");
expect(result).toBe("WORLD");
});
process"world" (NON_EMPTY_STRING)Existing Test:
it('should process string', () => {
const result = process("hello");
expect(result).toBe("HELLO");
});
Proposed Test:
it('should handle empty string', () => {
const result = process("");
expect(result).toBe("");
});
process"" (EMPTY_STRING)Existing Test:
it('should divide numbers', () => {
const result = divide(10, 2);
expect(result).toBe(5);
});
Proposed Test:
it('should throw on division by zero', () => {
expect(() => divide(10, 0)).toThrow();
});
divide10 (POSITIVE_SMALL), 0 (ZERO + ERROR_CONDITION)Existing Test:
@Test
public void testSumPositiveNumbers() {
List<Integer> numbers = Arrays.asList(1, 2, 3);
int result = sum(numbers);
assertEquals(6, result);
}
sum[1, 2, 3] (NON_EMPTY_COLLECTION with POSITIVE values)Proposed Test:
@Test
public void testSumDifferentNumbers() {
List<Integer> numbers = Arrays.asList(4, 5, 6);
int result = sum(numbers);
assertEquals(15, result);
}
sum[4, 5, 6] (NON_EMPTY_COLLECTION with POSITIVE values)Existing Test:
@Test
public void testSumPositiveNumbers() {
List<Integer> numbers = Arrays.asList(1, 2, 3);
int result = sum(numbers);
assertEquals(6, result);
}
Proposed Test:
@Test
public void testSumEmptyCollection() {
List<Integer> numbers = new ArrayList<>();
int result = sum(numbers);
assertEquals(0, result);
}
sum[] (EMPTY_COLLECTION)Existing Test:
@Test
public void testProcessString() {
String result = process("hello");
assertEquals("HELLO", result);
}
Proposed Test:
@Test
public void testProcessNull() {
String result = process(null);
assertNull(result);
}
processnull (NULL_UNDEFINED)Scope:
@pytest.mark.parametrize)Classification Challenges:
random.randint()) cannot be classifiedConservative Approach:
Performance:
Validation Status: Awaiting execution of acceptance tests per TASK-005
Placeholder for Results:
To be updated after validation testing completes.
This skill provides write-agent with comprehensive guidance to:
Key Principles:
Integration: