0
# AST Transformations
1
2
Annotation-based test enhancements including the `@NotYetImplemented` transformation for test-driven development. Automatically inverts test results to support "failing tests first" development patterns.
3
4
## Capabilities
5
6
### @NotYetImplemented Annotation
7
8
Method annotation that inverts test case results - tests fail when they pass and pass when they fail. Ideal for test-driven development where you write failing tests first.
9
10
```groovy { .api }
11
/**
12
* Method annotation for inverting test case results
13
* Compatible with JUnit 3, 4, and 5
14
*/
15
@interface NotYetImplemented {
16
17
/**
18
* Custom exception class for unexpected passes
19
* Must have constructor accepting single String message
20
*/
21
Class<? extends AssertionError> exception() default AssertionError.class;
22
}
23
```
24
25
**Usage Examples:**
26
27
```groovy
28
import groovy.test.NotYetImplemented
29
import groovy.test.GroovyTestCase
30
31
class FeatureTest extends GroovyTestCase {
32
33
@NotYetImplemented
34
void testNewFeatureNotImplementedYet() {
35
// This test currently fails, which is expected
36
def result = myNewFeature()
37
assertEquals("expected behavior", result)
38
39
// When the feature is implemented, remove @NotYetImplemented
40
// If you forget, the test will fail to remind you
41
}
42
43
@NotYetImplemented(exception = MyCustomException)
44
void testWithCustomException() {
45
// If this test unexpectedly passes, throw MyCustomException
46
def result = anotherNewFeature()
47
assertTrue(result.isValid())
48
}
49
50
void testExistingFeature() {
51
// Normal test without annotation
52
def result = existingFeature()
53
assertNotNull(result)
54
}
55
}
56
```
57
58
**JUnit 4 Usage:**
59
60
```groovy
61
import groovy.test.NotYetImplemented
62
import org.junit.Test
63
import static groovy.test.GroovyAssert.*
64
65
class JUnit4FeatureTest {
66
67
@Test
68
@NotYetImplemented
69
void testNotImplementedFeature() {
70
// Currently fails - that's expected
71
def service = new FeatureService()
72
def result = service.newMethod()
73
assertEquals("expected", result)
74
}
75
76
@Test
77
void testImplementedFeature() {
78
// Normal passing test
79
def service = new FeatureService()
80
assertNotNull(service.existingMethod())
81
}
82
}
83
```
84
85
**JUnit 5 Usage:**
86
87
```groovy
88
import groovy.test.NotYetImplemented
89
import org.junit.jupiter.api.Test
90
import static org.junit.jupiter.api.Assertions.*
91
92
class JUnit5FeatureTest {
93
94
@Test
95
@NotYetImplemented
96
void testFutureFeature() {
97
// Implementation pending
98
def calculator = new Calculator()
99
assertEquals(42, calculator.complexCalculation())
100
}
101
}
102
```
103
104
### Legacy @NotYetImplemented (Deprecated)
105
106
The legacy annotation in `groovy.transform` package is deprecated. Use `groovy.test.NotYetImplemented` instead.
107
108
```groovy { .api }
109
/**
110
* Legacy annotation - use groovy.test.NotYetImplemented instead
111
* @deprecated use groovy.test.NotYetImplemented
112
*/
113
@Deprecated
114
@interface groovy.transform.NotYetImplemented {
115
// No attributes
116
}
117
```
118
119
## Behavior Details
120
121
### Test Inversion Logic
122
123
The `@NotYetImplemented` annotation works by:
124
125
1. **Catching test failures**: If the test method throws any exception, the annotation catches it and the test passes
126
2. **Detecting unexpected success**: If the test method completes without throwing an exception, the annotation throws an `AssertionError` (or custom exception)
127
3. **Preserving stack traces**: Original exception details are logged for debugging
128
129
### Exception Handling
130
131
```groovy
132
class NotYetImplementedTest extends GroovyTestCase {
133
134
@NotYetImplemented
135
void testCurrentlyFailing() {
136
// This assertion fails, so the test passes overall
137
assertEquals("not implemented", getFeatureResult())
138
}
139
140
@NotYetImplemented
141
void testUnexpectedlyPassing() {
142
// If this starts passing, you'll get an error like:
143
// "testUnexpectedlyPassing is marked as not yet implemented but passes unexpectedly"
144
assertTrue(true) // This would cause failure if feature is implemented
145
}
146
}
147
```
148
149
### Custom Exception Types
150
151
Use custom exceptions to distinguish between different types of not-yet-implemented features:
152
153
```groovy
154
class DatabaseFeatureException extends AssertionError {
155
DatabaseFeatureException(String message) {
156
super(message)
157
}
158
}
159
160
class ApiFeatureException extends AssertionError {
161
ApiFeatureException(String message) {
162
super(message)
163
}
164
}
165
166
class FeatureTest extends GroovyTestCase {
167
168
@NotYetImplemented(exception = DatabaseFeatureException)
169
void testDatabaseFeature() {
170
// Database-related feature not implemented
171
def result = database.advancedQuery()
172
assertNotNull(result)
173
}
174
175
@NotYetImplemented(exception = ApiFeatureException)
176
void testApiFeature() {
177
// API-related feature not implemented
178
def response = api.newEndpoint()
179
assertEquals(200, response.status)
180
}
181
}
182
```
183
184
## Integration with Test Suites
185
186
The annotation works seamlessly with all test suite types:
187
188
```groovy
189
// Works with GroovyTestSuite
190
java -Dtest=NotImplementedFeatureTest.groovy groovy.test.GroovyTestSuite
191
192
// Works with AllTestSuite
193
def suite = AllTestSuite.suite("test", "**/*Test.groovy")
194
// Tests with @NotYetImplemented will be included and behave correctly
195
```
196
197
## Development Workflow
198
199
Typical test-driven development workflow with `@NotYetImplemented`:
200
201
### 1. Write Failing Test
202
203
```groovy
204
class UserServiceTest extends GroovyTestCase {
205
206
@NotYetImplemented
207
void testUserPasswordReset() {
208
def service = new UserService()
209
def result = service.resetPassword("user@example.com")
210
211
assertTrue(result.success)
212
assertNotNull(result.resetToken)
213
assertEquals("user@example.com", result.email)
214
}
215
}
216
```
217
218
### 2. Run Tests (Should Pass)
219
220
The test fails because `resetPassword` is not implemented, but `@NotYetImplemented` inverts this to a pass.
221
222
### 3. Implement Feature
223
224
```groovy
225
class UserService {
226
def resetPassword(String email) {
227
// Implementation added
228
return [
229
success: true,
230
resetToken: generateToken(),
231
email: email
232
]
233
}
234
}
235
```
236
237
### 4. Remove Annotation
238
239
```groovy
240
class UserServiceTest extends GroovyTestCase {
241
242
// @NotYetImplemented - Remove this annotation
243
void testUserPasswordReset() {
244
def service = new UserService()
245
def result = service.resetPassword("user@example.com")
246
247
assertTrue(result.success)
248
assertNotNull(result.resetToken)
249
assertEquals("user@example.com", result.email)
250
}
251
}
252
```
253
254
### 5. Test Now Passes Normally
255
256
The test runs normally and passes, confirming the implementation works correctly.
257
258
## Error Messages
259
260
Clear error messages help identify when features are implemented:
261
262
```
263
testUserRegistration is marked as not yet implemented but passes unexpectedly
264
```
265
266
This error tells you:
267
1. Which test method is affected
268
2. That the feature appears to be working
269
3. You should remove the `@NotYetImplemented` annotation
270
271
## Compatibility Notes
272
273
- **JUnit 3**: Full support with `GroovyTestCase`
274
- **JUnit 4**: Full support with `@Test` methods
275
- **JUnit 5**: Full support with `@Test` methods
276
- **Spock**: Works with Spock test methods
277
- **TestNG**: Compatible with TestNG test methods
278
279
The annotation uses compile-time AST transformation, so it works regardless of the test framework used at runtime.