C#でドメイン駆動開発 アーキテクチャーの実装とテスト駆動での書き方を解説5

当サイトではアフィリエイト広告を利用しています。

C#でドメイン駆動開発

前回はドメイン駆動開発で採用するアーキテクチャーについて解説しました。今回はそれを実際に実装していきます。全体をユーザーインターフェース、インフラストラクチャー、ドメイン層に分け,さらにユニットテスト用にテストプロジェクトを作成します。その後テスト駆動開発でテストコードを書いていく方法を順番に解説していきます。

実装

プロジェクトの追加

それではプロジェクトの作成をしましょう。最初にWinFormを選びソリューション名を「DDD」プロジェクト名を「DDD.WinForm」にしましょう。次にクラスライブラリを選び「DDD.Domain」を追加します。同じく「DDD.Infrastructure」を追加します。最後にテストフレームワークを選び「DDDTest.Tests」という名前で登録します。「DDD.Tests」としないのはテストプロジェクトが必ず一番下に来るように並べたいからです。好みの問題です。

参照関係はDomainは参照なし。最上位です。InfrastructureはDomainを参照します。WinFormはDomainとInfrastructureを参照します。TestsはDomain,Infrastructure,WinFormを参照します。

TestsにはNugetで「Moq」と「Chaining Assertion」をインストールします。最初の実装さてそれでは最初の実装をしましょう。多くのプログラムはデータを取って画面に表示する。または画面の値をどこかに保存する。というシナリオがほとんどかと思います。データを取って画面に表示するまでに何かしら加工したり,判定したりはするでしょうが,大きく分けるとその2つがエンタープライズアプリケーションとしては王道かと思います。

ゲームアプリとかなら違いがあるかもしれませんが。最初のシナリオはシンプルにデータを取って表示しましょう。ドメイン駆動開発なのでドメイン部分を作りますが,ドメインをテスト駆動で作成するので,まずはテストコードを書きます。

「Tests」にファイル追加でテストを追加します。名前は「MeasureViewModelTest」とします。基本的にはViewModelに対してテストを書いていきます。決まりではありませんが,クラス一つ一つをテストしていくより,シーケンスをつかさどるViewModelをテストするのが一番効果的です。

多くの分岐があるようなドメインロジッククラスがあればそれのみ個別でテストコードを書きます。目的はViewModelとドメイン層に対して,できるだけカバレッジを上げることです。カバレッジとはテストコードが通った割合。分岐などがある場合は全ての分岐を通るようにテストを書きます。

ただ,絶対に通らない本番コード用のコンストラクタなどもあるので90%以上が目安です。カバレッジはNugetでOpenCoverを取得し,拡張機能のOpenCoverUIで計測できます。

MeasureViewModelTestを作ったら「計測_シナリオ」というテストを作ります。そこにいきなり「var viewModel = new MeasureViewModel()」と書きます。MeasureViewModelクラスはまだないのでコンパイルエラーになります。テスト駆動なのでクラスを作る前にテストを書きます。基本的に命名はテストコードでします。

インテリセンスが出てこなくて書きにくいと思うかもしれませんが勘違いです
ここで命名しているのです

命名するときにインテリセンスなんて今までも出てきてないですよね?それと同じことです。

ともかく,MeasureViewModelを作ります。場所は「WinForm」に「ViewModels」というフォルダを作成しそこに作成します。クラスをPublicに変更し,ViewModelBaseを継承します。ViewModelBaseはデータバインドを可能にするための記述を共通化するために実装しています。

ファイルはViewModelsフォルダに配置し,中身は次のようにします。

namespace DDD.WinForm.ViewModels
{
    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual bool SetProperty<T>(ref T field, T value, [CallerMemberName]string propertyName = null)
        {
            if (Equals(field, value)) { return false; }
            field = value;
            var h = this.PropertyChanged;
            if (h != null) { h(this, new PropertyChangedEventArgs(propertyName)); }
            return true;
        }   
    }
}

MeasureViewModelTestに戻るとMeasureViewModelのコンパイルエラー部分でコントロールを押しながらドットを押下してEnterすることでUsingが追加され,コンパイルエラーが消えると思います。

一度テストを実行しましょう。成功すると思います。

次に

viewModel.MeasureValue.Is(“--”);

と書きます。

これは,MeasureViewModelが生成されたときのMeasureValueの初期値が“–”ということを示しています。画面を起動したときの初期値になります。

MeasureValueはコンパイルエラーになるのでコントロールボタンを押しながらドットを押し,Enterします。

テストをするとレッドバーになると思います。レッドバーを取り除きましょう。自動生成されたプロパティpublic object MeasureValue{get;set;}は次のように書き換えます。

public string MeasureValue{get;set;} = “--”;

さらにデータバインドを可能にするために,値が変更された時の通知がされるように書き直します。

private string _measureValue = "--";

public string MeasureValue
{
    get { return _measureValue; }
    set { SetProperty(ref _measureValue, value); }
}

SetPropertyは継承元のViewModelBaseに記載しています。中身はMVVMライブラリーのPrismと同じ実装にしています。

これでテストしてみましょう。これでテストが通りましたね。
今のが一連のテスト駆動の手順です。

  1. ・テストを書く
  2. ・コンパイルエラーを取り除く
  3. ・レッドバーを出す
  4. ・レッドバーを取り除く

です。そしてグリンバーが出たら最後にやることがあります。

  1. ・リファクタリングする
  2. ・重複コードを取り除くなど
  3. ・View,ViewModel,Infrastructureにドメインコードがないか?

などなど。

今回は単純なコードのため特に重複コードやドメインコードが散らばっているという状態にはなっていません。このサイクルをぐるぐる回していきます。

これでは先に進みます。今のは非常に単純なケースでしたが,次は少々ややこしくなります。

インターフェースを普段使い慣れていない人は急に難しくなったと感じるかもしれません。テストコードにテストを追加します。

viewModel.Measure();

viewModel.MeasureValue.Is(“1.23 m/s”);

Measure()が存在しないためコンパイルエラーになりますが,いつもの要領でメソッドを生成します。

コンパイルエラーが取れたらテストを実行しましょう。レッドバーになります。

今回は少々厄介です。まず1.23がどこからやってくるのでしょうか?もちろんこれはテスト用の値です。ちなみにこのテストのシナリオは「計測ボタンを押したら計測値が表示される。計測値は小数点2桁で丸められ,単位はm/sで生じされる」ということを表しています。

テスト駆動とはこのように先に欲しい結果をテストコードに書きます。「こういうアクションを起こすとプロパティの値はこう変わっているはず!」という思いを込めてテストコードを書きます。あとはレッドバーをグリーンバーにするだけです。では始めましょう。MeasureViewModelのMeasureメソッドは未実装のExceptionが記述されていると思います。

これを消します。そしてもう一度テストしてみましょう。当然まだレッドバーです。MeasureメソッドにMeasureValue = “1.23m/s”;

と書けばテストはとおりますが,こんなコードは意味がありませんね。それ以外の方法でテストを通しましょう。

ここでドメイン駆動開発の1つの要素を紹介します。