@@ -120,6 +120,16 @@ When adding or modifying tasks:
120120
121121### Testing
122122
123+ JD.Efcpt.Build uses ** TinyBDD** for behavior-driven testing. All tests follow a consistent Given-When-Then pattern.
124+
125+ #### Testing Framework
126+
127+ We use ** TinyBDD** for all tests (not traditional xUnit Arrange-Act-Assert). This provides:
128+ - ✅ Clear behavior specifications
129+ - ✅ Readable test scenarios
130+ - ✅ Consistent patterns across the codebase
131+ - ✅ Self-documenting tests
132+
123133#### Running Tests
124134
125135``` bash
@@ -129,47 +139,238 @@ dotnet test
129139# Run with detailed output
130140dotnet test -v detailed
131141
132- # Run specific test
133- dotnet test --filter " FullyQualifiedName~TestName"
142+ # Run specific test category
143+ dotnet test --filter " FullyQualifiedName~SchemaReader"
144+
145+ # Run with code coverage
146+ dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover
134147```
135148
136- #### Writing Tests
149+ #### Writing Tests with TinyBDD
150+
151+ ** Test Structure:**
152+
153+ ``` csharp
154+ using TinyBDD .Xunit ;
155+ using Xunit ;
156+
157+ [Feature (" Component: brief description of functionality" )]
158+ [Collection (nameof (AssemblySetup ))]
159+ public sealed class ComponentTests (ITestOutputHelper output ) : TinyBddXunitBase (output )
160+ {
161+ // Define state records
162+ private sealed record SetupState (
163+ string InputValue ,
164+ ITestOutputHelper Output );
165+
166+ private sealed record ExecutionResult (
167+ bool Success ,
168+ string Output ,
169+ Exception ? Error = null );
170+
171+ [Scenario (" Description of specific behavior" )]
172+ [Fact ]
173+ public async Task Scenario_Name ()
174+ {
175+ await Given (" context setup" , () => new SetupState (" test-value" , Output ))
176+ .When (" action is performed" , state => PerformAction (state ))
177+ .Then (" expected outcome occurs" , result => result .Success )
178+ .And (" additional assertion" , result => result .Output == " expected" )
179+ .Finally (result => CleanupResources (result ))
180+ .AssertPassed ();
181+ }
182+
183+ private static ExecutionResult PerformAction (SetupState state )
184+ {
185+ try
186+ {
187+ // Execute the action being tested
188+ var output = DoSomething (state .InputValue );
189+ return new ExecutionResult (true , output );
190+ }
191+ catch (Exception ex )
192+ {
193+ return new ExecutionResult (false , " " , ex );
194+ }
195+ }
196+
197+ private static void CleanupResources (ExecutionResult result )
198+ {
199+ // Clean up any resources
200+ }
201+ }
202+ ```
137203
138- - Add tests for new features
139- - Test both success and error scenarios
140- - Use descriptive test names: ` Should_ExpectedBehavior_When_Condition `
141- - Keep tests isolated and independent
142- - Mock external dependencies
204+ #### Testing Best Practices
143205
144- Example test structure:
206+ ** DO:**
207+ - ✅ Use TinyBDD for all new tests
208+ - ✅ Write descriptive scenario names (e.g., "Should detect changed fingerprint when DACPAC modified")
209+ - ✅ Use state records for Given context
210+ - ✅ Use result records for When outcomes
211+ - ✅ Test both success and failure paths
212+ - ✅ Clean up resources in ` Finally ` blocks
213+ - ✅ Use meaningful assertion messages
214+
215+ ** DON'T:**
216+ - ❌ Use traditional Arrange-Act-Assert (use Given-When-Then)
217+ - ❌ Skip the ` Finally ` block if cleanup is needed
218+ - ❌ Write tests without clear scenarios
219+ - ❌ Test implementation details (test behavior)
220+ - ❌ Create inter-dependent tests
221+
222+ #### Testing Patterns
223+
224+ ** Pattern 1: Simple Value Transformation**
145225
146226``` csharp
227+ [Scenario (" Should compute fingerprint from byte array" )]
147228[Fact ]
148- public void Should_StageTemplates_When_TemplateDirectoryExists ()
229+ public async Task Computes_fingerprint_from_bytes ()
149230{
150- // Arrange
151- var task = new StageEfcptInputs
152- {
153- OutputDir = testDir ,
154- TemplateDir = sourceTemplateDir ,
155- // ... other properties
156- };
231+ await Given ( " byte array with known content " , () => new byte [] { 1 , 2 , 3 , 4 })
232+ . When ( " computing fingerprint " , bytes => ComputeFingerprint ( bytes ))
233+ . Then ( " fingerprint is deterministic " , fp => ! string . IsNullOrEmpty ( fp ))
234+ . And ( " fingerprint has expected format " , fp => fp . Length == 16 )
235+ . AssertPassed ();
236+ }
237+ ```
157238
158- // Act
159- var result = task .Execute ();
239+ ** Pattern 2: File System Operations**
160240
161- // Assert
162- Assert .True (result );
163- Assert .True (Directory .Exists (expectedStagedPath ));
241+ ``` csharp
242+ [Scenario (" Should create output directory when it doesn't exist" )]
243+ [Fact ]
244+ public async Task Creates_missing_output_directory ()
245+ {
246+ await Given (" non-existent directory path" , () =>
247+ {
248+ var path = Path .Combine (Path .GetTempPath (), Guid .NewGuid ().ToString ());
249+ return new SetupState (path , Output );
250+ })
251+ .When (" ensuring directory exists" , state =>
252+ {
253+ Directory .CreateDirectory (state .Path );
254+ return new Result (Directory .Exists (state .Path ), state .Path );
255+ })
256+ .Then (" directory is created" , result => result .Exists )
257+ .Finally (result =>
258+ {
259+ if (Directory .Exists (result .Path ))
260+ Directory .Delete (result .Path , true );
261+ })
262+ .AssertPassed ();
164263}
165264```
166265
266+ ** Pattern 3: Exception Testing**
267+
268+ ``` csharp
269+ [Scenario (" Should throw when connection string is invalid" )]
270+ [Fact ]
271+ public async Task Throws_on_invalid_connection_string ()
272+ {
273+ await Given (" invalid connection string" , () => " not-a-valid-connection-string" )
274+ .When (" reading schema" , connectionString =>
275+ {
276+ try
277+ {
278+ reader .ReadSchema (connectionString );
279+ return (false , null as Exception );
280+ }
281+ catch (Exception ex )
282+ {
283+ return (true , ex );
284+ }
285+ })
286+ .Then (" exception is thrown" , result => result .Item1 )
287+ .And (" exception message is descriptive" , result =>
288+ result .Item2 ! .Message .Contains (" connection" ) ||
289+ result .Item2 ! .Message .Contains (" invalid" ))
290+ .AssertPassed ();
291+ }
292+ ```
293+
294+ ** Pattern 4: Integration Tests with Testcontainers**
295+
296+ ``` csharp
297+ [Feature (" PostgreSqlSchemaReader: integration with real database" )]
298+ [Collection (nameof (PostgreSqlContainer ))]
299+ public sealed class PostgreSqlSchemaIntegrationTests (
300+ PostgreSqlFixture fixture ,
301+ ITestOutputHelper output ) : TinyBddXunitBase (output )
302+ {
303+ [Scenario (" Should read schema from PostgreSQL database" )]
304+ [Fact ]
305+ public async Task Reads_schema_from_postgres ()
306+ {
307+ await Given (" PostgreSQL database with test schema" , () => fixture .ConnectionString )
308+ .When (" reading schema" , cs => new PostgreSqlSchemaReader ().ReadSchema (cs ))
309+ .Then (" schema contains expected tables" , schema => schema .Tables .Count > 0 )
310+ .And (" tables have columns" , schema => schema .Tables .All (t => t .Columns .Any ()))
311+ .AssertPassed ();
312+ }
313+ }
314+ ```
315+
316+ #### Test Coverage Goals
317+
318+ | Component | Target | Current |
319+ | -----------| --------| ---------|
320+ | ** MSBuild Tasks** | 95%+ | ~ 90% |
321+ | ** Schema Readers** | 90%+ | ~ 85% |
322+ | ** Resolution Chains** | 90%+ | ~ 88% |
323+ | ** Utilities** | 85%+ | ~ 82% |
324+
325+ #### Integration Testing
326+
327+ ** Database Provider Tests:**
328+ - Use Testcontainers for SQL Server, PostgreSQL, MySQL
329+ - Use in-memory SQLite for fast tests
330+ - Mock unavailable providers (Snowflake requires LocalStack Pro)
331+
332+ ** Sample Projects:**
333+ - Create minimal test projects in ` tests/TestAssets/ `
334+ - Test actual MSBuild integration
335+ - Verify generated code compiles
336+
337+ #### Running Integration Tests
338+
339+ ``` bash
340+ # Requires Docker for Testcontainers
341+ docker info
342+
343+ # Run integration tests
344+ dotnet test --filter " Category=Integration"
345+
346+ # Run specific provider tests
347+ dotnet test --filter " FullyQualifiedName~PostgreSql"
348+ ```
349+
350+ #### Debugging Tests
351+
352+ ``` csharp
353+ // TinyBDD provides detailed output on failure
354+ await Given (" setup" , CreateSetup )
355+ .When (" action" , Execute )
356+ .Then (" assertion" , result => result .IsValid )
357+ .AssertPassed ();
358+
359+ // On failure, you'll see:
360+ // ❌ Scenario failed at step: Then "assertion"
361+ // Expected: True
362+ // Actual: False
363+ // State: { ... }
364+ ```
365+
366+ For more details, see [ TinyBDD documentation] ( https://github.com/ledjon-behluli/TinyBDD ) .
367+
167368### Documentation
168369
169370When contributing, please update:
170371
171372- ** README.md** - For user-facing features
172- - ** QUICKSTART.md ** - For common usage scenarios
373+ - ** docs/ ** - For detailed documentation in docs/user-guide/
173374- ** XML comments** - For all public APIs
174375- ** Code comments** - For complex logic
175376
@@ -238,7 +439,7 @@ Maintainers handle releases using this process:
238439
239440- ** GitHub Issues** - For bugs and feature requests
240441- ** GitHub Discussions** - For questions and community support
241- - ** Documentation** - Check README.md and QUICKSTART.md first
442+ - ** Documentation** - Check README.md and docs/user-guide/ first
242443
243444## Recognition
244445
0 commit comments