Skip to content

テスト戦略

Tax Calculation Core は純粋関数ライブラリとして、以下の多層テスト戦略を採用する。

→ テストルール全体: .claude/rules/testing.md → Golden Test データ: ゴールデンテスト

Golden Tests(国税庁計算例との完全一致)

docs/design/testing/golden-test-examples.md に整理した国税庁の設例データと1:1で対応するテストを実装する。

csharp
[Fact]
public void ExecutePipeline_ShouldMatchNtaExample1_WhenGivenGoldenTestInput()
{
    // Arrange: NTA設例1(令和7年分 年末調整のしかた p57-59)
    var input = GoldenTestData.NtaExample1;
    var parameters = TaxYearParameters.ForYear(2025);

    // Act
    var result = TaxCalculationPipeline.ExecutePipeline(input, parameters);

    // Assert: 全ステップの期待値と完全一致
    result.SalaryIncomeAfterDeduction.Should().Be(7_020_000m);   // ②
    result.IncomeAdjustmentDeduction.Should().Be(47_000m);       // ③
    result.AdjustedSalaryIncome.Should().Be(6_973_000m);         // ④
    result.TotalIncomeDeductions.Should().Be(4_786_102m);        // ⑤
    result.TaxableIncome.Should().Be(2_186_000m);                // ⑥
    result.AssessedTax.Should().Be(121_100m);                    // ⑦
    result.YeaIncomeTax.Should().Be(44_600m);                    // ⑨
    result.YeaAnnualTax.Should().Be(45_500m);                    // ⑩
    result.OverUnderPayment.Should().Be(-155_200m);              // ⑪ (還付)
}

Property Tests(FsCheck による不変条件検証)

csharp
[Property]
public Property TaxAmount_ShouldBeNonNegative_WhenIncomeIsNonNegative()
{
    return Prop.ForAll(
        Arb.From<decimal>().Filter(income => income >= 0),
        income =>
        {
            var result = TaxCalculationPipeline.ExecutePipeline(
                CreateInput(grossSalary: income), R7Parameters);
            return result.YeaAnnualTax >= 0;
        });
}

[Property]
public Property Pipeline_ShouldBePureFunction_WhenGivenSameInput()
{
    return Prop.ForAll(
        Arb.From<TaxCalculationInput>(),
        input =>
        {
            var result1 = TaxCalculationPipeline.ExecutePipeline(input, R7Parameters);
            var result2 = TaxCalculationPipeline.ExecutePipeline(input, R7Parameters);
            return result1 == result2;  // record の値等価性
        });
}

[Property]
public Property TotalDeductions_ShouldNotExceedTotalIncome()
{
    return Prop.ForAll(
        Arb.From<TaxCalculationInput>(),
        input =>
        {
            var result = TaxCalculationPipeline.ExecutePipeline(input, R7Parameters);
            return result.TotalIncomeDeductions <= result.AdjustedSalaryIncome
                || result.TaxableIncome == 0;
        });
}

不変条件の一覧:

不変条件検証内容
所得 ≥ 0 → 税額 ≥ 0年調年税額は常に0以上
同一入力 → 同一結果純粋関数保証(参照透過性)
控除額 ≤ 総所得 or 課税所得 = 0控除合計が所得を超える場合は課税所得がゼロにフロアされる
各ステップの端数処理が仕様通り切捨て/切上げの精度検証

Boundary Tests(税率区分境界・控除閾値境界)

csharp
[Theory]
[InlineData(1_949_000, 0.05, 0)]         // 5%区間の上端付近
[InlineData(1_950_000, 0.05, 0)]         // 5%区間の上端(以下なので含む)
[InlineData(1_951_000, 0.10, 97_500)]    // 10%区間の下端
[InlineData(3_299_000, 0.10, 97_500)]    // 10%区間の上端付近
[InlineData(3_300_000, 0.10, 97_500)]    // 10%区間の上端(以下なので含む)
[InlineData(3_301_000, 0.20, 427_500)]   // 20%区間の下端
public void CalculateAssessedTax_ShouldApplyCorrectBracket_WhenAtBoundary(
    decimal taxableIncome, decimal expectedRate, decimal expectedDeduction)
{
    var result = TaxCalculationPipeline.CalculateAssessedTax(
        taxableIncome, R7Parameters.TaxRateTable);

    var expected = taxableIncome * expectedRate - expectedDeduction;
    result.Should().Be(expected);
}

追加の境界テスト対象:

境界テストケース
給与収入850万円所得金額調整控除の適用/非適用境界
共通閾値 T(R7: 58万円)扶養親族・同一生計配偶者の該当/非該当境界
本人の合計所得金額900万円/950万円/1,000万円配偶者控除の3段階逓減境界
基礎控除の各所得区分境界132万円/336万円/489万円/655万円/2,350万円等
1月1日生まれ vs 1月2日生まれ前日満了ルールによる年齢判定境界

Year-Versioned Tests(年度パラメータ差し替え)

csharp
[Theory]
[InlineData(2024, 550_000)]  // R6: 最低保障55万円
[InlineData(2025, 650_000)]  // R7: 最低保障65万円
public void SalaryDeduction_ShouldUseCorrectMinimum_WhenYearChanges(
    int taxYear, decimal expectedMinimum)
{
    var parameters = TaxYearParameters.ForYear(taxYear);
    var result = TaxCalculationPipeline.CalculateSalaryIncomeAfterDeductionAsync(
        1_000_000m, parameters.SalaryDeductionTable);

    // 給与収入100万円 − 最低保障額 = 給与所得控除後の金額
    result.Should().Be(1_000_000m - expectedMinimum);
}

年度間差異テストの対象:

年度変更テスト内容
R6 → R7給与所得控除の最低保障額(55万円→65万円)
R6 → R7共通閾値 T(48万円→58万円)
R6 → R7基礎控除テーブルの区分変更
R7 → R7施行日前後(11/30以前 vs 12/1以後)のパラメータ差異
R7 → R8(予定)共通閾値 T(58万円→62万円予定)