Files
2025-11-30 09:07:22 +08:00

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:

  1. Coverage-Driven Workflow: Analyze → Prioritize → Test → Verify
  2. Priority Matrix: P1 (error handling) → P4 (infrastructure)
  3. Pattern-Based Testing: Structured approaches to common scenarios
  4. Table-Driven Approach: Multiple scenarios with shared logic
  5. Error Path Testing: Systematic edge case coverage
  6. Dependency Injection: Mock external dependencies
  7. Quality Standards: Test structure and best practices
  8. TDD Cycle: Red-Green-Refactor

Language-Specific Elements (Require Adaptation)

  1. Syntax and Imports: Language-specific
  2. Testing Framework APIs: Different per ecosystem
  3. Coverage Tool Commands: Language-specific tools
  4. Mock Implementation: Different mocking libraries
  5. 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:

  • pytest parametrize is excellent for table-driven tests
  • Fixtures provide powerful setup/teardown
  • unittest.mock is 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-cov for coverage
  • Use pytest-mock for simpler mocking
  • Test module imports separately

JavaScript/TypeScript

Strengths:

  • Jest has excellent built-in mocking
  • test.each is 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/await test support
  • Use ts-jest for 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 rstest for parametrized tests
  • Use mockall for 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