2

Moq を使用して、テスト対象のメソッド内の静的メソッド呼び出しをモック化できないことはわかっています。メソッドをリファクタリングしてテストできるようにするには、どうすればよいでしょうか? 基本クラスのメソッドを呼び出すメソッドもありますが、それをリファクタリングする必要がありますか? MS.Fakes や TypeMocks を使用して shim を作成するのではなく、リファクタリングして堅実なコードを記述します。

    public override DateTime ResolveDate(ISeries comparisonSeries, DateTime targetDate)
    {
        if (comparisonSeries == null)
        {
            throw new ArgumentNullException("comparisonSeries");
        }

        switch (comparisonSeries.Key)
        {
            case SeriesKey.R1:
            case SeriesKey.R2:
            case SeriesKey.R3:
            case SeriesKey.R4:
            case SeriesKey.R5:
                return DateHelper.PreviousOrCurrentQuarterEnd(targetDate);
        }

        return base.ResolveDate(comparisonSeries, targetDate);
    }

    [TestMethod]
    public void SomeTestMethod()
    {
        var mockIAppCache = new Mock<IAppCache>();
        var mockISeries = new Mock<ISeries>();

        ReportFR2 report = new ReportFR2(SeriesKey.FR2, mockIAppCache);
        DateTime resolvedDate = report.ResolveDate(mockISeries, DateTime.Now);

        //Assert.AreEqual("something", "something");

    }
4

2 に答える 2

2

上記を見ると、テストできる基本的な条件が 3 つあります。

  1. 比較シリーズがnullの場合

  2. 比較系列キーがR1:R5の場合

  3. 比較シリーズ キーが null ではなく、R1 以外の場合: R5

条件 1 では、これをテストで簡単にカバーできます。

条件 2 では、R1:R5 の場合、静的メソッドにヒットするように見えます。

  • ResolveDate メソッドの観点からは、このブランチにヒットしたときの値が何であるかはまだ気になります。
  • R1:R5 が静的ヘルパーを呼び出すことを証明できればよいのですが、コメントで述べたように、それをきれいに行う唯一の方法は、DateHelper をインターフェイスでラップし、それをこのクラスのコンストラクターに渡すことです。それは努力する価値がないかもしれません。
  • そうしないことにした場合でも、その分岐に該当するテストを提供し、DateHelper.PreviousOrCurrentQuarterEnd()関数を直接ターゲットにしてすべてのエッジ ケースをヒットする別のテスト セットを作成することをお勧めします。これは、何かが失敗した場合に、どのコードが原因であるかを特定するのに役立ちます。

条件 3 についても条件 2 と同じことが言えます。基本クラスにありますが、それでも有効な論理分岐です。

  • 繰り返しますが、それが基底クラスを呼び出していることを証明するのは困難です。

  • しかし、結果を確認することは依然として有効です。

したがって、最初に記述できるテストのセットが 4 つあると思います。これらのテストに合格した後、ユーティリティ クラスのリファクタリングを行うかどうかを決定できますDateHelper. 。私の推測では、ノーと言うでしょう :-D

  1. ReportRF2 クラスと null 比較シリーズが与えられた場合

    • report.ResolveDate を呼び出す場合

    • Null 参照例外をスローする必要があります。
      `Assert.Throws( () => report.ResolveDate( null, DateTime.Now ));を使用します。

  2. R1:R5 のセット内の ReportRF2 クラスとシリーズ キーが与えられた場合

    • 境界 X (0001/1/1 など) の日付を解決する場合、y と等しくなければなりません。

    • ".." の場合、...、...; (エッジ/境界ケースについて繰り返します。データドリブンの使用を検討してください)

  3. ReportRF2 クラスと R1:R5 および NOT NULL のセットにないシリーズ キーがあるとします。

    • 境界 X の日付を解決するとき ... #2 に似ていますが、おそらく異なる期待される結果です。
  4. 与えられた静的ユーティリティ クラス DateHelper

    • PreviousOrCurrentQuarterEnd() を計算し、日付が X の場合、y と等しくなければなりません。
    • 上記の #2 のエッジ ケースに似ています。

ResolveDateこれにより、期待される結果がカバーされ、失敗がメソッドまたはメソッドに起因していることがわかりますDateHelper.PreviousOrCurrentQuarterEnd()。純粋主義者が望むほど分離することはできないかもしれませんが、エッジ ケースとハッピー パスをカバーしている限り、アプリが計画どおりに機能していることを証明できます (これらのテストに合格している限り)。

実際にできないことは、比較シリーズが null の場合を除いて、特定の動作が取られることをアサートすることです。そのため、その検証が必要かどうかを判断するのはあなた次第です。ただし、特定の値または範囲に入ると、予測可能な出力が得られ、それが何らかの価値を追加するという証拠が必要です。

于 2013-06-22T03:29:27.523 に答える
1

@Damonの素敵な答えに追加するだけDateHelperで、インターフェイスでラップするのは非常に簡単です:

public interface IDateHelper
{
    DateTime PreviousOrCurrentQuarterEnd(DateTime targetDate);
}

前述のように、このインターフェイスを実装するインスタンス クラスが必要になりますが、単体テストではMock<IDateHelper:

public class InstanceDateHelper : IDateHelper
{
    public DateTime PreviousOrCurrentQuarterEnd(DateTime targetDate)
    {
        return DateTimeHelper.PreviousOrCurrentQuarterEnd(targetDate);
    }
}

ほらIDateHelperインターフェイスをモックできるようになり、既存の静的コードを使用する実装ができました。

このラッピング手法を使用して、 new を起動するメソッドで単体テストを作成したProcessので、テスト対象のメソッドが を呼び出すかどうかだけを知る必要があるときに、本格的なプロセスを実際に起動することなくメソッドをテスト.Start(StartInfo)できました。副作用。

この方法を想像してください:

public bool StartProcessAndWaitForExit(ProcessStartInfo info)
{
    var process = Process.Start(info); // test-hindering static method call
    //...
}

私がしなければならなかった唯一の変更はこれでした:

public bool StartProcessAndWaitForExit(IProcessWrapper process, ProcessStartInfo info)
{
    var process = process.Start(info); // injected wrapper interface makes method testable
    //...
}

ResolveDateを必要とするクラス内の唯一のメソッドである場合は、IDateHelperそれをメソッド パラメーターとして挿入しても問題ありません。同様にそれを必要とするメソッドがたくさんある場合は、それをコンストラクター引数として挿入し、private readonly IDateHelper _helper;フィールドを作成する (コンストラクターで初期化する) のが最善の方法です。

于 2013-06-22T20:55:12.137 に答える