Testing Guide¶
Substation includes a comprehensive test suite covering all major components. This guide explains how to run tests, write new tests, and understand the testing infrastructure.
Quick Start¶
# Run all tests
~/.swiftly/bin/swift test
# Run tests in parallel (faster)
~/.swiftly/bin/swift test --parallel
# Run with verbose output
~/.swiftly/bin/swift test -v
Test Suites¶
Substation currently has 36+ tests organized into four test suites:
OSClientTests¶
Tests for the OpenStack API client library.
Coverage:
- Configuration initialization
- Password authentication with names and IDs
- Application credential authentication
- Mixed authentication methods
- Credential validation
Run only OSClient tests:
SubstationTests¶
Tests for cloud configuration, YAML parsing, and authentication management.
Coverage:
- YAML value processing (quotes, escapes, environment variables)
- Cloud configuration parsing
- Multiple cloud configurations
- Authentication method determination
- Configuration validation
- Secure credential storage
- Region auto-detection
- Error handling and recovery
Run only Substation tests:
Specific test classes:
MemoryManagementTests¶
Tests for memory safety, leak detection, and proper resource cleanup.
Coverage:
- Client cleanup without crashes
- Concurrent requests with cancellation
- Multiple client creation and cleanup
- Network failure recovery
- Task cancellation handling
- URLSession delegate retention
- URLSession invalidation on deinit
- Weak self in task capture
Run only memory tests:
TUITests¶
Tests for terminal UI components and utilities.
Coverage:
- Filter line matching
- Query handling
Run only TUI tests:
Test Commands¶
Basic Testing¶
# Run all tests
~/.swiftly/bin/swift test
# Run with verbose output (shows all test names)
~/.swiftly/bin/swift test -v
# Run tests in parallel for faster execution
~/.swiftly/bin/swift test --parallel
Filtering Tests¶
# Run specific test suite
~/.swiftly/bin/swift test --filter OSClientTests
# Run specific test class
~/.swiftly/bin/swift test --filter EnhancedCloudConfigTests
# Run specific test method
~/.swiftly/bin/swift test --filter EnhancedCloudConfigTests.testBasicCloudsParsing
Code Coverage¶
Generate coverage report (LCOV format)¶
# Note: Use Swift toolchain's llvm-cov to avoid version mismatches
~/.swiftly/bin/llvm-cov export \
.build/debug/substationPackageTests.xctest/Contents/MacOS/substationPackageTests \
-instr-profile=.build/debug/codecov/default.profdata \
-format=lcov > coverage.lcov
View coverage summary¶
~/.swiftly/bin/llvm-cov report \
.build/debug/substationPackageTests.xctest/Contents/MacOS/substationPackageTests \
-instr-profile=.build/debug/codecov/default.profdata
Generate HTML coverage report¶
~/.swiftly/bin/llvm-cov show \
.build/debug/substationPackageTests.xctest/Contents/MacOS/substationPackageTests \
-instr-profile=.build/debug/codecov/default.profdata \
-format=html -output-dir=coverage-html
Logging and Debugging¶
# Save test output to log file
~/.swiftly/bin/swift test 2>&1 | tee .build/test.log
# Run with debug logging
~/.swiftly/bin/swift test -v 2>&1 | tee .build/test-debug.log
# Show only test summary
~/.swiftly/bin/swift test 2>&1 | grep "Test Suite"
Writing Tests¶
Test File Structure¶
Test files are organized by component:
Tests/
├── OSClientTests/
│ ├── OSClientTests.swift
│ └── MemoryManagementTests.swift
├── SubstationTests/
│ └── EnhancedCloudConfigTests.swift
└── TUITests/
└── TUITests.swift
Basic Test Template¶
import XCTest
@testable import YourModule
final class YourTests: XCTestCase {
// Test method - must start with "test"
func testSomething() {
// Arrange
let input = "test"
// Act
let result = processInput(input)
// Assert
XCTAssertEqual(result, "expected")
}
// Async test
func testAsyncOperation() async throws {
let result = try await performAsyncOperation()
XCTAssertNotNil(result)
}
}
Test Assertions¶
Common XCTest assertions:
// Equality
XCTAssertEqual(actual, expected)
XCTAssertNotEqual(actual, expected)
// Nil checking
XCTAssertNil(value)
XCTAssertNotNil(value)
// Boolean
XCTAssertTrue(condition)
XCTAssertFalse(condition)
// Error handling
XCTAssertThrowsError(try riskyOperation())
XCTAssertNoThrow(try safeOperation())
// Failure
XCTFail("Test failed with custom message")
Async Testing¶
func testAsyncOperation() async throws {
let manager = AuthenticationManager()
let result = await manager.determineAuthMethod(from: config)
XCTAssertNotNil(result)
}
func testThrowingAsyncOperation() async throws {
let parser = EnhancedYAMLParser()
let config = try await parser.parse(data)
XCTAssertEqual(config.clouds.count, 1)
}
Actor Testing¶
func testActorOperation() async {
let storage = SecureCredentialStorage()
// Store value
try await storage.store("secret", for: "key")
// Retrieve value
let retrieved = try await storage.retrieve(for: "key")
XCTAssertEqual(retrieved, "secret")
}
Continuous Integration¶
All tests run automatically via GitHub Actions on every push and pull request.
CI Workflow¶
The CI pipeline includes three workflows:
- tests.yml - Basic test execution
- build.yml - Build verification in debug and release
- ci.yml - Comprehensive CI with coverage
Workflow Triggers¶
Tests run on:
- Push to
main
ordevelop
branches - Pull requests to
main
ordevelop
branches
CI Requirements¶
- All tests must pass
- Build must complete with zero warnings
- Code must compile in both debug and release configurations
Viewing CI Results¶
- Go to the Actions tab in GitHub
- Click on a workflow run
- View test results and logs
- Download artifacts (build logs, test results, coverage reports)
Test Best Practices¶
1. Test Naming¶
Use descriptive test names that explain what is being tested:
// Good
func testPasswordAuthMethodDetermination() async { }
func testApplicationCredentialParsing() async throws { }
// Bad
func testAuth() { }
func test1() { }
2. Arrange-Act-Assert Pattern¶
Organize tests into three clear sections:
func testExample() {
// Arrange - Set up test data
let input = "test"
let expected = "result"
// Act - Execute the code under test
let actual = process(input)
// Assert - Verify the result
XCTAssertEqual(actual, expected)
}
3. Test Independence¶
Each test should be independent and not rely on other tests:
// Good - Self-contained test
func testUserCreation() async throws {
let storage = SecureCredentialStorage()
try await storage.store("value", for: "key")
let result = try await storage.retrieve(for: "key")
XCTAssertEqual(result, "value")
}
// Bad - Relies on previous test state
func testUserRetrieval() async throws {
// Assumes data was stored by another test
let result = try await storage.retrieve(for: "key")
XCTAssertEqual(result, "value")
}
4. Use Meaningful Assertions¶
Provide context in assertion messages:
// Good
XCTAssertEqual(
config.clouds.count,
2,
"Expected 2 clouds in configuration"
)
// Acceptable
XCTAssertEqual(config.clouds.count, 2)
5. Test Edge Cases¶
Don't just test the happy path:
func testEdgeCases() async throws {
// Empty input
let emptyResult = try await parser.parse(emptyData)
XCTAssertEqual(emptyResult.clouds.count, 0)
// Invalid input
XCTAssertThrowsError(try await parser.parse(invalidData))
// Nil handling
let nilResult = await storage.retrieve(for: "nonexistent")
XCTAssertNil(nilResult)
}
6. Clean Up Resources¶
Use defer or tearDown for cleanup:
func testWithTempFile() throws {
let tempFile = createTempFile()
defer { try? FileManager.default.removeItem(at: tempFile) }
// Test code using tempFile
}
Troubleshooting¶
Tests Not Running¶
Problem: swift test
hangs or doesn't execute
Solution:
# Clean build artifacts
rm -rf .build/
# Rebuild and test
~/.swiftly/bin/swift build
~/.swiftly/bin/swift test
Compilation Errors¶
Problem: Tests don't compile
Solution:
- Check that all test dependencies are available
- Ensure
@testable import
statements are correct - Verify Swift version compatibility (requires Swift 6.1+)
Test Failures¶
Problem: Tests fail unexpectedly
Solution:
- Run tests with verbose output:
swift test -v
- Check test logs:
.build/test.log
- Run specific failing test:
swift test --filter FailingTest
- Add debug print statements to tests
Memory Issues¶
Problem: Tests crash or run out of memory
Solution:
# Run tests sequentially (not parallel)
~/.swiftly/bin/swift test
# Monitor memory during tests
top -pid $(pgrep swift-testing)
Test Coverage Goals¶
Current test coverage:
- OSClient: Core functionality covered
- Substation: Configuration and authentication covered
- MemoryKit: Memory management covered
- SwiftTUI: Basic components covered
Coverage Targets¶
- Minimum 70% line coverage for all modules
- 100% coverage for critical paths (authentication, caching)
- All public APIs should have tests
Measuring Coverage¶
# Generate coverage report
~/.swiftly/bin/swift test --enable-code-coverage
# View coverage summary
~/.swiftly/bin/llvm-cov report \
.build/arm64-apple-macosx/debug/substationPackageTests.xctest/Contents/MacOS/substationPackageTests \
-instr-profile=.build/arm64-apple-macosx/debug/codecov/default.profdata
# View detailed coverage
~/.swiftly/bin/llvm-cov show \
.build/arm64-apple-macosx/debug/substationPackageTests.xctest/Contents/MacOS/substationPackageTests \
-instr-profile=.build/arm64-apple-macosx/debug/codecov/default.profdata
Next Steps¶
- Developer Guide - Contributing guidelines
- API Reference - Library API documentation
- Architecture - System design overview