Cotton Bolls: NUnit Converter
« ストーリーポイント | トップページ | DBのテスト »
2004.04.04
NUnit Converter
今回の仕事はC#を使ったASP.NETのWebアプリ開発になった.これまでC#には全く興味がなかったので,モチベーションを上げるのに苦労した.言語が変わると何もかも不自由になってしまう.Visual StudioにQuick Fixとリファクタリング機能がないのは信じられないって感じだ.
まずはテストが書けないと話にならない.早速NUnitをインストールする.APIテストでC#の調査していると,NUnitが文字列比較で余計なことをしてくれることがわかった.JUnitのように同じ部分を省いてしまうのだ.しかもAssert.AreEquals引数にobjectのキャストを入れても回避できない.そこで,泣く泣くStringWrapperクラスを作る.これはNUnitにstringと解釈されないようにするためのラッパークラスだ:
public class StringWrapper { private string str; public StringWrapper(string str) { this.str = str; } public override string ToString() { return str == null ? "(null)" : str; } public override bool Equals(object obj) { StringWrapper target = obj as StringWrapper; if (target == null) return false; if (str == null) return target.str == null; return str.Equals (target.str); } public override int GetHashCode() { return str == null ? 0 : str.GetHashCode(); } }
そしてdiffされると困るテストコードは次のように書く.
Assert.AreEqual(new StringWrapper(expected), new StringWrapper(actual));
ふう.
その後,ADO.NETでDataSetのテストコードを書くときに止まってしまう.DataSetはRDBのテーブル構造をそのまま表わしたようなクラスだ.なので,Converterなしのテストなんかやってられない.急いでJUnit ConverterをC#に移植する.NUnit Converterの作成だ.このとき,やっとC#のいいところが見つかった.すなわち,
- 可変長引数
- Boxing
の2つだ.
可変長引数は,BeanValueConverterとBeanConverterで役に立った.この2つのConverterはある特定のクラスのインスタンスをプロパティ値による文字列表現に変換する.可変長引数なら,プロパティを何個でも指定できる.JUnit Converterのように何個もコンストラクタを用意する必要がない:
Converter converter = new DefaultConverter(); converter = new BeanConverter(converter, typeof(Programmer), "FirstName", "LastName", "Languages");
また,可変長引数は期待値の作成でも役に立った.以前このブログで書いた,すごく短い名前のファクトリーメソッドを使う方法だ:
object expected = E.a(E.p("FirstName", "Masaru"), E.ap("Languages", "Ruby", "Java", "EmacsLisp", "C#"), E.p("LastName", "Ishii")); converter.AssertEquals(expected, ishii);
ここでE.aは配列,E.pはProperty, E.apは配列の値を持つPropertyに変換するファクトリーメソッドだ(E:Expected, a:array, p:property, ap:array propertyの略).これでツリー構造の期待値の作成がかなり便利になった.
Boxingは,配列のConverterで役に立った.C#の場合,ICollectionへのConverer(CollectionConverter) ひとつでobject配列,int配列,ListArrayなどすべてに対応できる.JUnit Converterの場合,ObjectArrayConverter, IntArrayConverter, CollectionConverterと何種類も作らないといけなかった.Boxingの便利さが実感できる.
以上でConverterの機能は一通り実装できたが,他にもC#特有のIndexerConverterを作ってみた.これを使えばDataRowのテストコードの作成が楽になる.
[Test] public void DataTable() { DataTable table = new DataTable("Songlines"); table.Columns.Add(new DataColumn("Track", typeof(int))); table.Columns.Add(new DataColumn("Title", typeof(string))); table.Columns.Add(new DataColumn("Artist", typeof(string))); DataRow row; row = table.NewRow(); row["Track"] = 1; row["Title"] = "Roger The Miller"; row["Artist"] = "Karan Casey"; table.Rows.Add(row); row = table.NewRow(); row["Track"] = 2; row["Title"] = "She Is Like The Swallow"; row["Artist"] = "Karan Casey"; table.Rows.Add(row); Converter converter = new DefaultConverter(); converter = new BeanValueConverter(converter, typeof(DataTable), "Rows"); converter = new IndexerConverter(converter, typeof(DataRow), "Track", "Title", "Artist"); object expected = E.a(E.a(E.p("Track", 1), E.p("Title", "Roger The Miller"), E.p("Artist", "Karan Casey")), E.a(E.p("Track", 2), E.p("Title", "She Is Like The Swallow"), E.p("Artist", "Karan Casey"))); converter.AssertEquals(expected, table, 2); }
このようにIndexerConverterにはインデックスキーを直接指定する.各キーの型からReflectionでIndexerを検索し,インデックス値を求めることになる.
後は,SQL Serverのテストで役に立つSqlSelectConverterも作ってみた.
[Test] public void Songlines() { Converter converter = new DefaultConverter(); converter = new SqlSelectConverter(converter); SqlSelect select = new SqlSelect("SELECT track, title, artist" + " FROM songlines " + " ORDER BY track", connection); object expected = E.a(E.a(1, "Roger The Miller", "Karan Casey"), E.a(2, "She Is Like The Swallow", "Karan Casey")); converter.AssertEquals(expected, select); }
SqlSelectオブジェクトにSELECT文とSqlConnection,必要ならSqlTransactionを指定する.これでDB周りのテストがだいぶ楽になりそうだ.
NUnit ConverterのおかげでだいぶC#がわかってきた.後はNUnit Diff Addinか….こっちはかなり大変そうなので誰か作ってくれないかな.
10:15 PM in .NET | 固定リンク
トラックバック
この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/391131
この記事へのトラックバック一覧です: NUnit Converter: