KENTEM TechBlog

建設業のDXを実現するKENTEMの技術ブログです。

「テストコードを増やさず、テストケースを増やす」の正体!プロパティベースドテストの可能性

この記事は、 KENTEM TechBlog アドベントカレンダー2025 9日目、12月9日の記事です。

「くっ... このレビュー、どこまで見ればいいんだ...」

「テストケースこれで...足りてるのか...?」

ここ最近、目を血走らせてコードと格闘しているのエンジニアTです。

生成AIを使ってコーディングするのが当たり前になりつつある今、私はテストコードに苦しんでいました。 なぜAIが作るとあんなに膨大な量になるのでしょうか?(削っても多いから恐ろしい)

そんな悩みから勢いだけで「テストコードを増やさず、テストケースを増やす」方法について書いてみました。

同じ悩みを抱える人のヒントになれば幸いです。

注意点: 本稿の正しい読み方 本稿は新卒2年目エンジニアが作成した大変引用に向かない記事です。 曖昧な表現や本質的ではない部分にフォーカスして説明している部分があります。
また、シンプルに間違っている場合もあります。
これらをご了承の上でお楽しみください。

テストコードの増殖、それは突然に...

エンジニアTは、GitHub Copilotを活用したテストコード自動生成にテンション爆上げでした。
なにせ、「この関数のテストを書いて」と頼むだけで、ものの数秒で網羅性の高そうなテストケースがパッと手に入るのですからとんでもない。

そんな私はAIにテストを書かせて

  • テストカバレッジは急上昇!
  • 開発スピードも一時的にアップ!

と叫び散らかしていました。

がしかし、その幸福は長くは続きませんでした...

テストコードのコツさっ!

楽したツケは「レビュー負荷」と「モック管理地獄」

AIに書かせたテストコードはとにかく長い!めっちゃ作ってくるのです!
やたらめったらごり押しでテストケースを増やし、ごり押しでつくったモックでごり押した、ごり押し単体テストが生成されます。

いや、いいですよ?(正直テストつくるの大変だし。)
そもそもテストがないコードにパッと作ってくれたりするのはいいけど....

....これ誰がレビューするの?

むりむりむーりだよーー

AIは「網羅性」を達成するために、頑張ってコードの隅々までテストケースを生成してくれます。
その結果、
1.  レビューしたくなくなる大量のテストケースを生成
2.  これまた冗長な場当たり的なモックの生成

これにより、私だけじゃなく、チームにも負荷を与えていました。

「テストコードを増やさず、テストケースを増やす」

そんな時、とあるオンラインセミナーでこんなセリフを聞きました。
(うろ覚えなので事実を曲解している可能性があります。さらに言えば誰がおっしゃっていたかも忘れました...)

「AIに書かせてごり押しするテストを作らせるのは、2011~2013年ごろ?(記憶曖昧)に単体テストおじさんが大量のテストコードを作ったときと一緒。歴史を繰り返しているだけ。」

ほうほう、そうなんだ~

「だからこそ、AIに書かせるべきは歴史を反映したコードであるべきなのです。私たちはテストコードを増やさず、テストケースを増やす方法を知ってますからね。」

えっ...?

ほーん........は?
え、知らない知らない。全然知らない。

テストコード増やさずにテストケースを増やす??? おもろ、気になる!教えて!!

...しかし、このセリフだけ残してセミナーは終了。
私は肝心なところを知らないまま「テストコードを増やさず、テストケースを増やす?ナニソレ~」な状態を1週間過ごしました。

おそらく答えは「プロパティベースドテスト」!!

オチを焦らされに焦らされていた私は、ついに希望の光を見つけました!
それが、プロパティベースドテスト(Property-Based Testing, PBT)です!

プロパティベースドテストとは、「システムの満たすべき挙動(プロパティ)」に対してテストする手法です。

従来のテスト(事例ベースのテスト)

従来のユニットテスト(事例ベースのテスト )は、入力例を与えて挙動を確認していました。

// 例:JSONシリアライザ/デシリアライザするメソッド

[Fact]
public void JsonRoundTrip_SimpleCase_元のオブジェクトに戻ること()
{
    // 開発者が手動で作成したシンプルな固定データ
    var original = new UserProfile
    { 
        Id = 1,
        Name = "Alice",
        LastLogin = new DateTime(2025, 1, 1)
    };

    // シリアライズ ⇒ デシリアライズ を実行
    var deserializedValue = Deserialize<UserProfile>(Serialize(original));

    // 検証:この固定の事例で処理が成功するか
    Assert.True(original.Equals(deserializedValue));
}

[Fact]
public void JsonRoundTrip_EdgeCase_特殊文字を扱うこと()
{
    // 適当なエッジケース (特殊文字、null)
    var original = new UserProfile
    {
        Id = 999,
        Name = "Null\tLine",
        LastLogin = null 
    };

    // シリアライズ ⇒ デシリアライズ を実行
    var deserializedValue = Deserialize<UserProfile>(Serialize(original));

    // 検証:この固定の事例で処理が成功するか
    Assert.True(original.Equals(deserializedValue));
}

