メインコンテンツまでスキップ

初級者向け!手書きMoqを使ってユニットテストをさらに進めよう(前編)

鶴田
ディアシステム(株)開発一部第2課

こんにちは。開発2課の鶴田です。

この記事は、前回の「ユニットテストを使ってカバレッジを計測、テストの品質を向上させよう」の続編です。前回は、主に「プログラムのロジックが十分にテストされているか(=カバレッジ)」を確認することで、テストの質を高める方法を紹介しました。

今回はそこから一歩進んで、実際のアプリでよく登場する「データベースアクセス」や「外部データとのやりとり」が入った場面でも、ユニットテストを効果的に行う方法を紹介します。

一般的にはMoqというオープンソースソフトウェア(以下、OSSと呼びます)を使って、データベースの代わりとなる代用オブジェクトを作成、テストします。しかし、実際の業務では開発ルール上、OSSを利用できないケースもあります。

そこで今回は、OSSを使わずに実装する方法を紹介します。 OSSを使わずに手でMoq的なモノを実装することで、Moqの基本的な概念に対する理解が深まる効果もあります♪

いきなり実装に入ると混乱しやすいため、この記事(前編)では今回のゴールと実装方針、用語の解説を先に紹介します。次回の後編で、実際にコードを書いてみましょう。

※後編の実装編ではわたしが書いた記事を流用します。記事はこちらです。

今回のゴールと実装方針

実際のアプリでは、商品マスター(前回の記事ではProduct)や在庫マスター(前回の記事ではZaiko)の情報は「データベース」や「CSVファイル」などの外部データソースから取得されます。しかしそれをそのままユニットテスト化しようとすると、データベースの準備や接続が必要となり、ユニットテストを実装・実行するためのコストが上がります。

そこで今回は、データ取得の仕組みをインターフェイスをつかって抽象化し、本番環境とテスト環境で差し替えられるようにする方針を取ります。

たとえば、商品マスター(前回の記事ではProduct)の場合は以下の通りとなります。

public interface IProductRepository
{
// Find は習慣的に決まっている名前
Product? Find(string productCode);
}

// テスト用の取得・更新用クラスはsealedにすることが一般的
public sealed class InMemoryProductRepository : IProductRepository
{
// テスト用 Product 向けリポジトリクラス
Product? Find(string productCode)
{
// テスト用の値を返すロジック
return new Product



}
}

このように「データの取り出し方」だけを切り離すことで、DBやテスト用CSVがなくてもユニットテストを実行することができ、結果として保守しやすいコードになります。

用語の解説

Moq(モック)

Moqは .NET向けに広く使われているOSSです。インターフェイスや仮想メソッドに対して、簡単に「スタブ」や「スパイ」として振る舞うオブジェクトを生成できます。

var mock = new Mock<IProductRepository>();
mock.Setup(x => x.Find("001")).Returns(new Product("001", "商品A", 100));

このように記述することで、Find("001") の戻り値をテストごとに制御できます。また、呼び出し回数の確認(Verify)なども可能です。とても便利なライブラリですが、本記事では使用しない方針で進めます。

リポジトリ (Repository)

リポジトリとは「データの取得や保存のしくみを隠すためのクラス」です。 実装用とテスト用、2パターンを用意し、共通のインターフェイス経由で実装することで差し替えることができるようになります。

テストダブル (Test Double)

「テストのときに、本物の代わりに使うオブジェクト」の総称です。今回は以下の2つの機能をつかいます。

スタブ:決まった値を返すだけ。今回の例ではInMemoryProductRepositoryがスタブに該当します スパイ:呼び出されたかどうかを記録します。今回は呼び出された回数を内部で記録、検証します。

依存関係逆転の原則(DIP)と依存性の注入(DI)

テスト用のリポジトリに差し替えるためには、依存関係逆転の原則(DIP)と依存性の注入(DI)という考え方が重要になります。

依存関係逆転の原則(DIP) は以下の2つのルールからなりたっています。

  1. 上位のモジュールは下位のモジュールに依存してはならない。

  2. 抽象詳細に依存してはならない。

    • 上位 ・・・・ビジネスロジックやアプリ全体を制御するロジック
    • 下位 ・・・・DBやCSV、JSONに対する入出力ロジック
    • 依存する ・・ここでは実装を変更したときに影響を受けることを意味する。具体的な例でいうと、下位モジュールのコードを変更したときに、上位モジュールがエラーになったりすることを言う
    • 抽象 ・・・・インターフェィスや抽象クラスが該当する
    • 詳細 ・・・・具体的な実装

依存性の注入(DI) はオブジェクトが必要とする依存先(他のオブジェクトやサービス)を、外部から渡してもらうデザインパターンです。

ちなみにですが、私は「依存性の注入」がデザインパターンの名前のことであることに気づきませんでした。(名前って重要や・・・) 当社の土井がアップしたこちらの記事でわかりやすく解説しています。5分程度でサラッと読めるよい記事です。ご一読されることをおススメします。 依存関係逆転の原則とDI(Dependency Injection)

後編の予告

後編では、実際にInMemoryProductRepositoryやProductServiceを使ってユニットテストを書きます。 前回同様、Fine Code Coverageを使って、テストがしっかり書けているかも確認します。