Moq は、テストダブル(=単体テスト用の代役オブジェクト)を簡単に作るためのライブラリです。
テストダブル
単体テストにおける問題のひとつとして、以下のようなことがあります。
テスト対象の依存オブジェクトが・・・
- ネットワーク通信が必要
- データベースアクセスが必要
など、外部に依存する場合、そのままでは単体テストを作りにくいです。
これを解決するために、実際のオブジェクトの代わりを用意します。
テスト用の代役オブジェクトをテストダブルと呼びます。テストダブルはその役割に応じて、スタブ、モック、フェイク、などと呼称されます(それらの違いについては、本記事の主題と直接は関係がないので割愛します)。
典型的なテストダブルの実装
テストダブルはあくまで代役なので、本物と同じ振る舞いはしません。単体テストが動けば良いと割り切ります。
- 本物と同じ I/F を持つ。
- 通信などの振る舞いはしない。
- 特定の入力が渡されると、特定の出力を返す。
ただ、このときの問題として、本物と同じ I/F を実装するのって結構めんどくさいです。特に、I/F が多い場合や、開発中で I/F が変化する場合がつらいです。単体テストのためだけにそこまで頑張らなくてはいけないのか?
そこで、実装をサポートしてくれるライブラリがあると嬉しいです。
ライブラリ
いくつか代表的なものを挙げます(他にもありますが)。
- OCMock : iOS (Objective-C)
- Mockito : Android (Java / Kotlin)
- Moq : .NET / Xamarin (C#)
この記事では、Moq について書きます。
Moq とは
- Mocking Framework for .NET
- https://github.com/moq/moq4
発音は「もっく」か「もっきゅ」らしいです。「q」は「k」の発音になることが多いと思いますが(「Linq」とか)、「Mock」と「Moq」で発音が同じというのはつらいので個人的には「もっきゅ」がいい気がします。ついでに、カタカナでなくひらがなで書くのがいい感じ?
Moq の使い方 : 最初の一歩
public interface IHoge {
bool DoSomething(string value);
}
var mock = new Mock<IHoge>();
// interface が自動的に実装される
mock.Object.DoSomething("abc"); // -> false
とりあえずこれだけで、実装クラスを書かなくても全ての interface
を勝手に実装してくれます。
注意 (1) interface が必要
置き換えたいオブジェクトの interface
を定義しておく必要があります。
- 本物の
class
はそのinterface
を実装する - テストダブルもその
interface
を実装する
これは設計に影響を与えるので注意が必要です。どちらかといえば設計が改善されるのですが、複雑度が上がるきらいもあります。
なお参考として、本物の class
を継承してテストダブルにする手もあります。これだと interface
を定義しなくてすみます。ただ、これはテストダブルの挙動が本物と混ざってよくわからないことになりがちで、避けた方が良いように思います。
Moq の使い方 : 基本編
まずは、欲しいテストダブルを作るための基本を押さえます。
例:固定値を返すプロパティ
public interface IHoge {
string Name { get; }
}
var mock = new Mock<IHoge>();
mock.SetupGet(x => x.Name)
.Returns("xyz");
var name = mock.Object.Name; // -> "xyz"
例:Set 可能なプロパティ
public interface IHoge {
string Name { get; set; }
}
var mock = new Mock<IHoge>();
mock.SetupProperty(x => x.Name, "xyz");
mock.Object.Name = "123";
var name = mock.Object.Name; // -> "123"
例:特定の入力に特定の出力を返す
public interface IHoge {
bool DoSomething(string value);
}
var mock = new Mock<IHoge>();
mock.Setup(x => x.DoSomething("abc"))
.Returns(true);
mock.Object.DoSomething("abc"); // -> true
例:入力の条件を指定する
public interface IHoge {
bool DoSomething(string value);
}
var mock = new Mock<IHoge>();
mock.Setup(x => x.DoSomething(It.Is<string>(s => s.Length < 10)))
.Returns(true);
mock.Object.DoSomething("abc"); // -> true
例:入力内容に応じた出力を返す
public interface IHoge {
string DoSomething(string value);
}
var mock = new Mock<IHoge>();
mock.Setup(x => x.DoSomething(It.IsAny<string>()))
.Returns(s => s.ToUpper());
mock.Object.DoSomething("abc"); // -> "ABC"
テストコードからの使い方
実際の使い方としては、以下のようになります。
まず事前準備としてテストダブルの実装をします。
-
Mock
インスタンスを生成する。 -
Setup
をずらずらと書く。 -
mock.Object
を取得する。
そして、単体テストコードでは、実際のオブジェクトの代わりに mock.Object
を使います。
注意 (2) 依存オブジェクトの差し替え
本物のオブジェクトをテストダブルに差し替えられるようにしておく必要があります。
手段は何でもよくて、単にコンストラクタで渡しても良いですし、DI コンテナを使っても良いです。
ひとつ大事な点として、テスト対象は class
ではなく interface
に依存するようにしておきます。そうしておかないと、自由に差し替えるのが難しくなります。
Moq の使い方 : 応用編
基本編だけでだいたい足りますが、以下のような点も気になるところです。
- プロパティの定義をいちいち書きたくない。
- 例外のテストをしたい。
- 特定の処理が呼ばれるたびに返す値を変化させたい。
- 特定の処理がちゃんと呼ばれたか確認したい。
それらの例を挙げます。
例:プロパティの定義を楽にする
public interface IHoge {
string Name { get; set; }
}
var mock = new Mock<IHoge>();
mock.SetupAllProperties();
例:例外を発生させる
public interface IHoge {
bool DoSomething(string value);
}
var mock = new Mock<IHoge>();
mock.Setup(x => x.DoSomething("abc"))
.Throws<InvalidOperationException>();
例:返す値を変化させる
var mock = new Mock<IHoge>();
mock.SetupSequence(x => x.DoSomething("abc"))
.Returns(2)
.Returns(1)
.Returns(0)
.Throws<InvalidOperationException>();
例:呼び出されたかどうかを検証する
var mock = new Mock<IHoge>();
mock.Setup(x => x.DoSomething("abc"))
.Returns(true);
mock.Verify(x => x.DoSomething("abc"),
Times.AtLeastOnce());
例:呼び出された際の処理を記述する
var mock = new Mock<IHoge>();
mock.Setup(x => x.DoSomething("abc"))
.Callback(() => Console.WriteLine("Before returns"))
.Returns(true)
.Callback(() => Console.WriteLine("After returns"));
注意点など
Moq の機能はたくさんあって、凝りだすといろいろできます。しかし、やりすぎには注意が必要です。
あくまでテスト用の代役オブジェクトであって、単体テストのときだけ使えればいい、本物になる必要はないと割り切ります。
ライブラリを使うメリット
テストダブルを楽に作るのに Moq のようなライブラリは便利です。
ただ、テストダブルを実装していくと、ソースコードの記述量は案外多くなります。そのため、最初は楽だったけどだんだん大変になってきた、なんてことも起こります。場合によっては、ライブラリなしでテストダブルを実装する方が楽になるかもしれません。
そのため、いつでもライブラリを使うのがベターであるとは言い切れません。
一方、ライブラリを使うメリットとしては、テストダブル実装コードを一箇所に(1メソッドに or 1ファイルに)まとめやすいという点があります。
テストコードやテストダブル実装コードは、テストの内容ごとにまとまって書かれているのが良いかと思います。ライブラリを使うことで、そのようなテストコードを書きやすくなります。
余談
外部に依存するものの単体テストをそもそも本当に書くべきかどうか、ということも気にしておいた方が良いかもしれません。
その外部オブジェクト自身が単体テストすればすむという場合もあるでしょう。もちろん、それではすまない場合もあって、そんなときの手段のひとつとしてテストダブルの活用が考えられます。
参考
Moq : Mocking Framework for .NET
(Mobile Act Osaka #2 で、この内容について LT をした際の発表資料)
本記事は、上記の LT の内容を元に、補足や修正を加えたものです。