読者です 読者をやめる 読者になる 読者になる

JUnit実践入門を C# 4.0 + NUnit + RhinoMocks + NSubstitute で書いてみた (一部書けず)

C# テスト

良いテストの書くにはどうすればよいのだろうと思っていたところに、JUnit実践入門が発売されました。

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)


これは良いと早速購入し、C# 4.0 + NUnitで試してみることにしました*1

テストランナーについて

VisualStudio2012からはMSTestもExpress版で使えるとのことですが、JUnitと似ていた方が理解しやすいだろうと、NUnitを選びました。
ちなみに、MSTestの使い方については、以下にまとめられています。
JUnit実践入門 MSTest用パッチ #TddAdventJp - 亀岡的プログラマ日記



モック・スタブのフレームワークについて

以下の記事を読み、記載されていたRhino Mocksを使うことにしました。
#123 Rhino.Mocksを使ってみた « C# « a wandering wolf


クイックリファレンスは、以下にありました。
while(coding){DoTDD();}: Rhino Mocks Arrange / Act / Assert (AAA) Syntax Quick Reference


ただ、stackoverflowではRhino Mocksが「Keep in mind that Rhino is no longer developed」とも書かれていましたし、公式ページの動きは2011年くらいで止まっているようなのが気がかりでした。
c# - Rhino Mocks - Do we really need stubs? - Stack Overflow


その後、「The Art of Unit Testing, Second Edition」という書籍を読んだところ、Rhino Mocksの代わりに NSubstitute がおすすめされていたため、そちらも合わせて使うことにしました*2
また、NUnitプロジェクトでも使われているようです。
NuGet Gallery | NUnit.Mocks 2.6.4


なお、NSubstituteのPartial Mocksについては、今のところ実装されていないようです。
Support for Partial Mocks · Issue #41 · nsubstitute/NSubstitute · GitHub


Moqについては、以下の記事に使い方がまとめられていました。
Dummy vs. Stub vs. Spy vs. Fake vs. Mock | Niraj Bhatt - Architect's Blog



環境


ちなみに、NUnitのエラーを避けるためにAnyCPUを、Rhino Mocksなどを使うために非ClientProfileの.NET4を、それぞれ選択しています。
参考:64ビットのWindowsOS上でNUnitがテストプロジェクトをロードできない - sappokori's diary



ソースコード

以下にて公開しました。ライセンスはNYSLです。
thinkAmi/9784774153773_JUnit · GitHub



注意

残念ながら以下の一部の課題は、テストの書き方が分からなかったために解くことができませんでした。
今後フレームワークを理解する中で書き方が分かれば、追記していく予定です。

  • ch20.ex03
  • ch20.ex05
  • ch20.ex06


また、JavaC#の違いや自分の無力さにより、一部演習問題の内容を改変してあります。



演習問題を解いた時に考えたこと

ch18.ex01

文字列の変換はRegex.Replace()を使って、ラムダ式のあり/なしバージョンで書いてみました。

ch18.ex02

「DivideByZeroException」というそれらしい例外があったため、そちらを使いました。

ch18.ex05

Dictionaryを使っている上、int型のデフォルト値が0なので、SingleOrDefault()を使いました。

public int GetNum(Item item)
{
    return values.Where(v => v.Key == item.Name).SingleOrDefault().Value;
}
ch18.ex06

C# 4.0が使えるので、System.Threading.Tasksの書き方をメインにしました。
また、C# 4.0のおかげで、JavaのCountDownLatchと似た、System.Threading.CountdownEventを使ったものも書いてみました。
それ以前のバージョンのC#の場合も、以下を参考にすれば書けそうな気がしますが、今回は書きませんでした。
multithreading - Is there a C# equivalent to Java's CountDownLatch? - Stack Overflow


なお、System.Threading.Threadの場合のCountdownEventの書き方は以下を参考にしています*3
Threading in C# - Part 2 - Basic Synchronization



ch19.ex01

JUnitの「カスタムMatcher」は、NUnitでは「カスタムConstraint」と呼ぶようです。
書き方については以下を参考にして、NUnit.Framework.Constraints.Constraintを継承して作成しました。
NUnit のカスタムConstraint


なお、オーバーライドなどの内容は以下のとおりです。

NUnit GUI上でやりたいこと 方法
独自の検証 Matches()をオーバーライド
「Expected:」の内容を上書き WriteDescriptionTo()をオーバーライド
「But was:」の内容を上書き WriteActualValueTo()をオーバーライド
「Expected:」の一行上に文字列を追加 Assert.That()の第3引数として、追加したい文字列を渡す
ch19.ex06

enumの扱いがJavaとは異なるため、enumの各要素の値を文字列化するのはToString()で行いました。
動作スピードは遅いようですが、今回は手軽さを優先し、こちらを採用しました。
2008-01-24 - 当面C#と.NETな記録


また、NUnitのAssumeについては以下の公式ドキュメントを参考にしました。
NUnit - Theory



ch20.ex01

Datetimeオブジェクトをコンストラクタで渡す方法と、処理を別クラスで移譲した方法の2種類を書きました。
なお、C#にはパッケージプライベートという概念がないため、「sut.cal = mockcal」のような形では値を渡せませんでした。
残念ながらより良い方法が浮かばなかったため、以下を参考に、後者の方法でも別クラスをコンストラクタで渡しています。
testing - How write stub method with NUnit in C# - Stack Overflow


また、前者についてはリフレクションを使って、強引にprivateフィールドを書き換えています。
参考:C# で private メソッドを呼んでみる - 割と普通なブログ



ch20.ex02

Rhino Mocks・NSubstituteの2種類を使って書いています。
なお、試しに正しくない例外をexpectedしてみたところ、NSubstitute + NUnit GUI上でエラーメッセージが欠けました。
ToolTipsやCopyしてテキストファイルにペーストした場合は欠けませんでした (NSubstituteの方がエラーメッセージは長かった)。
そのため、もしかしたら、NUnitの制限なのかもしれません。
(左:Rhino Mocks、右:NSubstitute)



ch20.ex03

テストでstreamまわりをどう実装すれば良いのか分からず、テストコードを作成できませんでした。

ch20.ex04

こちらも、Rhino Mocks・NSubstituteの2種類を使って書いています。

ch20.ex05

.NETではサーブレットをどう表現すれば良いか悩み、コードを書けませんでした。

ch20.ex06

以下の資料には目を通したものの、スパイの作り方が分からなかったため、テストコードを作成できませんでした。
単体テスト: テスト代替の連続性について検討する



*1:自分の場合、書籍とは別の言語で書いてみると理解が深まると、「作ればわかる!Google App Engine for Javaプログラミング」本をPythonで書いてみて実感していたため。Javaに詳しくないというのもありますが...

*2:書籍はまだMEAPv6版でしたので、今後変わるのかもしれません

*3:C# 4.0 in a Nutshell」のマルチスレッドに関する記載をそのまま公開しているようで、ありがたい限りです。