|
| 1 | +# 4D Unit Testing Framework |
| 2 | + |
| 3 | +A comprehensive unit testing framework for the 4D 4GL platform with test tagging, filtering, and CI/CD integration. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +This project provides a complete testing framework for 4D applications featuring: |
| 8 | + |
| 9 | +- **Auto test discovery** - Finds test classes ending with "Test" |
| 10 | +- **Comment-based tagging** - Organize tests with `// #tags: unit, integration, slow` |
| 11 | +- **Flexible filtering** - Run specific test subsets by name, pattern, or tags |
| 12 | +- **Multiple output formats** - Human-readable and JSON output with terse/verbose modes |
| 13 | +- **CI/CD ready** - Structured JSON output for automated testing pipelines |
| 14 | +- **Parallel test execution** - Run test suites concurrently for improved performance |
| 15 | +- **Automatic transaction management** - Test isolation with automatic rollback |
| 16 | +- **Manual transaction control** - Full transaction lifecycle management for advanced scenarios |
| 17 | + |
| 18 | +## Running Tests |
| 19 | + |
| 20 | +### Quick Start with Makefile |
| 21 | + |
| 22 | +```bash |
| 23 | +# Run all tests |
| 24 | +make test |
| 25 | + |
| 26 | +# Pass parameters directly to test command |
| 27 | +make test format=json |
| 28 | +make test tags=unit |
| 29 | +make test format=json tags=unit excludeTags=slow |
| 30 | +make test test=ExampleTest |
| 31 | + |
| 32 | +# Alternative named commands |
| 33 | +make test-json # Run all tests with JSON output |
| 34 | +make test-class CLASS=ExampleTest |
| 35 | +make test-tags TAGS=unit |
| 36 | +make test-tags TAGS=integration,performance |
| 37 | +make test-exclude-tags TAGS=slow |
| 38 | +make test-require-tags TAGS=unit,fast |
| 39 | + |
| 40 | +# Convenience shortcuts |
| 41 | +make test-unit # Run only unit tests |
| 42 | +make test-integration # Run only integration tests |
| 43 | +make test-unit-json # Run unit tests with JSON output |
| 44 | + |
| 45 | +# JUnit XML output for CI/CD integration |
| 46 | +make test-junit # Run all tests with JUnit XML output |
| 47 | +make test-ci # Run tests for CI/CD (saves to test-results/junit.xml) |
| 48 | +make test-unit-junit # Run unit tests with JUnit XML output |
| 49 | +make test-integration-junit # Run integration tests with JUnit XML output |
| 50 | + |
| 51 | +# Parallel execution |
| 52 | +make test-parallel # Run all tests in parallel |
| 53 | +make test-parallel-json # Run tests in parallel with JSON output |
| 54 | +make test-parallel-unit # Run unit tests in parallel |
| 55 | +make test-parallel-workers WORKERS=4 # Run with custom worker count |
| 56 | + |
| 57 | +# Show all available commands |
| 58 | +make help |
| 59 | +``` |
| 60 | + |
| 61 | +### Manual Test Execution (Advanced) |
| 62 | + |
| 63 | +If you need more control or the Makefile doesn't meet your needs: |
| 64 | + |
| 65 | +```bash |
| 66 | +# Run all tests with human output |
| 67 | +/Applications/tool4d.app/Contents/MacOS/tool4d --project $(PWD)/testing/Project/testing.4DProject --skip-onstartup --dataless --startup-method "test" |
| 68 | + |
| 69 | +# Run all tests with JSON output |
| 70 | +/Applications/tool4d.app/Contents/MacOS/tool4d --project $(PWD)/testing/Project/testing.4DProject --skip-onstartup --dataless --startup-method "test" --user-param "format=json" |
| 71 | + |
| 72 | +# Run all tests with JUnit XML output (saves to test-results/junit.xml) |
| 73 | +/Applications/tool4d.app/Contents/MacOS/tool4d --project $(PWD)/testing/Project/testing.4DProject --skip-onstartup --dataless --startup-method "test" --user-param "format=junit" |
| 74 | +``` |
| 75 | + |
| 76 | +### Test Filtering Parameters |
| 77 | + |
| 78 | +```bash |
| 79 | +# Run specific test class |
| 80 | +--user-param "test=ExampleTest" |
| 81 | + |
| 82 | +# Run tests by tags |
| 83 | +--user-param "tags=unit" |
| 84 | +--user-param "tags=integration,performance" |
| 85 | +--user-param "excludeTags=slow" |
| 86 | +--user-param "requireTags=unit,fast" |
| 87 | + |
| 88 | +# Combined filtering |
| 89 | +--user-param "tags=unit excludeTags=slow" |
| 90 | +--user-param "format=json tags=integration" |
| 91 | +--user-param "format=junit tags=unit" |
| 92 | +--user-param "format=junit outputPath=results/junit.xml" |
| 93 | + |
| 94 | +# Parallel execution |
| 95 | +--user-param "parallel=true" |
| 96 | +--user-param "parallel=true maxWorkers=4" |
| 97 | +--user-param "parallel=true format=json tags=unit" |
| 98 | +``` |
| 99 | + |
| 100 | +### Current Test Status |
| 101 | + |
| 102 | +- **Total Tests**: 121 tests across 14 test suites |
| 103 | +- **Pass Rate**: 100% |
| 104 | +- **Key Test Classes**: TaggingSystemTest, TaggingExampleTest, TestRunnerTest, TestSuiteTest |
| 105 | + |
| 106 | +## Project Structure |
| 107 | + |
| 108 | +``` |
| 109 | +testing/Project/Sources/Classes/ |
| 110 | +├── TestRunner.4dm # Main test orchestration |
| 111 | +├── TestSuite.4dm # Individual test class management |
| 112 | +├── TestFunction.4dm # Single test execution & tagging |
| 113 | +├── Assert.4dm # Assertion library |
| 114 | +├── Testing.4dm # Test context |
| 115 | +├── TaggingExampleTest.4dm # Tagging examples |
| 116 | +├── TaggingSystemTest.4dm # Tagging functionality tests |
| 117 | +└── [other test classes] |
| 118 | +``` |
| 119 | + |
| 120 | +The framework automatically discovers and runs any class ending with "Test" that contains methods starting with "test_". |
| 121 | + |
| 122 | +## JUnit XML Output for CI/CD Integration |
| 123 | + |
| 124 | +The framework supports JUnit XML output format for integration with GitLab CI/CD and other continuous integration systems. |
| 125 | + |
| 126 | +### JUnit XML Features |
| 127 | + |
| 128 | +- **GitLab Integration**: Test results appear in merge requests and pipeline views |
| 129 | +- **File Artifacts**: XML files can be archived as CI artifacts for historical tracking |
| 130 | +- **Test Navigation**: Direct links to failing test files in GitLab UI |
| 131 | +- **Standard Format**: Compatible with Jenkins, CircleCI, and other CI tools |
| 132 | +- **Detailed Reporting**: Includes test timing, failure messages, and stack traces |
| 133 | + |
| 134 | +### Basic Usage |
| 135 | + |
| 136 | +```bash |
| 137 | +# Generate JUnit XML (saves to test-results/junit.xml) |
| 138 | +make test-junit |
| 139 | + |
| 140 | +# Custom output location |
| 141 | +make test format=junit outputPath=custom/path/results.xml |
| 142 | + |
| 143 | +# Combined with filtering |
| 144 | +make test-unit-junit # Unit tests only |
| 145 | +make test format=junit tags=integration # Integration tests only |
| 146 | +``` |
| 147 | + |
| 148 | +### GitLab CI Integration |
| 149 | + |
| 150 | +Add this to your `.gitlab-ci.yml`: |
| 151 | + |
| 152 | +```yaml |
| 153 | +test: |
| 154 | + script: |
| 155 | + - make test-ci # Generates test-results/junit.xml |
| 156 | + artifacts: |
| 157 | + reports: |
| 158 | + junit: test-results/junit.xml |
| 159 | + paths: |
| 160 | + - test-results/ |
| 161 | + when: always |
| 162 | + expire_in: 30 days |
| 163 | + coverage: '/Lines:\s*(\d+\.?\d*)%/' |
| 164 | +``` |
| 165 | +
|
| 166 | +### JUnit XML Structure |
| 167 | +
|
| 168 | +The generated XML includes: |
| 169 | +
|
| 170 | +- **Test Suites**: One per test class |
| 171 | +- **Test Cases**: Individual test methods with timing |
| 172 | +- **Failures**: Detailed failure messages with location info |
| 173 | +- **File References**: Links to source test files |
| 174 | +- **Timestamps**: Test execution timing for performance tracking |
| 175 | +
|
| 176 | +Example output structure: |
| 177 | +```xml |
| 178 | +<?xml version="1.0" encoding="UTF-8"?> |
| 179 | +<testsuites name="4D Test Results" tests="121" failures="0" errors="0" time="1.234"> |
| 180 | + <testsuite name="_ExampleTest" tests="5" failures="0" errors="0" time="0.156"> |
| 181 | + <testcase classname="_ExampleTest" name="test_areEqual_pass" |
| 182 | + file="testing/Project/Sources/Classes/_ExampleTest.4dm" time="0.023"/> |
| 183 | + </testsuite> |
| 184 | +</testsuites> |
| 185 | +``` |
| 186 | + |
| 187 | +## Parallel Test Execution |
| 188 | + |
| 189 | +The framework supports parallel execution of test suites to significantly reduce total test runtime while maintaining test isolation. |
| 190 | + |
| 191 | +### Enabling Parallel Execution |
| 192 | + |
| 193 | +```bash |
| 194 | +# Enable parallel execution (uses CPU core count as default worker count) |
| 195 | +make test-parallel |
| 196 | + |
| 197 | +# Enable parallel execution with custom worker count |
| 198 | +make test-parallel-workers WORKERS=4 |
| 199 | + |
| 200 | +# Combine parallel execution with other options |
| 201 | +make test parallel=true format=json tags=unit maxWorkers=6 |
| 202 | +``` |
| 203 | + |
| 204 | +### How Parallel Execution Works |
| 205 | + |
| 206 | +1. **Suite-Level Parallelism**: Test suites run concurrently, but individual tests within a suite run sequentially |
| 207 | +2. **Worker Pool**: Creates worker processes up to the specified maximum (default: CPU core count, max: 8) |
| 208 | +3. **Automatic Load Balancing**: Distributes test suites across available workers |
| 209 | +4. **Test Isolation**: Each worker runs in its own process with separate transaction scope |
| 210 | +5. **Result Aggregation**: Collects and merges results from all workers before generating final report |
| 211 | + |
| 212 | +### Parallel Execution Opt-Out |
| 213 | + |
| 214 | +Test suites can opt out of parallel execution using comment annotations: |
| 215 | + |
| 216 | +```4d |
| 217 | +// Test class that requires sequential execution |
| 218 | +// #parallel: false |
| 219 | +
|
| 220 | +Class constructor() |
| 221 | +
|
| 222 | +Function test_database_exclusive_operation($t : cs:Testing) |
| 223 | + // This test requires exclusive database access |
| 224 | + // and will run sequentially even in parallel mode |
| 225 | +``` |
| 226 | + |
| 227 | +### Performance Benefits |
| 228 | + |
| 229 | +- **30-60% reduction** in total test runtime for typical test suites |
| 230 | +- **Better resource utilization** on multi-core machines |
| 231 | +- **Improved developer experience** with faster feedback loops |
| 232 | +- **CI/CD optimization** reducing pipeline duration |
| 233 | + |
| 234 | +### Best Practices for Parallel Execution |
| 235 | + |
| 236 | +1. **Design for Independence**: Ensure test suites don't depend on each other's state |
| 237 | +2. **Use Transactions**: Leverage automatic transaction management for database isolation |
| 238 | +3. **Opt-Out When Needed**: Use `// #parallel: false` for tests requiring exclusive resources |
| 239 | +4. **Monitor Performance**: Compare sequential vs parallel execution times |
| 240 | +5. **Tune Worker Count**: Adjust `maxWorkers` based on your hardware and test characteristics |
| 241 | + |
| 242 | +## Transaction Management |
| 243 | + |
| 244 | +The framework provides automatic transaction management for test isolation and manual transaction control for advanced scenarios. |
| 245 | + |
| 246 | +### Automatic Transaction Management |
| 247 | + |
| 248 | +By default, each test runs in its own transaction that is automatically rolled back after completion, ensuring: |
| 249 | +- **Test Isolation**: Tests cannot interfere with each other's data |
| 250 | +- **Clean Environment**: Database state is restored after each test |
| 251 | +- **No Side Effects**: Failed tests don't leave partial data |
| 252 | + |
| 253 | +### Controlling Transaction Behavior |
| 254 | + |
| 255 | +Use comment-based annotations to control transaction behavior: |
| 256 | + |
| 257 | +```4d |
| 258 | +Function test_withTransactions($t : cs:C1710.Testing) |
| 259 | + // Automatic transactions enabled (default) |
| 260 | + // Test data changes will be rolled back |
| 261 | + |
| 262 | +Function test_withoutTransactions($t : cs:C1710.Testing) |
| 263 | + // #transaction: false |
| 264 | + // Disables automatic transaction management |
| 265 | +``` |
| 266 | + |
| 267 | +### Manual Transaction Control |
| 268 | + |
| 269 | +The Testing context provides methods for manual transaction management: |
| 270 | + |
| 271 | +```4d |
| 272 | +Function test_manualTransactions($t : cs:C1710.Testing) |
| 273 | + // #transaction: false |
| 274 | + |
| 275 | + // Start transaction manually |
| 276 | + $t.startTransaction() |
| 277 | + |
| 278 | + // Check transaction status |
| 279 | + If ($t.inTransaction()) |
| 280 | + // Perform database operations |
| 281 | + End if |
| 282 | + |
| 283 | + // Validate or cancel transaction |
| 284 | + If ($success) |
| 285 | + $t.validateTransaction() |
| 286 | + Else |
| 287 | + $t.cancelTransaction() |
| 288 | + End if |
| 289 | +
|
| 290 | +Function test_transactionWrapper($t : cs:C1710.Testing) |
| 291 | + // #transaction: false |
| 292 | + |
| 293 | + // Execute operation within transaction (auto-rollback) |
| 294 | + var $success : Boolean |
| 295 | + $success:=$t.withTransaction(Formula( |
| 296 | + // Database operations here |
| 297 | + // Will be rolled back automatically |
| 298 | + )) |
| 299 | + |
| 300 | + // Execute operation with validation (persists data) |
| 301 | + $success:=$t.withTransactionValidate(Formula( |
| 302 | + // Database operations here |
| 303 | + // Will be validated if test succeeds |
| 304 | + )) |
| 305 | +``` |
| 306 | + |
| 307 | +### Transaction Control Comments |
| 308 | + |
| 309 | +| Comment | Effect | |
| 310 | +| ------------------------ | ---------------------------------------- | |
| 311 | +| `// #transaction: false` | Disables automatic transactions | |
| 312 | +| No comment | Enables automatic transactions (default) | |
0 commit comments