一見、悪くなさそうですが、落とし穴があります。

使っているライブラリのせいで「""(空文字)が入っているとエラー」を起こすようになっていました!
なんてことがあれば、気づきようがないでしょう。

よって、このようにケースが増えるのではないでしょうか。
さらにエラーが起きれば、、、AIにごり押しで作ってもらう他ないでしょう。

// <--- 上記に書いたテストに加えて --->

[Fact]
public void JsonRoundTrip_EdgeCase_空文字()
{
    var original = new UserProfile
    {
        Id = 999,
        Name = "", // 空文字のパターン
        LastLogin = null 
    };
    
    // シリアライズ ⇒ デシリアライズ を実行
    var deserializedValue = Deserialize<UserProfile>(Serialize(original));

    // 検証:この固定の事例で処理が成功するか
    Assert.True(original.Equals(deserializedValue));
}


// <--- 以降、エラーの起きたテストケースがどんどん増えていく... AI作ってくれ... --->

あーあ、こうなってしまえばテストコードの肥大化は免れません。
AIのごり押しに負けない根性のプルリクレビューが求められるでしょう。

プロパティベースドテスト

対して、プロパティベースドテストは、「システムの満たすべき挙動」として定義された条件に合わせて大量のテストデータを自動生成し検証を行います。

今回はC#のランダムテストライブラリFsCheckを使います。

using FsCheck;
using FsCheck.Xunit;

// テスト対象:JSONシリアライザ/デシリアライザするメソッド
public string Serialize<T>(T obj) { /* 実装 */ }
public T Deserialize<T>(string json) { /* 実装 */ }

[Property]
public Property JsonRoundTrip_UserProfile_元のオブジェクトに戻ること(
    UserProfile originalProfile) // 自動的にUserProfileを生成
{
    // シリアライズ ⇒ デシリアライズ を実行
    var deserializedProfile = Deserialize<UserProfile>(Serialize(originalProfile));

    // システムの満たすべき挙動: シリアライズ ⇒ デシリアライズしても、元のオブジェクトと等しい
    return (originalProfile.Equals(deserializedProfile)).ToProperty();
}

プロパティベースドテストの驚く点は、これだけのテストコードで事実上、数百パターン以上の検証ができる点です。

テストメソッドの引数にあるoriginalProfileは、ライブラリによってランダム自動生成され、数百回または数千回(カスタム可能)のパターンで検証されます。

それができる理由はココです。

// システムの満たすべき挙動: 往復処理をしても、元のオブジェクトと等しい
return (originalProfile.Equals(deserializedProfile)).ToProperty();

個別の入力ごとに期待値を並べるのではなく、「どんな入力に対しても成立してほしい挙動(プロパティ)」をチェックしていることです。

これにより、テストケースごとにコードを増やす必要はなくなり、少量のコードで強力なテストが作れます!

つまり、めちゃ長いテストコードを減らせる可能性がある!ということです!!

補足 ただし、自動生成される都合上、実行結果にブレが生じます。
基本的にランダム生成なので、デバッグ時などはseed値を指定して実行するなどの工夫が必要です。(結構面倒ですが今回は省略します)

AIに書かせるべきテストはコレだっ!(間に合いませんでした。すみません。)

プロパティベースドテストは、まさにAIがテストコードを生み出す時代において、

  • コード量を削減できる! ⇒ レビュー負荷は下がる
  • テストカバレッジも向上する! ⇒ 品質は上がる

相性抜群の手法と言えるでしょう。

とはいっても、机上の空論。実際やってみないとわかりません。

そこで、実際にやってみました!!

....となるはずでしたが、アドベントカレンダーには間に合いませんでした。すみません。

次回以降頑張るので、もっと詳しく知りたい方は以下の記事や本をご参考にしてください。

まとめ:今日から始めるプロパティベースドテスト

間に合わなかった私が偉そうなこと言えませんので、あたりさわりのないことだけ伝えます。

AIにテストを書かせるのはラクでラクでたまりません。
しかし、ごり押しで作らせるとかえって苦しさの原因になるかもしれません。

ぜひ、「テストコードを増やさず、テストケースを増やす」やり方で書かせてみてはいかがでしょうか?

私が言えないセリフ

ここまで読んでいただいた方、あるいはここだけ読んでいる方もありがとうございます。
この記事だけでは説明不足だと思います。前述した参考記事やAI等を活用して理解を深めていただけると幸いです。

次回、「プロパティベースドテストに乗り換えてみた!!」....の予定です。
気が向いたらまた見てください。
ありがとうございました。

おわりに

KENTEMでは、様々な拠点でエンジニアを大募集しています! 建設×ITにご興味頂いた方は、是非下記のリンクからご応募ください。 recruit.kentem.jp career.kentem.jp