C#によるProperty Based TestingのためのFsCheckの使い方
GUIアプリ開発にてProperty Based Testing用のライブラリ、FsCheckを利用しました。日本語の記事が少なく、苦労しましたので、自分の備忘録も含めてここにまとめてみたいと思います。
Property Based Testingとは
Property Based Testingとは、ある関数のテストに用いる変数を一般化して、その関数の性質を検証する手法のことです。主に関数型言語の世界で広く使用される概念になります。
Property Based Testingの対となる概念としてExample Based Testing(意訳すると例題による検証)があり、これは具体的な変数値と想定する答えをあらかじめ用意しておき、検証する手法になります。Xunitなどを用いて広く実施されているテストに相当します。
Property Based TestingやExample Based Testingについてはすでに多くの記事がありますので詳細な説明はここでは割愛いたします。詳しくは下記参考記事をご覧ください。
参考記事:
An introduction to property-based testing(英語の記事)
Property Based Testing(日本語の記事)
動作環境
本記事は以下の環境で動作を確認しました。
VisualStudio2017
.Net Framework 4.8
FsCheckとは
Property Based Testingを実現できるF#製のライブラリになります。現在、F#、C#、VBで利用できるようです。
GitHub:https://github.com/fscheck/FsCheck
テストを実施する
「FsCheck」と「FsCheck.Xunit」をNuGetインストールして、usingを用いて呼び出しておきましょう。
using FsCheck;
using FsCheck.Xunit;
テストクラスの作り方・動かし方
例題として以下の関数を検証したいと思います。
参考記事:Property-Based Testing with C#
public static int Add(int x, int y)
{
return x + y;
}
記述の方法はXunitと似ています。特徴は判定結果をAssertするのではなく__「Property」という型を返すこと__です。条件式をToPropertyメソッドを使ってProperty型に変換します。
ここでは足し算の性質の一つを検証しています。
[Property]
public Property EqualMultiplyWithTwo(int x)
{
return (x * 2 == CalcMethods.Add(x, x)).ToProperty();
}
成功した場合
テストを実行してみましょう。下図のように確かにテストを実行できることを確認できました。
テストに成功した場合のテストケースの分析を行うこともできす。
詳しくはテスト数を確認するをご覧ください。
失敗した場合
わざと失敗するテストケースを書いてみます。
[Property]
public Property EqualMultiplyWithTwo(int x)
{
return (x * 2 + 1 == CalcMethods.Add(x, x)).ToProperty();
}
実行してみますと、失敗のメッセージと共に__Original__と__Shrunk__という値が表示されていると分かります。
__Original__はテストが失敗した時のテストの入力値、__Shrunk__は対象テストを失敗させることのできる最小の入力値を表しています。__Shrunk__に表示されたテストケースを考察することで不具合の原因を見つけやすくなります。
FsCheckのTips
テストに名前を付ける(DisplayName)
テストに名前を付けるにはDisplayNameを宣言します。
[Property(DisplayName ="同じ数を足したら二倍になる")]
public Property EqualMultiplyWithTwo(int x)
{
return (x * 2 == CalcMethods.Add(x, x)).ToProperty();
}
テストプロジェクトを開いてみると、テストケース名が表示されています。
入力値を制限したい(When)
FsCheckはテスト関数の引数に自動的に値を割り当ててくれます。(デフォルトでは100回ランダムに値を発生させます。)しかし、割り算の検証など特定の数を引数として代入してほしくないときもあります。
テスト対象の関数
public static double Divide(int x, int y)
{
return x / y;
}
分母が0になる場合を除いて検証します。
[Property(DisplayName = "割り算の検証")]
public Property CheckDivide(int x, int y)
{
Func<bool> property = () => x / y == CalcMethods.Divide(x, y);
return property.When(y != 0);
}
例外を検証する(Prop.thorws)
テスト対象の関数として以下のような関数を用意します。(if文がなくともDivideByZeroExceptionの例外が発生しますが、分かりやすさのため記述しています。)
public static double DivideWithException(int x, int y)
{
if (y == 0) { throw new DivideByZeroException("分母が0になっています。"); }
return x / y;
}
Prop.thorwsと遅延処理を組み合わせて例外を検証できます。
[Property(DisplayName = "割り算の検証(例外処理)")]
public Property CheckDivideWithException(int x)
{
return Prop.Throws<DivideByZeroException, double>(new Lazy<double>(() => CalcMethods.DivideWithException(x, 0)));
}
テスト数を確認する(Trivial)
Trivialを用いて、特定の条件を満たすテストが何回実行されたのか確認できます。
[Property(DisplayName = "同じ数を足したら二倍になる(テスト数の検証)")]
public Property EqualMultiplyWithTwoCheckCases(int x)
{
return (x * 2 == CalcMethods.Add(x, x)).Trivial(x > 0);
}
テストエクスプローラにて該当テストの「Output」をクリックすると(下図参照)、条件に該当するテストが何%実施されたのかを確認できます。
他にもClassifyプロパティを用いる複数の条件でテストケースを調べることができます。
テスト失敗時の計算結果を出力する(Label)
テストが失敗した際、計算結果を出力したい場面があります。そんなときに使用するのがLabelプロパティです。(テストが成功した時の出力方法は知りません。。教えてください。)
[Property(DisplayName = "同じ数を足したら二倍になる(関数値の出力)")]
public Property EqualMultiplyWithTwoOutput(int x)
{
var val1 = x + x + 1;
return (val1 == CalcMethods.Add(x, x)).Label(String.Format("val1:{0}",val1));
}
参考記事