168
148

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

C#Advent Calendar 2017

Day 18

Moq : Mocking Framework for .NET

Posted at

Moq は、テストダブル(=単体テスト用の代役オブジェクト)を簡単に作るためのライブラリです。

テストダブル

単体テストにおける問題のひとつとして、以下のようなことがあります。

テスト対象の依存オブジェクトが・・・

  • ネットワーク通信が必要
  • データベースアクセスが必要

など、外部に依存する場合、そのままでは単体テストを作りにくいです。

これを解決するために、実際のオブジェクトの代わりを用意します。

テスト用の代役オブジェクトをテストダブルと呼びます。テストダブルはその役割に応じて、スタブ、モック、フェイク、などと呼称されます(それらの違いについては、本記事の主題と直接は関係がないので割愛します)。

典型的なテストダブルの実装

テストダブルはあくまで代役なので、本物と同じ振る舞いはしません。単体テストが動けば良いと割り切ります。

  • 本物と同じ I/F を持つ。
  • 通信などの振る舞いはしない。
  • 特定の入力が渡されると、特定の出力を返す。

ただ、このときの問題として、本物と同じ I/F を実装するのって結構めんどくさいです。特に、I/F が多い場合や、開発中で I/F が変化する場合がつらいです。単体テストのためだけにそこまで頑張らなくてはいけないのか?

そこで、実装をサポートしてくれるライブラリがあると嬉しいです。

ライブラリ

いくつか代表的なものを挙げます(他にもありますが)。

  • OCMock : iOS (Objective-C)
  • Mockito : Android (Java / Kotlin)
  • Moq : .NET / Xamarin (C#)

この記事では、Moq について書きます。

Moq とは

発音は「もっく」か「もっきゅ」らしいです。「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 の内容を元に、補足や修正を加えたものです。

168
148
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
168
148

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?