Testing¶
Guide to testing the SQA CLI application.
Test Framework¶
SQA CLI uses Minitest as its testing framework, chosen for:
- Simple, intuitive syntax
- Fast execution
- Part of Ruby standard library
- Less DSL "magic" than alternatives
Running Tests¶
Run All Tests¶
Expected output:
# Running tests with run options --seed 29814:
........
Finished tests in 0.000414s, 19323.6711 tests/s, 50724.6366 assertions/s.
8 tests, 21 assertions, 0 failures, 0 errors, 0 skips
Run Specific Test File¶
Run with Verbose Output¶
Test Structure¶
Directory Layout¶
test/
├── test_helper.rb # Test configuration
└── sqa/
└── cli/
├── version_test.rb # Version tests
└── dispatcher_test.rb # Dispatcher tests
Test Helper¶
The test_helper.rb configures the test environment:
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
require "sqa"
require "minitest/autorun"
require "minitest/reporters"
Minitest::Reporters.use! [
Minitest::Reporters::DefaultReporter.new(color: true, slow_count: 5)
]
Features: - Colored output - Slow test reporting - Auto-discovery
Test Coverage¶
Current Coverage¶
Version Module: - ✅ VERSION constant exists - ✅ VERSION format (semantic versioning)
Dispatcher: - ✅ No arguments shows help - ✅ Help command works - ✅ Version command works - ✅ Unknown command shows error - ✅ Returns correct exit codes
Coverage Statistics¶
Writing Tests¶
Basic Test Structure¶
require "test_helper"
class SQA::CLI::MyCommandTest < Minitest::Test
def setup
# Runs before each test
@command = SQA::CLI::Commands::MyCommand.new([])
end
def teardown
# Runs after each test (if needed)
end
def test_something_works
# Test implementation
assert_equal expected, actual
end
end
Example: Version Test¶
class SQA::CLI::VersionTest < Minitest::Test
def test_version_exists
refute_nil SQA::CLI::VERSION
end
def test_version_format
assert_match(/\d+\.\d+\.\d+/, SQA::CLI::VERSION)
end
end
Example: Dispatcher Test¶
class SQA::CLI::DispatcherTest < Minitest::Test
def test_run_with_no_arguments_shows_help
output = capture_io do
result = SQA::CLI::Dispatcher.run([])
assert_equal 0, result
end
assert_match(/Usage:/, output.join)
end
def test_run_with_unknown_command
output = capture_io do
result = SQA::CLI::Dispatcher.run(['unknown'])
assert_equal 2, result
end
assert_match(/Unknown command/, output.join)
end
end
Testing Helpers¶
Capture Output¶
Minitest provides capture_io to test output:
def test_output
output = capture_io do
puts "Hello"
end
assert_equal "Hello\n", output[0] # stdout
assert_equal "", output[1] # stderr
end
Assertions¶
Common Minitest assertions:
# Equality
assert_equal expected, actual
refute_equal expected, actual
# Truthiness
assert condition
refute condition
assert_nil value
refute_nil value
# Pattern matching
assert_match /pattern/, string
refute_match /pattern/, string
# Inclusion
assert_includes collection, item
refute_includes collection, item
# Exceptions
assert_raises(ExceptionClass) { code }
# Type checking
assert_instance_of Class, object
assert_kind_of Class, object
Test Categories¶
Unit Tests¶
Test individual components in isolation:
def test_load_stock
command = Commands::Analyze.new(['--ticker', 'AAPL'])
stock = command.send(:load_stock)
assert_instance_of SQA::Stock, stock
assert_equal 'AAPL', stock.ticker
end
Integration Tests¶
Test components working together:
def test_analyze_command_integration
output = capture_io do
result = Dispatcher.run(['analyze', '--ticker', 'AAPL', '--methods', 'regime'])
assert_equal 0, result
end
assert_match(/Regime Analysis/, output.join)
end
End-to-End Tests¶
Test complete user workflows:
def test_full_backtest_workflow
output = capture_io do
# Run backtest
result = Dispatcher.run(['backtest', '--ticker', 'AAPL', '--strategy', 'RSI'])
assert_equal 0, result
end
# Verify output contains expected sections
assert_match(/Backtest Results/, output.join)
assert_match(/Total Return/, output.join)
assert_match(/Sharpe Ratio/, output.join)
end
Fixtures and Test Data¶
Sample Data¶
Tests use the data in data/ directory:
def test_with_sample_data
# Stock data available at data/stocks/AAPL.csv
# Portfolio data at data/portfolios/my_portfolio.csv
end
Creating Test Fixtures¶
For unit tests that don't need real data:
Mocking and Stubbing¶
Use Minitest's built-in mocking:
def test_with_mock
mock_stock = Minitest::Mock.new
mock_stock.expect(:ticker, 'AAPL')
mock_stock.expect(:data, @test_data)
# Use mock in test
assert_equal 'AAPL', mock_stock.ticker
# Verify expectations
mock_stock.verify
end
Continuous Integration¶
The test suite runs automatically on:
- Every commit to main branch
- Every pull request
- Manual workflow dispatch
See .github/workflows/ for CI configuration.
Test Performance¶
Slow Tests¶
Tests taking >0.001s are highlighted:
Optimization Tips¶
-
Avoid repeated file I/O:
-
Use smaller datasets:
-
Mock expensive operations:
Code Coverage¶
To measure code coverage (future enhancement):
# Install simplecov
gem install simplecov
# Add to test_helper.rb
require 'simplecov'
SimpleCov.start
# Run tests
bundle exec rake test
# View coverage report
open coverage/index.html
Test-Driven Development¶
Recommended TDD workflow:
-
Write failing test:
-
Run test (should fail):
-
Implement feature:
-
Run test (should pass):
-
Refactor while keeping tests green.
Common Testing Patterns¶
Testing CLI Output¶
def test_cli_output
output = capture_io do
CLI.run(['command', 'args'])
end
stdout, stderr = output
assert_match(/Expected/, stdout)
assert_empty stderr
end
Testing Exit Codes¶
Testing Option Parsing¶
def test_option_parsing
command = Command.new(['--option', 'value'])
assert_equal 'value', command.options[:option]
end
Future Testing Enhancements¶
Planned improvements:
- Integration Tests:
- Test each command with real data
- Verify output formatting
-
Check error handling
-
Performance Tests:
- Benchmark key operations
- Ensure no regression
-
Profile memory usage
-
Property-Based Testing:
- Generate random inputs
- Verify invariants
-
Find edge cases
-
Contract Tests:
- Verify SQA gem interface
- Check backward compatibility
- Ensure data formats
Troubleshooting Tests¶
Tests Fail Locally¶
-
Check Ruby version:
-
Update dependencies:
-
Check SQA gem:
Tests Pass Locally but Fail in CI¶
- Check for environment-specific code
- Verify file paths are relative
- Check for time-dependent tests
- Review CI logs for details
Slow Tests¶
-
Profile tests:
-
Look for:
- File I/O in loops
- Network calls
-
Large data processing
-
Optimize or mock expensive operations
Resources¶
See Also¶
- Architecture - System architecture
- Contributing - Development guide
- CLI Reference - Command documentation