私は最近単体テストを行っており、MOQ フレームワークと MS テストを使用してさまざまなシナリオのモックを作成することに成功しました。プライベート メソッドをテストできないことはわかっていますが、MOQ を使用して静的メソッドをモックできるかどうかを知りたいです。
5 に答える
Moq (およびその他のDynamicProxyベースのモッキング フレームワーク) は、仮想メソッドまたは抽象メソッド以外のものをモックすることはできません。
Sealed/static クラス/メソッドは、Typemock (商用) や Microsoft Moles (無料、Visual Studio 2012 Ultimate /2013 /2015 ではFakesとして知られています) などの Profiler API ベースのツールでのみ偽装できます。
または、設計をリファクタリングして静的メソッドへの呼び出しを抽象化し、依存性注入を介してこの抽象化をクラスに提供することもできます。そうすれば、設計が改善されるだけでなく、Moq などの無料ツールでテストできるようになります。
テスト容易性を可能にする共通パターンは、ツールをまったく使用せずに適用できます。次の方法を検討してください。
public class MyClass
{
public string[] GetMyData(string fileName)
{
string[] data = FileUtil.ReadDataFromFile(fileName);
return data;
}
}
をモックしようとする代わりに、次のようにメソッドでFileUtil.ReadDataFromFile
ラップできます。protected virtual
public class MyClass
{
public string[] GetMyData(string fileName)
{
string[] data = GetDataFromFile(fileName);
return data;
}
protected virtual string[] GetDataFromFile(string fileName)
{
return FileUtil.ReadDataFromFile(fileName);
}
}
次に、単体テストで、から派生させてMyClass
それを呼び出しますTestableMyClass
。次に、メソッドをオーバーライドしてGetDataFromFile
、独自のテスト データを返すことができます。
それが役立つことを願っています。
他の回答で述べたように、MOQ は静的メソッドをモックできません。原則として、可能であれば静的を避ける必要があります。
時々それは不可能です。1 つは、レガシまたはサード パーティのコードを使用するか、静的な BCL メソッドを使用する場合です。
可能な解決策は、モックできるインターフェイスを使用して静的をプロキシにラップすることです
public interface IFileProxy {
void Delete(string path);
}
public class FileProxy : IFileProxy {
public void Delete(string path) {
System.IO.File.Delete(path);
}
}
public class MyClass {
private IFileProxy _fileProxy;
public MyClass(IFileProxy fileProxy) {
_fileProxy = fileProxy;
}
public void DoSomethingAndDeleteFile(string path) {
// Do Something with file
// ...
// Delete
System.IO.File.Delete(path);
}
public void DoSomethingAndDeleteFileUsingProxy(string path) {
// Do Something with file
// ...
// Delete
_fileProxy.Delete(path);
}
}
欠点は、多くのプロキシがある場合、ctor が非常に雑然とする可能性があることです (ただし、多くのプロキシがある場合、クラスがやりすぎている可能性があり、リファクタリングされる可能性があると主張できます)。
別の可能性は、その背後にあるインターフェースの異なる実装を持つ「静的プロキシ」を持つことです
public static class FileServices {
static FileServices() {
Reset();
}
internal static IFileProxy FileProxy { private get; set; }
public static void Reset(){
FileProxy = new FileProxy();
}
public static void Delete(string path) {
FileProxy.Delete(path);
}
}
私たちの方法は今
public void DoSomethingAndDeleteFileUsingStaticProxy(string path) {
// Do Something with file
// ...
// Delete
FileServices.Delete(path);
}
テストのために、FileProxy プロパティをモックに設定できます。このスタイルを使用すると、注入されるインターフェイスの数が減りますが、依存関係が少しわかりにくくなります (ただし、私が推測する元の静的呼び出しほどではありません)。
Moq は、クラスの静的メンバーをモックできません。
テスト容易性のためにコードを設計するときは、静的メンバー (およびシングルトン) を避けることが重要です。テスト容易性のためにコードをリファクタリングするのに役立つ設計パターンは、依存関係の挿入です。
これは、次のように変更することを意味します。
public class Foo
{
public Foo()
{
Bar = new Bar();
}
}
に
public Foo(IBar bar)
{
Bar = bar;
}
これにより、単体テストからモックを使用できます。本番環境では、すべてを結び付けることができるNinjectやUnityなどの依存性注入ツールを使用します。
これについては、以前ブログに書きました。よりテストしやすいコードに使用するパターンについて説明します。多分それはあなたを助けることができます:単体テスト、地獄か天国か?
もう 1 つの解決策は、Microsoft Fakes Frameworkを使用することです。これは、適切に設計されたテスト可能なコードを作成する代わりになるものではありませんが、役に立ちます。Fakes フレームワークを使用すると、静的メンバーをモックし、実行時にそれらを独自のカスタム動作に置き換えることができます。
私たちは通常、具体的なクラスに直接依存するのではなく、インターフェイスなどの抽象化に依存することによって、インスタンス (非静的) クラスとそのメソッドをモックします。
静的メソッドでも同じことができます。静的メソッドに依存するクラスの例を次に示します。(これはひどく不自然です。) この例では、静的メソッドに直接依存しているため、それをモックすることはできません。
public class DoesSomething
{
public long AddNumbers(int x, int y)
{
return Arithemetic.Add(x, y); // We can't mock this :(
}
}
public static class Arithemetic
{
public static long Add(int x, int y) => x + y;
}
メソッドをモックできるようにするためにAdd
、抽象化を注入できます。インターフェイスを注入する代わりに、Func<int, int, long>
またはデリゲートを注入できます。どちらでも機能しますが、デリゲートの目的を示す名前を付けて、同じ署名を持つ他の関数と区別できるため、デリゲートの方が好みです。
デリゲートと、デリゲートを注入したときのクラスの外観は次のとおりです。
public class DoesSomething
{
private readonly AddFunction _addFunction;
public DoesSomething(AddFunction addFunction)
{
_addFunction = addFunction;
}
public long AddNumbers(int x, int y)
{
return _addFunction(x, y);
}
}
これは、クラスのコンストラクターにインターフェイスを挿入する場合とまったく同じように機能します。
インターフェイスで行うのと同じように、Moq を使用してデリゲートのモックを作成できます。
var addFunctionMock = new Mock<AddFunction>();
addFunctionMock.Setup(_ => _(It.IsAny<int>(), It.IsAny<int>())).Returns(2);
var sut = new DoesSomething(addFunctionMock.Object);
...しかし、その構文は不可解です。私はそれをグーグルしなければなりませんでした。Moq の代わりに無名関数を使用すると、はるかに簡単になります。
AddFunction addFunctionMock = (x, y) => 2;
var sut = new DoesSomething(addFunctionMock);
正しい署名を持つ任意のメソッドを使用できます。必要に応じて、そのシグネチャを使用してテスト クラスに別のメソッドを定義し、それを使用することができます。
余談ですが、デリゲートを注入する場合、IoC コンテナーでそれをどのように設定すればよいでしょうか? インターフェイスと実装を登録するのと同じように見えます。使用IServiceCollection
:
serviceCollection.AddSingleton<AddFunction>(Arithemetic.Add);