最近、ユニットテストに Moq を使用し始めました。 Moqを使用して、テストする必要のないクラスをモックアウトします。
通常、静的メソッドはどのように処理しますか?
_public void foo(string filePath)
{
File f = StaticClass.GetFile(filePath);
}
_
この静的メソッドStaticClass.GetFile()
はどのようにモックできますか?
追伸Moqと単体テストでお勧めの読み物をいただければ幸いです。
@ Pure.Krome:良い応答ですが、いくつかの詳細を追加します
@Kevin:コードにもたらすことができる変更に応じて、ソリューションを選択する必要があります。
これを変更できる場合、何らかの依存性注入によりコードのテストが容易になります。できない場合は、適切な隔離が必要です。
無料のモックフレームワーク(Moq、RhinoMocks、NMock ...)では、デリゲート、インターフェイス、および仮想メソッドのみをモックできます。したがって、静的メソッド、密閉メソッド、および非仮想メソッドには、3つのソリューションがあります。
Molesをお勧めします。これは無料で効率的で、Moqのようなラムダ式を使用するためです。ただ一つの重要な詳細:モグラはモックではなくスタブを提供します。したがって、インターフェイスとデリゲートにはMoqを使用できます;)
モック:インターフェースを実装し、特定のメソッドからスローする例外を返す/例外に動的に値を設定する機能を許可し、チェックする機能を提供するクラス特定のメソッドが呼び出されたかどうか。
スタブ:メソッドが呼び出されたかどうかを確認する機能を提供しないことを除いて、モッククラスに似ています。
.NETには、MOQおよびその他のモックライブラリを除外する可能性があります。モックする静的メソッドを含むアセンブリでソリューションエクスプローラーを右クリックし、フェイクアセンブリの追加を選択する必要があります。次に、アセンブリの静的メソッドを自由にモックできます。
System.DateTime.Now
静的メソッドをモックしたいとします。たとえば、これを次のように実行します。
using (ShimsContext.Create())
{
System.Fakes.ShimDateTime.NowGet = () => new DateTime(1837, 1, 1);
Assert.AreEqual(DateTime.Now.Year, 1837);
}
静的プロパティとメソッドごとに同様のプロパティがあります。
これは、 Pose nugetから入手可能なライブラリで実現できます。とりわけ静的メソッドをモックできます。テストメソッドでこれを書いてください:
Shim shim = Shim.Replace(() => StaticClass.GetFile(Is.A<string>()))
.With((string name) => /*Here return your mocked value for test*/);
var sut = new Service();
PoseContext.Isolate(() =>
result = sut.foo("filename") /*Here the foo will take your mocked implementation of GetFile*/, shim);
詳細については、こちらを参照してください https://medium.com/@tonerdo/unit-testing-datetime-now-in-c-without-using-interfaces-978d372478e8
テスト目的で外部から設定できるデリゲートを呼び出すために、静的メソッドをリファクタリングするという概念で遊んでいます。
これはテストフレームワークを使用せず、完全に特注のソリューションになりますが、リファクタリングは呼び出し元の署名に影響を与えないため、比較的安全です。
これが機能するためには、静的メソッドにアクセスする必要があるため、System.DateTime
などの外部ライブラリに対しては機能しません。
以下に、2つのパラメーターを受け取る戻り値型と、戻り値型を持たないジェネリックを含む、いくつかの静的メソッドを作成した例を示します。
メインの静的クラス:
public static class LegacyStaticClass
{
// A static constructor sets up all the delegates so production keeps working as usual
static LegacyStaticClass()
{
ResetDelegates();
}
public static void ResetDelegates()
{
// All the logic that used to be in the body of the static method goes into the delegates instead.
ThrowMeDelegate = input => throw input;
SumDelegate = (a, b) => a + b;
}
public static Action<Exception> ThrowMeDelegate;
public static Func<int, int, int> SumDelegate;
public static void ThrowMe<TException>() where TException : Exception, new()
=> ThrowMeDelegate(new TException());
public static int Sum(int a, int b)
=> SumDelegate(a, b);
}
単体テスト(xUnitおよびShouldly)
public class Class1Tests : IDisposable
{
[Fact]
public void ThrowMe_NoMocking_Throws()
{
Should.Throw<Exception>(() => LegacyStaticClass.ThrowMe<Exception>());
}
[Fact]
public void ThrowMe_EmptyMocking_DoesNotThrow()
{
LegacyStaticClass.ThrowMeDelegate = input => { };
LegacyStaticClass.ThrowMe<Exception>();
true.ShouldBeTrue();
}
[Fact]
public void Sum_NoMocking_AddsValues()
{
LegacyStaticClass.Sum(5, 6).ShouldBe(11);
}
[Fact]
public void Sum_MockingReturnValue_ReturnsMockedValue()
{
LegacyStaticClass.SumDelegate = (a, b) => 6;
LegacyStaticClass.Sum(5, 6).ShouldBe(6);
}
public void Dispose()
{
LegacyStaticClass.ResetDelegates();
}
}
私はこれが少し遅いことを知っていますが、このラウンドアバウトの解決策により、Moqを使用して静的メソッドをモックできました。
これを行うために、1つのメソッドが静的メソッドStaticClass.GetFile
を呼び出すクラスを作成しました(Placeholder
と呼びましょう)。
public class Placeholder{
//some empty constructor
public File GetFile(){
File f = StaticClass.GetFile(filePath);
return f;
}
}
次に、foo
でStaticClass.GetFile
を呼び出す代わりに、Placeholder
のインスタンスを作成し、GetFile
関数を呼び出しました。
public void foo(string filePath)
{
Placeholder p = new Placeholder();
File f = p.GetFile(filePath);
}
ユニットテストでは、StaticClass.GetFile
をモックする代わりに、GetFile
クラスの非静的Placeholder
メソッドをモックすることができました。