👻

テストコードは努力値振りだと思っている話|育てる実戦個体なコード

に公開

問題点

プログラマー3年目をすぎました。
3年目ともなると日々色々なことを考えます。

設計...多少わかる!
DI...使える!
レビュー...それなりに戦える!
でも、こう思う瞬間があります。

  • 「この修正、本当に壊れてないよな...?」
  • 「リファクタしたいけど、ちょっと怖い」
  • 「動いてるし、一旦このままで...」

コードは書けます。
でも実際の業務のコードで"安心して触れるコード"は、意外と少ないんですよね。

これはまるで、個体値Vなのに努力値無振りのポケモンみたいだなぁと思いました。

スペックは高い。
でも実戦で殴り合うと、思ったより弱い!

3年目ともなると分かることがあります。
怖いのは難しい実装ではありません。

動いていた既存コードが壊れることです!


結論

テストコードを書くことは、努力値稼ぎと同じです。

テストが一つ、また一つと増えるたびに、
コードベースは少しずつ、でも確実に堅牢になっていきます。
コードも成長するのです。

表面上は何も変化がないので分かりにくいです。
UIは変わらないからビジネスサイドの人間は気づきません。
ユーザーも気づかない。

でもテストコードを書くと内部では、確実に努力値が積み上がっています。

ポケモンでも同じです。
努力値は派手なエフェクトが出ません。
でも対戦で確実に"差"が出ます。

テストも同じです。
改修時、障害時、仕様変更時にその"差"が確実に出ます。


解説

例:シンプルな割引ロジック

// 価格計算を行うシンプルなドメインロジック
public class PriceCalculator
{
    /// <summary>
    /// 価格を計算する
    /// </summary>
    /// <param name="price">元の価格</param>
    /// <param name="isPremiumUser">プレミアムユーザーかどうか</param>
    /// <returns>割引後の価格</returns>
    public int Calculate(int price, bool isPremiumUser)
    {
        // ガード節(Guard Clause)
        // 0以下の価格は不正値として扱い、常に0を返す
        // 早期returnすることでネストを浅くし、可読性を保つ
        if (price <= 0)
        {
            return 0;
        }

        // プレミアムユーザーの場合は10%割引
        // ビジネスルールがここに集約されている
        if (isPremiumUser)
        {
            // (int)キャストしているため、小数点以下は切り捨てになる
            // → 端数処理の仕様はテストで明示すべきポイント
            return (int)(price * 0.9);
        }

        // 通常ユーザーは割引なし
        return price;
    }
}

最初はこれで十分です。

しかし現実では仕様変更の嵐です
例えば

  • 割引率が変更される
  • 端数処理のルールが変わる
  • 不正値の扱いが追加される
  • キャンペーン条件が増える

テストがなければ、毎回がジムリーダー戦並の気合いが必要になります。


基本の努力値振り(正常系)

using Xunit;

// 通常系(正常パターン)を検証するテストクラス
public class PriceCalculatorTests
{
    // [Fact] は「単一ケースのテスト」
    // 引数を取らず、このメソッドは1回だけ実行される
    [Fact]
    public void プレミアムユーザーの場合_10パーセント割引となるべき()
    {
        // Arrange(準備)
        // テスト対象クラスを生成する
        var calculator = new PriceCalculator();

        // Act(実行)
        // 1000円・プレミアムユーザー(true)として価格計算を実行
        var result = calculator.Calculate(1000, true);

        // Assert(検証)
        // 10%割引なので900円になることを確認
        Assert.Equal(900, result);
    }

    // 通常ユーザーのケースを検証
    [Fact]
    public void 通常ユーザーの場合_価格はそのままとなるべき()
    {
        // Arrange
        var calculator = new PriceCalculator();

        // Act
        // プレミアムユーザーではない(false)場合
        var result = calculator.Calculate(1000, false);

        // Assert
        // 割引なしなので1000円のままであることを確認
        Assert.Equal(1000, result);
    }
}

これは最低限のHP振りみたいなものです。

基本挙動が保証されるだけでも、
改修時の心理的コストは一気に下がります。
心地よい気持ちを味わえて一人孤独に幸せを噛みしめる時間となります👻


境界値という"調整"

// [Theory] は「データを変えて複数回実行するテスト」
// 下の [InlineData] に書いた値が、このメソッドの引数に順番に渡される
// つまりこのテストは「0」と「-100」の2回実行される
[Theory]
[InlineData(0)]      // ← price に 0 が入ってテストされる
[InlineData(-100)]   // ← price に -100 が入ってテストされる
public void 価格が0以下の場合_0を返すべき(int price)
{
    // Arrange(準備)
    var calculator = new PriceCalculator();

    // Act(実行)
    var result = calculator.Calculate(price, true);
    // ↑ 上の InlineData の値が price に入り、Calculate が実行される

    // Assert(検証)
    Assert.Equal(0, result);
}

境界値を書きました。

これは素早さ調整のようなものでしょうか。
「ここは絶対に落とさない」という意思表示になります。

テストは仕様の言語化でもあるのです。


まとめ

個体値(言語スキル・設計知識)だけでは不十分です。

努力値(テスト)を振って、
初めて実戦レベルになります。

テストが一つ、また一つと増えるたびに、
コードベースは少しずつ、でも確実に堅牢になり成長していきます。

表面上は地味です。
でも内部は確実に強くなっています。

爆速実装より、まずテスト1本書いてみてください。
派手なリファクタより、境界値1ケース書いてみてください。

テストを書くことは、努力値を振るようなものです。

静かに、確実に、コードを強く成長する行為です。👻

HPを上げたいときはビッパをひたすら狩っていました。🦫✨
ポケルスがあると努力値が倍になるから、作業効率が一気に上がるんですよね。🦠⚡

ではまた!

GitHubで編集を提案

Discussion