概要
・Exceptionが発生した時、引数をメッセージ(ログ)に出力してほしい。
・メソッドをつくる度にTry Catchを書きたくはない。
・呼び出しクラスの大本にTry Catchを仕込みたくない。
・AOPで対応しよう!
残念なメッセージ
NUnitでAssert.IsTrue(bool result)というメソッドがあります。
この子はTrueかFalseしか知らないので気が利きません。
string a = "吾輩は猫である";
string b = "犬";
public void TextContains(string a, string b)
{
Assert.IsTrue( a.Contains(b) );// False:試験エラーとなる
}
MESSAGE:
Expected: True
But was: False
このままでは何でエラーになったのかわかりません。
こんなメッセージがほしい
引数の値も教えてくれると嬉しいです。
MESSAGE:
Expected: True
But was: False
=================
TextContains
a: 吾輩は猫である
b: 犬
メッセージを仕込む
C#でAOPをするため、PostSharpをインストールします。
・Visual Studio用のPostSharpをダウンロード&インストールします。
・NuGetでPostSharpを参照に追加します。
Exception発生時に割り込みするクラスを作成します。
NUintの結果レポートにはAssertExceptionのメッセージが出力されるので、
メッセージを乗っ取って引数を出力します。
[Serializable]
public sealed class TraceArgument : OnMethodBoundaryAspect
{
// Exception時に動作
public override void OnException(MethodExecutionArgs args)
{
base.OnException(args);
// AssertExceptionのときメッセージを拡張
if (args.Exception.GetType() == typeof(AssertionException))
TraceMethodArguments(args);
}
private static void TraceMethodArguments(MethodExecutionArgs args)
{
// メソッドの引数
Arguments parameters = args.Arguments;
if (parameters == null) return;
StringBuilder message = new StringBuilder();
message
.AppendLine(args.Exception.Message) // 従来のメッセージ
.AppendLine("------------------")
.AppendLine(args.Method.Name); // メソッド名
// 引数をループ
int index = 0;
string paramValue = null;
foreach (object p in parameters)
{
// 引数の名前
string paramName = args.Method.GetParameters()[index].Name;
// 引数の型
Type type = p.GetType();
// 型ごとにValueを取り出す
if (type == typeof(string) || type == typeof(int) || type == typeof(double) || type == typeof(decimal))
{
paramValue = (string)p;
}
else if (type.IsEnum)
{
paramValue = p.ToString();
}
else if (type == typeof(XmlDocument))
{
paramValue = ((XmlDocument)p).OuterXml;
}
else
{ //try to serialize
try
{
XmlSerializer serializer = new XmlSerializer(p.GetType());
StringWriter strWriter = new StringWriter();
serializer.Serialize(strWriter, p);
paramValue = strWriter.ToString();
}
catch
{
paramValue = "[引数をシリアライズできませんでした]";
}
}
message
.Append(" ")
.Append(paramName).Append(": ")
.Append(paramValue).AppendLine()
;
index++;
}
// Exceptionにメッセージを詰めなおす
AssertionException ex = new AssertionException(message.ToString(), args.Exception);
throw ex;
}
}
[TraceArgument]
public class WebDriverWrapper
{
public void TextContains(string a, string b)
{
Assert.IsTrue( a.Contains(b) );
}
}
5.試験に失敗する(AssertException)と引数を出力してくれるようになります
MESSAGE:
Expected: True
But was: False
=================
TextContains
a: 吾輩は猫である
b: 犬
思ったこと&利用例
PostSharpが神いわゆるゴッドでした。
紙メソッドが神メソッドになりました。
OnMethodBoundaryAspectで割り込めるタイミングはOnException以外にOnEntry, OnExit, OnSuccessがあります。
私は今回作成したクラスをSeleniumのWebDriverのラッパークラスに設定しています。
NUnitでエラーが出たときに、実際の引数でどんな値をチェックしていたのかわかるととても便利です。
Seleniumは、ローカルで成功したのにサーバで失敗したり、
時々一回だけ失敗したりするので、エラー原因を簡単に知れると便利です。