14 KiB
Cross-Language Test Strategy Adaptation
Version: 2.0 Source: Bootstrap-002 Test Strategy Development Last Updated: 2025-10-18
This document provides guidance for adapting test patterns and methodology to different programming languages and frameworks.
Transferability Overview
Universal Concepts (100% Transferable)
The following concepts apply to ALL languages:
- Coverage-Driven Workflow: Analyze → Prioritize → Test → Verify
- Priority Matrix: P1 (error handling) → P4 (infrastructure)
- Pattern-Based Testing: Structured approaches to common scenarios
- Table-Driven Approach: Multiple scenarios with shared logic
- Error Path Testing: Systematic edge case coverage
- Dependency Injection: Mock external dependencies
- Quality Standards: Test structure and best practices
- TDD Cycle: Red-Green-Refactor
Language-Specific Elements (Require Adaptation)
- Syntax and Imports: Language-specific
- Testing Framework APIs: Different per ecosystem
- Coverage Tool Commands: Language-specific tools
- Mock Implementation: Different mocking libraries
- Build/Run Commands: Different toolchains
Go → Python Adaptation
Transferability: 80-90%
Testing Framework Mapping
| Go Concept | Python Equivalent |
|---|---|
testing package |
unittest or pytest |
t.Run() subtests |
pytest parametrize or unittest subtests |
t.Helper() |
pytest fixtures |
t.Cleanup() |
pytest fixtures with yield or unittest tearDown |
| Table-driven tests | @pytest.mark.parametrize |
Pattern Adaptations
Pattern 1: Unit Test
Go:
func TestFunction(t *testing.T) {
result := Function(input)
if result != expected {
t.Errorf("got %v, want %v", result, expected)
}
}
Python (pytest):
def test_function():
result = function(input)
assert result == expected, f"got {result}, want {expected}"
Python (unittest):
class TestFunction(unittest.TestCase):
def test_function(self):
result = function(input)
self.assertEqual(result, expected)
Pattern 2: Table-Driven Test
Go:
func TestFunction(t *testing.T) {
tests := []struct {
name string
input int
expected int
}{
{"case1", 1, 2},
{"case2", 2, 4},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Function(tt.input)
if result != tt.expected {
t.Errorf("got %v, want %v", result, tt.expected)
}
})
}
}
Python (pytest):
@pytest.mark.parametrize("input,expected", [
(1, 2),
(2, 4),
])
def test_function(input, expected):
result = function(input)
assert result == expected
Python (unittest):
class TestFunction(unittest.TestCase):
def test_cases(self):
cases = [
("case1", 1, 2),
("case2", 2, 4),
]
for name, input, expected in cases:
with self.subTest(name=name):
result = function(input)
self.assertEqual(result, expected)
Pattern 6: Dependency Injection (Mocking)
Go:
type Executor interface {
Execute(args Args) (Result, error)
}
type MockExecutor struct {
Results map[string]Result
}
func (m *MockExecutor) Execute(args Args) (Result, error) {
return m.Results[args.Key], nil
}
Python (unittest.mock):
from unittest.mock import Mock, MagicMock
def test_process():
mock_executor = Mock()
mock_executor.execute.return_value = expected_result
result = process_data(mock_executor)
assert result == expected
mock_executor.execute.assert_called_once()
Python (pytest-mock):
def test_process(mocker):
mock_executor = mocker.Mock()
mock_executor.execute.return_value = expected_result
result = process_data(mock_executor)
assert result == expected
Coverage Tools
Go:
go test -coverprofile=coverage.out ./...
go tool cover -func=coverage.out
go tool cover -html=coverage.out
Python (pytest-cov):
pytest --cov=package --cov-report=term
pytest --cov=package --cov-report=html
pytest --cov=package --cov-report=term-missing
Python (coverage.py):
coverage run -m pytest
coverage report
coverage html
Go → JavaScript/TypeScript Adaptation
Transferability: 75-85%
Testing Framework Mapping
| Go Concept | JavaScript/TypeScript Equivalent |
|---|---|
testing package |
Jest, Mocha, Vitest |
t.Run() subtests |
describe() / it() blocks |
| Table-driven tests | test.each() (Jest) |
| Mocking | Jest mocks, Sinon |
| Coverage | Jest built-in, nyc/istanbul |
Pattern Adaptations
Pattern 1: Unit Test
Go:
func TestFunction(t *testing.T) {
result := Function(input)
if result != expected {
t.Errorf("got %v, want %v", result, expected)
}
}
JavaScript (Jest):
test('function returns expected result', () => {
const result = functionUnderTest(input);
expect(result).toBe(expected);
});
TypeScript (Jest):
describe('functionUnderTest', () => {
it('returns expected result', () => {
const result = functionUnderTest(input);
expect(result).toBe(expected);
});
});
Pattern 2: Table-Driven Test
Go:
func TestFunction(t *testing.T) {
tests := []struct {
name string
input int
expected int
}{
{"case1", 1, 2},
{"case2", 2, 4},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Function(tt.input)
if result != tt.expected {
t.Errorf("got %v, want %v", result, tt.expected)
}
})
}
}
JavaScript/TypeScript (Jest):
describe('functionUnderTest', () => {
test.each([
['case1', 1, 2],
['case2', 2, 4],
])('%s: input %i should return %i', (name, input, expected) => {
const result = functionUnderTest(input);
expect(result).toBe(expected);
});
});
Alternative with object syntax:
describe('functionUnderTest', () => {
test.each([
{ name: 'case1', input: 1, expected: 2 },
{ name: 'case2', input: 2, expected: 4 },
])('$name', ({ input, expected }) => {
const result = functionUnderTest(input);
expect(result).toBe(expected);
});
});
Pattern 6: Dependency Injection (Mocking)
Go:
type MockExecutor struct {
Results map[string]Result
}
JavaScript (Jest):
const mockExecutor = {
execute: jest.fn((args) => {
return results[args.key];
})
};
test('processData uses executor', () => {
const result = processData(mockExecutor, testData);
expect(result).toBe(expected);
expect(mockExecutor.execute).toHaveBeenCalledWith(testData);
});
TypeScript (Jest):
const mockExecutor: Executor = {
execute: jest.fn((args: Args): Result => {
return results[args.key];
})
};
Coverage Tools
Jest (built-in):
jest --coverage
jest --coverage --coverageReporters=html
jest --coverage --coverageReporters=text-summary
nyc (for Mocha):
nyc mocha
nyc report --reporter=html
nyc report --reporter=text-summary
Go → Rust Adaptation
Transferability: 70-80%
Testing Framework Mapping
| Go Concept | Rust Equivalent |
|---|---|
testing package |
Built-in #[test] |
t.Run() subtests |
#[test] functions |
| Table-driven tests | Loop or macro |
| Error handling | Result<T, E> assertions |
| Mocking | mockall crate |
Pattern Adaptations
Pattern 1: Unit Test
Go:
func TestFunction(t *testing.T) {
result := Function(input)
if result != expected {
t.Errorf("got %v, want %v", result, expected)
}
}
Rust:
#[test]
fn test_function() {
let result = function(input);
assert_eq!(result, expected);
}
Pattern 2: Table-Driven Test
Go:
func TestFunction(t *testing.T) {
tests := []struct {
name string
input int
expected int
}{
{"case1", 1, 2},
{"case2", 2, 4},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Function(tt.input)
if result != tt.expected {
t.Errorf("got %v, want %v", result, tt.expected)
}
})
}
}
Rust:
#[test]
fn test_function() {
let tests = vec![
("case1", 1, 2),
("case2", 2, 4),
];
for (name, input, expected) in tests {
let result = function(input);
assert_eq!(result, expected, "test case: {}", name);
}
}
Rust (using rstest crate):
use rstest::rstest;
#[rstest]
#[case(1, 2)]
#[case(2, 4)]
fn test_function(#[case] input: i32, #[case] expected: i32) {
let result = function(input);
assert_eq!(result, expected);
}
Pattern 4: Error Path Testing
Go:
func TestFunction_Error(t *testing.T) {
_, err := Function(invalidInput)
if err == nil {
t.Error("expected error, got nil")
}
}
Rust:
#[test]
fn test_function_error() {
let result = function(invalid_input);
assert!(result.is_err(), "expected error");
}
#[test]
#[should_panic(expected = "invalid input")]
fn test_function_panic() {
function_that_panics(invalid_input);
}
Coverage Tools
tarpaulin:
cargo tarpaulin --out Html
cargo tarpaulin --out Lcov
llvm-cov (nightly):
cargo +nightly llvm-cov --html
cargo +nightly llvm-cov --text
Adaptation Checklist
When adapting test methodology to a new language:
Phase 1: Map Core Concepts
- Identify language testing framework (unittest, pytest, Jest, etc.)
- Map test structure (functions vs classes vs methods)
- Map assertion style (if/error vs assert vs expect)
- Map test organization (subtests, parametrize, describe/it)
- Map mocking approach (interfaces vs dependency injection vs mocks)
Phase 2: Adapt Patterns
- Translate Pattern 1 (Unit Test) to target language
- Translate Pattern 2 (Table-Driven) to target language
- Translate Pattern 4 (Error Path) to target language
- Identify language-specific patterns (e.g., decorator tests in Python)
- Document language-specific gotchas
Phase 3: Adapt Tools
- Identify coverage tool (coverage.py, Jest, tarpaulin, etc.)
- Create coverage gap analyzer script for target language
- Create test generator script for target language
- Adapt automation workflow to target toolchain
Phase 4: Adapt Workflow
- Update coverage generation commands
- Update test execution commands
- Update IDE/editor integration
- Update CI/CD pipeline
- Document language-specific workflow
Phase 5: Validate
- Apply methodology to sample project
- Measure effectiveness (time per test, coverage increase)
- Document lessons learned
- Refine patterns based on feedback
Language-Specific Considerations
Python
Strengths:
pytestparametrize is excellent for table-driven tests- Fixtures provide powerful setup/teardown
unittest.mockis very flexible
Challenges:
- Dynamic typing can hide errors caught at compile time in Go
- Coverage tools sometimes struggle with decorators
- Import-time code execution complicates testing
Tips:
- Use type hints to catch errors early
- Use
pytest-covfor coverage - Use
pytest-mockfor simpler mocking - Test module imports separately
JavaScript/TypeScript
Strengths:
- Jest has excellent built-in mocking
test.eachis natural for table-driven tests- TypeScript adds compile-time type safety
Challenges:
- Async/Promise handling adds complexity
- Module mocking can be tricky
- Coverage of TypeScript types vs runtime code
Tips:
- Use TypeScript for better IDE support and type safety
- Use Jest's
async/awaittest support - Use
ts-jestfor TypeScript testing - Mock at module boundaries, not implementation details
Rust
Strengths:
- Built-in testing framework is simple and fast
- Compile-time guarantees reduce need for some tests
Result<T, E>makes error testing explicit
Challenges:
- Less mature test tooling ecosystem
- Mocking requires more setup (mockall crate)
- Lifetime and ownership can complicate test data
Tips:
- Use
rstestfor parametrized tests - Use
mockallfor mocking traits - Use integration tests (
tests/directory) for public API - Use unit tests for internal logic
Effectiveness Across Languages
Expected Methodology Transfer
| Language | Pattern Transfer | Tool Adaptation | Overall Transfer |
|---|---|---|---|
| Python | 95% | 80% | 80-90% |
| JavaScript/TypeScript | 90% | 75% | 75-85% |
| Rust | 85% | 70% | 70-80% |
| Java | 90% | 80% | 80-85% |
| C# | 90% | 85% | 85-90% |
| Ruby | 85% | 75% | 75-80% |
Time to Adapt
| Activity | Estimated Time |
|---|---|
| Map core concepts | 2-3 hours |
| Adapt patterns | 3-4 hours |
| Create automation tools | 4-6 hours |
| Validate on sample project | 2-3 hours |
| Document adaptations | 1-2 hours |
| Total | 12-18 hours |
Source: Bootstrap-002 Test Strategy Development Framework: BAIME (Bootstrapped AI Methodology Engineering) Status: Production-ready, validated through 4 iterations