Skip to content

Commit 9bfb90e

Browse files
abedatahubclaude
andauthored
docs(testing): add guidelines to avoid AI test anti-patterns (#15073)
Co-authored-by: Claude <[email protected]>
1 parent a865b6b commit 9bfb90e

File tree

1 file changed

+98
-0
lines changed

1 file changed

+98
-0
lines changed

CLAUDE.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,104 @@ connection_timeout = 30
155155
- Frontend: Tests in `__tests__/` or `.test.tsx` files
156156
- Smoke tests go in the `smoke-test/` directory
157157

158+
#### Testing Principles: Focus on Value Over Coverage
159+
160+
**IMPORTANT**: Quality over quantity. Avoid AI-generated test anti-patterns that create maintenance burden without providing real value.
161+
162+
**Focus on behavior, not implementation**:
163+
164+
- ✅ Test what the code does (business logic, edge cases that occur in production)
165+
- ❌ Don't test how it does it (implementation details, private fields via reflection)
166+
- ❌ Don't test third-party libraries work correctly (Spring, Micrometer, Kafka clients, etc.)
167+
- ❌ Don't test Java/Python language features (`synchronized` methods are thread-safe, `@Nonnull` parameters reject nulls)
168+
169+
**Avoid these specific anti-patterns**:
170+
171+
- ❌ Testing null inputs on `@Nonnull`/`@NonNull` annotated parameters
172+
- ❌ Verifying exact error message wording (creates brittleness during refactoring)
173+
- ❌ Testing every possible input variation (case sensitivity × whitespace × special chars = maintenance nightmare)
174+
- ❌ Using reflection to verify private implementation details
175+
- ❌ Redundant concurrency testing on `synchronized` methods
176+
- ❌ Testing obvious getter/setter behavior without business logic
177+
- ❌ Testing Lombok-generated code (`@Data`, `@Builder`, `@Value` classes) - you're testing Lombok's code generator, not your logic
178+
- ❌ Testing that annotations exist on classes - if required annotations are missing, the framework/compiler will fail at startup, not in your tests
179+
180+
**Appropriate test scope**:
181+
182+
- **Simple utilities** (enums, string parsing, formatters): ~50-100 lines of focused tests
183+
- Happy path for each method
184+
- One example of invalid input per method
185+
- Edge cases likely to occur in production
186+
- **Complex business logic**: Test proportional to risk and complexity
187+
- Integration points and system boundaries
188+
- Security-critical operations
189+
- Error handling for realistic failure scenarios
190+
- **Warning sign**: If tests are 5x+ the size of implementation, reconsider scope
191+
192+
**Examples of low-value tests to avoid**:
193+
194+
```java
195+
// ❌ BAD: Testing @Nonnull contract (framework's job)
196+
@Test
197+
public void testNullParameterThrowsException() {
198+
assertThrows(NullPointerException.class,
199+
() -> service.process(null)); // parameter is @Nonnull
200+
}
201+
202+
// ❌ BAD: Testing Lombok-generated code
203+
@Test
204+
public void testBuilderSetsAllFields() {
205+
MyConfig config = MyConfig.builder()
206+
.field1("value1")
207+
.field2("value2")
208+
.build();
209+
assertEquals(config.getField1(), "value1");
210+
assertEquals(config.getField2(), "value2");
211+
}
212+
213+
// ❌ BAD: Testing that annotations exist
214+
@Test
215+
public void testConfigurationAnnotations() {
216+
assertNotNull(MyConfig.class.getAnnotation(Configuration.class));
217+
assertNotNull(MyConfig.class.getAnnotation(ComponentScan.class));
218+
}
219+
// If @Configuration is missing, Spring won't load the context - you don't need a test for this
220+
221+
// ❌ BAD: Exact error message (brittle)
222+
assertEquals(exception.getMessage(),
223+
"Unsupported database type 'oracle'. Only PostgreSQL and MySQL variants are supported.");
224+
225+
// ❌ BAD: Redundant variations
226+
assertEquals(DatabaseType.fromString("postgresql"), DatabaseType.POSTGRES);
227+
assertEquals(DatabaseType.fromString("PostgreSQL"), DatabaseType.POSTGRES);
228+
assertEquals(DatabaseType.fromString("POSTGRESQL"), DatabaseType.POSTGRES);
229+
assertEquals(DatabaseType.fromString(" postgresql "), DatabaseType.POSTGRES);
230+
// ... 10 more case/whitespace variations
231+
232+
// ✅ GOOD: Focused behavioral test
233+
@Test
234+
public void testFromString_ValidInputsCaseInsensitive() {
235+
assertEquals(DatabaseType.fromString("postgresql"), DatabaseType.POSTGRES);
236+
assertEquals(DatabaseType.fromString("POSTGRESQL"), DatabaseType.POSTGRES);
237+
assertEquals(DatabaseType.fromString(" postgresql "), DatabaseType.POSTGRES);
238+
}
239+
240+
@Test
241+
public void testFromString_InvalidInputThrows() {
242+
assertThrows(IllegalArgumentException.class,
243+
() -> DatabaseType.fromString("oracle"));
244+
}
245+
246+
// ✅ GOOD: Testing YOUR custom validation logic on a Lombok class
247+
@Test
248+
public void testCustomValidation() {
249+
assertThrows(IllegalArgumentException.class,
250+
() -> MyConfig.builder().field1("invalid").build().validate());
251+
}
252+
```
253+
254+
**When in doubt**: Ask "Does this test protect against a realistic regression?" If not, skip it.
255+
158256
#### Security Testing: Configuration Property Classification
159257

160258
**Critical test**: `metadata-io/src/test/java/com/linkedin/metadata/system_info/collectors/PropertiesCollectorConfigurationTest.java`

0 commit comments

Comments
 (0)