web-dev-qa-db-ja.com

Moqを使用したC#でのユニットテスト保護メソッド

最近、抽象基本クラスを実装するテストでダミークラスを作成するのではなく、Moqを使用して抽象基本クラスを単体テストできることに気付きました。 moqを使用して抽象クラスの具象メソッドをテストする方法 を参照してください。例:できるよ:

public abstract class MyAbstractClass 
{
    public virtual void MyMethod()
    {
        // ...    
    }
}

[Test]
public void MyMethodTest()
{
    // Arrange            
    Mock<MyAbstractClass> mock = new Mock<MyAbstractClass>() { CallBase = true };

    // Act
    mock.Object.MyMethod();

    // Assert
    // ... 
}

今、私がラッパークラスを作成せずに保護されたメンバーをテストするを可能にする同様のテクニックがあるかどうか疑問に思いました。つまりこの方法をどのようにテストしますか:

public class MyClassWithProtectedMethod
{
    protected void MyProtectedMethod()
    {

    }
}

私はMoq.Protected名前空間を知っていますが、私が見る限り、それはあなたが例えばで期待を設定することしかできません。

mock.Protected().Setup("MyProtectedMethod").Verifiable();

ここでの明白な答えは「保護されたメソッドをテストせず、パブリックメソッドのみをテストする」であることも認識していますが、それは別の議論です。 Moqを使用してこれが可能かどうかを知りたいだけです。

更新:以下は、これを通常どおりにテストする方法です。

public class MyClassWithProtectedMethodTester : MyClassWithProtectedMethod
{
    public void MyProtectedMethod()
    {
        base.MyProtectedMethod();
    }
}

前もって感謝します。

16
magritte

手始めに、抽象メソッドをユニットテストする意味はありません。実装はありません!抽象メソッドが呼び出されたことを確認して、不純な抽象クラスを単体テストすることをお勧めします。

[Test]
public void Do_WhenCalled_CallsMyAbstractMethod()
{
    var sutMock = new Mock<MyAbstractClass>() { CallBase = true };
    sutMock.Object.Do();
    sutMock.Verify(x => x.MyAbstractMethod());
}

public abstract class MyAbstractClass
{
    public void Do()
    {
        MyAbstractMethod();
    }

    public abstract void MyAbstractMethod();
}

Doが仮想の場合に備えて、これを部分的なモックに変換するようにCallBaseを設定したことに注意してください。そうでなければ、MoqはDoメソッドの実装に取って代わります。

Protected()を使用すると、保護されたメソッドが同様の方法で呼び出されたことを確認できます。

Moqまたは別のライブラリを使用してモックを作成する場合、要点は実装をオーバーライドすることです。保護されたメソッドのテストには、既存の実装の公開が含まれます。それはMoqが行うように設計されていることではありません。 Protected()は、保護されたメンバーをオーバーライドするためのアクセスを提供します(文字列ベースであるため、おそらくリフレクションを介して)。

保護されたメソッドを呼び出すメソッドを使用してテストの子孫クラスを作成するか、単体テストでリフレクションを使用して保護されたメソッドを呼び出します。

または、さらに良いことに、保護されたメソッドを直接テストしないでください。

8
TrueWill

Moqで保護されたメンバーを呼び出す別の方法は、次のテンプレートです。

  1. クラスで、保護されたメンバーを使用して、関数を仮想としてマークします。例えば:

    public class ClassProtected
        {
            public string CallingFunction(Customer customer)
            {
                var firstName = ProtectedFunction(customer.FirstName);
                var lastName = ProtectedFunction(customer.LastName);
    
                return string.Format("{0}, {1}", lastName, firstName);
            }
    
            protected virtual string ProtectedFunction(string value)
            {
                return value.Replace("SAP", string.Empty);
            }
        }
    

次に、ユニットテストで参照を追加します

 using Moq.Protected;

ユニットテストでは、次のように書くことができます。

    [TestFixture]
    public class TestClassProttected
    {
        [Test]
        public void all_bad_words_should_be_scrubbed()
        {
            //Arrange
            var mockCustomerNameFormatter = new Mock<ClassProtected>();

            mockCustomerNameFormatter.Protected()
                .Setup<string>("ProtectedFunction", ItExpr.IsAny<string>())
                .Returns("here can be any value")
                .Verifiable(); // you should call this function in any case. Without calling next Verify will not give you any benefit at all

            //Act
            mockCustomerNameFormatter.Object.CallingFunction(new Customer());

            //Assert
            mockCustomerNameFormatter.Verify();
        }
    }

ItExprに注意してください。 Itの代わりに使用する必要があります。別の落とし穴がVerizableであなたを待っています。理由はわかりませんが、VerizableVerifyを呼び出さないと呼び出されません。

10
Yuriy Zaletskyy

「プライベートではなくパブリックAPIをテストする」という思考プロセスについてはすでに触れました。また、クラスから継承し、その方法で保護されたメンバーをテストする手法についても説明しました。これらは両方とも有効なアプローチです。

そのすべての下で、単純な真実は、この実装の詳細(プライベートまたは保護されたメンバーが重要であるため)を、それを使用するパブリックAPIを介して間接的にではなく、直接テストするのに十分重要であると考えることです。 isこれが重要である場合、おそらくそのownクラスに昇格するのに十分重要です。 (結局のところ、それが非常に重要である場合、おそらくMyAbstractClassが持つべきではない責任です。)クラスのインスタンスはMyAbstractClass内で保護されるため、基本型と派生型のみが保護されます。インスタンスにアクセスできますが、クラス自体はそれ以外の場合は完全にテスト可能であり、必要になった場合は他の場所で使用できます。

abstract class MyAbstractClass 
{
     protected ImportantMethodDoer doer;
}

class ImportantMethodDoer
{
     public void Do() { }
}

そうでなければ、あなたはすでに特定したアプローチに任せられます*。


* Moqは、特定のツールを使用していないため、プライベートメンバーまたは保護されたメンバーにアクセスするためのメカニズムを提供する場合と提供しない場合があります。私の答えは、建築の観点からです。

5
Anthony Pegram