第15章:パラメータ化(同じ形でケースを増やす)🔁
(=「1つのテストの型」で、境界値や例外ケースをスイスイ増やす回だよ〜!😆💞)
1) 今日のゴール🏁✨

この章が終わったら、こんな感じになれるよ💪😊
xUnitの[Fact]と[Theory]の違いが説明できる🙂[InlineData]でケースを増やすのがスッとできる🧪🔁- データが複雑になったら
[MemberData]/TheoryData<>に逃がせる🏃♀️💨 - 境界値テストを「追加しやすい形」で作れる📏✨
- ケースが増えても 読みやすさを落とさない工夫ができる📘💕
※この章は “最新のxUnit v3系” 前提でいくよ(xunit.v3 など)✨
(xUnit v3の対応フレームワークやパッケージ情報は公式NuGetと公式ドキュメントに準拠)(NuGet)
2) まずは超ざっくり:Fact と Theory の気持ち🥺🫶
✅ [Fact]
- 入力がないテスト(固定の1本)
- 例:
nullのとき例外、みたいな単発
✅ [Theory]
- 入力があるテスト(同じ形で複数ケースを流す)
- 例:
0円 / 1円 / 999円 / 上限超え…みたいにいっぱい試したいとき
そして大事な罠⚠️😵💫
[Theory]は データが無いと実行されないよ! (公式アナライザーでも「データ無いTheoryは走らない」って明言されてる)(xUnit.net)
3) ハンズオン:まずは [InlineData] で “境界値5連発” やってみよ🔫✨
題材は「カフェ会計」の超ミニ版☕️🧾 「小計(円)に税率をかけて、円に四捨五入して合計(円)を返す」ってことにするね😊 (四捨五入の仕様はテストで固定するのがTDDっぽい👍)
✅ 3-1. 先にテストを書く(Red)🚦🔴
ポイントはこれ👇
- 1テスト1意図🍰🙅♀️(前章のルールを守る)
- “同じ意図” の中で、入力だけ変えるのがパラメータ化🔁
using Xunit;
public class CafePriceTests
{
[Theory]
// subtotalYen, taxRate, expectedTotalYen
[InlineData(0, 0.10, 0)]
[InlineData(1, 0.10, 1)] // 1.1 => 1(四捨五入)
[InlineData(9, 0.10, 10)] // 9.9 => 10(四捨五入)
[InlineData(10, 0.10, 11)] // 11.0 => 11
[InlineData(999, 0.10, 1099)] // 1098.9 => 1099
public void Total_with_tax_is_rounded(int subtotalYen, double taxRate, int expectedTotalYen)
{
// Arrange
var sut = new CafePriceCalculator();
// Act
var total = sut.TotalWithTax(subtotalYen, taxRate);
// Assert
Assert.Equal(expectedTotalYen, total);
}
}
InlineData は「Theoryに inline の値を渡すための属性」って公式APIにもあるよ🧪✨(api.xunit.net)
✅ 3-2. 最小実装(Green)🚦🟢
いったん “雑でも通す” でOK(この章はパラメータ化が主役だからね)😊
public class CafePriceCalculator
{
public int TotalWithTax(int subtotalYen, double taxRate)
{
var raw = subtotalYen * (1.0 + taxRate);
return (int)Math.Round(raw, MidpointRounding.AwayFromZero);
}
}
ここで「四捨五入の方式(Midpoint)」までテストで縛ると、ブレなくて安心😇🫶 もし仕様が「切り捨て」なら、テストがそう導いてくれるよ✨
4) “InlineDataの限界” を知ろう😵💫➡️✨
InlineData は便利なんだけど、こうなりがち👇
- ケースが増えるほど 縦に長くて読みづらい📜😵
- 引数が増えると 何が何だか分からない🌀
- オブジェクトを渡したいときつらい(基本は値中心)🧸💦
- さらに、値の数が合ってないと怒られる(アナライザーで検出もされる)😇(xUnit.net)
だから次の逃げ道が超大事👇✨
5) ケースが育ってきたら [MemberData] で “データを外に出す”🏃♀️📦
✅ MemberDataって?
[MemberData] は「public static のプロパティ/フィールド/メソッドからデータを取ってくる」方式だよ📌✨(api.xunit.net)
(テスト本文がスッキリして読みやすくなるのが強い😊)
✅ 例:境界値セットを外に出す🧾📏
using System.Collections.Generic;
using Xunit;
public class CafePriceTests
{
public static IEnumerable<object[]> TotalWithTaxCases =>
new List<object[]>
{
new object[] { 0, 0.10, 0 },
new object[] { 1, 0.10, 1 },
new object[] { 9, 0.10, 10 },
new object[] { 10, 0.10, 11 },
new object[] { 999, 0.10, 1099 },
};
[Theory]
[MemberData(nameof(TotalWithTaxCases))]
public void Total_with_tax_is_rounded(int subtotalYen, double taxRate, int expectedTotalYen)
{
var sut = new CafePriceCalculator();
var total = sut.TotalWithTax(subtotalYen, taxRate);
Assert.Equal(expectedTotalYen, total);
}
}
💡nameof(...) を使うのは超おすすめ!
メンバー名ミスで死ぬ事故を減らせるよ(MemberDataは「存在するメンバーを指せ」系のルールもある)🧠✨(xUnit.net)
6) v3の推し:TheoryData<> で “型安全” にしよ🧷💖
IEnumerable<object[]> は動くけど、型が弱くて事故りやすいのが欠点🥺
そこで TheoryData<> が便利!✨(公式APIとして用意されてるよ)(api.xunit.net)
using Xunit;
public class CafePriceTests
{
public static TheoryData<int, double, int> TotalWithTaxCases => new()
{
{ 0, 0.10, 0 },
{ 1, 0.10, 1 },
{ 9, 0.10, 10 },
{ 10, 0.10, 11 },
{ 999, 0.10, 1099 },
};
[Theory]
[MemberData(nameof(TotalWithTaxCases))]
public void Total_with_tax_is_rounded(int subtotalYen, double taxRate, int expectedTotalYen)
{
var sut = new CafePriceCalculator();
var total = sut.TotalWithTax(subtotalYen, taxRate);
Assert.Equal(expectedTotalYen, total);
}
}
さらに v3 だと、MemberData の戻り値の許容が増えてたり(TheoryDataRow<> や async enumerable など)アナライザーにも案内があるよ📌✨(xUnit.net)
(今は「へぇ〜」でOK!必要になったら使う感じで😊)
7) “読みやすさが死ぬ” のを防ぐコツ🧠💡
パラメータ化って、やりすぎると逆に読みにくくなるの…😵💫💔 なので、ルールを持とう👇✨
✅ コツ1:データ列の順番は “意図の順番” にする📘
おすすめは基本これ👇
- input…, expected(期待値は最後) (読みながら「で、どうなるの?」が最後に来る)
✅ コツ2:ケースが多いなら “意味で分ける” 🧩
- 正常系セット
- 境界値セット
- 異常系セット(例外)
みたいに Theoryを分割すると、読んだ瞬間に理解できる😊✨
✅ コツ3:同じ意図の複数AssertはOK、意図が混ざったら分割🍰
- 「合計が正しい」意図なら
Assert.Equal(...)だけに集中🎯 - 「例外型 + メッセージ」まで見るなら、それは別のTheoryに分けてもいいよ🧯
8) Test Explorerで “Theoryの表示が変” になったら📌👀
Theoryは「データが変わると表示名や安定性が変わる」ことがあるよ〜😵💫 xUnit公式に Test ExplorerでのTheoryデータ表示の安定性の解説ページがあるので、困ったらそこを見るのが早い✨(xUnit.net)
9) AI(Copilot/Codex)活用:この章は “ケース出し” に最強🤖💞
AIはこの章と相性よすぎる😆✨ おすすめプロンプトはこれ👇(コピペOK)
- 「この仕様の 境界値 を、少なくとも10個。理由も一言つけて」📏
- 「正常/異常/境界値に分類して、TheoryData形式で出して」🧪
- 「入力の組合せが多いから、**分割方針(Theoryを何本にするか)**を提案して」🧩
- 「このInlineData、読みづらいから MemberDataに移して」📦
⚠️ ただし最後にこれだけは守ってね👇
- 採用条件:テストが通る + 意図に一致 ✅😇 (AIが作った“それっぽい”は簡単に混ざるので、テストが門番!🚪🛡️)
10) ミニ課題🎀📝(コミット単位のおすすめ付き)
課題A:境界値を “あと5ケース” 増やしてみよ📏✨
例(どれでもOK)👇
subtotalYen = -1(無効入力にする?例外?丸める?仕様決めよ)🚫taxRate = 0(税なし)taxRate = 0.08(別税率)- すごい大きい値(上限テスト)🗻
subtotalYen = 5(0.5系の丸め確認)🎯
おすすめコミット:
- InlineDataで5個足す ➜ テストRed確認🚦🔴
- 実装を最小修正で通す🚦🟢
- ケースが増えたら MemberData/ TheoryData に整理🧹✨
11) よくあるミス集(先に潰す)💣🧯
[Theory]にデータ付け忘れ → 0件実行で気づきにくい😵💫(公式も注意してる)(xUnit.net)InlineDataの値の個数が引数と合ってない → アナライザーで怒られる😇(xUnit.net)- ケース増やしすぎて「何を守ってるテスト?」になる → Theory分割しよ🧩
- データ生成が賢すぎて、逆に読めない → まずはベタでOK🙆♀️✨
12) まとめ🎁✨
パラメータ化はね、**「品質を上げる」ってより「ちゃんと試すのをラクにする」**技だよ🧪💖 ラクになると、境界値や異常系をサボらなくなる → 結果、強くなる💪🔥
次の章(テストデータの作り方🧸)にもつながるから、 この章のうちに InlineData → MemberData/ TheoryData の流れだけは体に入れちゃおう😆✨
必要なら、この第15章を “講義台本” にして👇も付けて出せるよ😊🎀
- 5〜10分ごとの進行(先生トーク)🎤
- 受講生が詰まるポイントと救済セリフ🛟
- 小テスト(選択式/穴埋め)📝