1

次のクラスを検討してください。

public class test
{
    public void start()
    {
        if (true)
            called();
    }

    internal protected virtual void called()
    {

    }
}

if (true)アンダーテストを行いたい。私の最初のアイデアは、呼び出されたことを確認するために Moq を使用することでしたcalled()。私はこのテストで終わった:

[TestFixture]
public partial class TestMethodInvocation
{
    [Test]
    public void TestWithMoqVerify()
    {
        var mock = new Mock<test>() {CallBase = true};
        mock.Object.start();
        mock.Verify(t => t.called());
    }
}

これを機能させるのにいくつか問題があり、テスト中のクラスをモックするために Moq を使用しないようにアドバイスされたこの質問を投稿しました。

そこで、サブクラスを追加し、プロパティを使用してメソッドが呼び出されたかどうかをテストしました。

public class test2 : test
{
    public bool WasCalled { get; set; }

    internal protected override void called()
    {
        WasCalled = true;
    }
}

public partial class TestMethodInvocation
{
    [Test]
    public void TestWithSubclassProperty()
    {
        var test = new test2();
        test.start();
        Assert.IsTrue(test.WasCalled);
    }
}

どちらのアプローチも機能しますが、サブクラスを作成する必要がないため、Moq 実装は実質的にテスト コードの量の半分です。このように Moq を使用するのは非常に悪いことですか? それとも、この種のテストを行うために別のフレームワークを使用する必要がありますか? それとも、これは私のコードの設計上の問題の結果ですか?

4

3 に答える 3

3

呼び出し検証用のテストを作成する必要があるかどうかについては、少し議論があります。私はそれらを避けようとしています。むしろ、外部の動作をテストしたいと思います。内部を利用することなく、何かをテストして、目的の結果が達成されたかどうかを確認します。もちろん、これは常に可能であるとは限りません。

さて、そうは言っても、例を挙げようと思います(できる限り最善の方法です)。-という名前のクラスGreeterがあるとします。これは、迷惑な SMS をすべての stackoverflow サブスクライバーに送信することになっています。ここで、SMS を送信するために、他のインフラストラクチャ コードを別の場所 (既にテスト済みで、すべて) に記述したとします。このコードは、次のようなインターフェイスの実装になるとしますIMessageService(私の例がうまくいかなかったらすみません)。

public interface IMessageService
{
    void SendSMS(string message);
}

さらに、SubscriberRepositoryすべての StackOverflow サブスクライバーを取得できる があるとします。何かのようなもの:

public interface ISubscriberRepository
{
    IEnumerable<Subscriber> GetStackOverflowSubscribers();
}

これがあなたのGreeterクラスです:

public class Greeter
{
    private readonly IMessageService _messageService;
    private readonly ISubscriberRepository _subscriberRepository;

    public Greeter(IMessageService messageService, ISubscriberRepository subscriberRepository)
    {
        _messageService = messageService;
        _subscriberRepository = subscriberRepository;
    }

    public void SendGreetingToStackOverflow()
    {
        IEnumerable<Subscriber> stackOverflowers = _subscriberRepository.GetStackOverflowSubscribers();

        foreach (Subscriber overflower in stackOverflowers)
        {
            _messageService.SendSMS("Hello World!");
        }
    }
}

実際に を使用しIMessageServiceて SMS を送信していることがわかります。この時点で、(おそらく)何回SendSMS()呼び出されたかをテストしxます。この場合、回数は StackOverflow サブスクライバーの量と同じにする必要があります。したがって、テストは次のようになります。

[Test]
public void SendGreetingToStackOverflow_CallsIMessageServiceSendSMSTwoTimes()
{
    var mockMessageService = new Mock<IMessageService>();
    var mockSubscriberRepo = new Mock<ISubscriberRepository>();

    // we will mock the repo and pretend that it returns 2 subscibers
    mockSubscriberRepo
        .Setup(x => x.GetStackOverflowSubscribers())
        .Returns(new List<Subscriber>() {new Subscriber(), new Subscriber()});

    // this is the one we're testing, all dependencies are fake
    var greeter = new Greeter(mockMessageService.Object, mockSubscriberRepo.Object);

    greeter.SendGreetingToStackOverflow();

    // was it called 2 times (for each subscriber) ?
    mockMessageService.Verify(
        x => x.SendSMS("Hello World!"),
        Times.Exactly(2));
}

繰り返しますが、これはおそらく最良の例ではないことを申し訳ありませんが、長い一日であり、私が思いつくことができる最高のものです:)。

お役に立てば幸いです。

于 2013-08-25T12:23:29.063 に答える
1

あなたがやろうとしていることにできるだけ近い、ある程度意味のある最小限の例は次のとおりです。

interface Callable
{
  void Called();
}

class Test
{
  public Test(Callable x)
  {
    this.callable = callable;
  }

  public void Start()
  {
    if (true)
      callable.Called();
  }

  private Callable callable;
}

次に、テストは次のようになります。

[TestFixture]
public partial class TestMethodInvocation
{
  [Test]
  public void TestWithMoqVerify()
  {
    var callableMock = new Mock<Callable>();
    var test = new Test(callableMock);
    test.Start();
    callableMock.Verify(t => t.Called());
  }
}

私のコメントを言い換えると:

クラスの内部をテストするべきではありません - 外部の振る舞いをテストしてください。

于 2013-08-25T12:53:41.947 に答える
1

あなたが求めている本当の質問は、メソッドがクラスcalled内で実行されることをどのようにテストするかということだと思いますか?test

それに答えるには、「testメソッドcalledが実行された後、オブジェクトはどのように変化するでしょうか?」と自問する必要があります。test次に、オブジェクトが期待どおりに変更されたことを間接的に検証する単体テストを作成します。

他の誰もが言ったことに加えて、Moq は特定のテストにとって重要でないコードを分離するために使用されます。あなたの場合、モックを作成したくありません-実際のコードをテストする必要があります!

私の答えは、呼び出しによってオブジェクトがcalledどのようにtest変化したかを確認する方法がない場合は、何が行われているかのロジックを考える必要があるかもしれないということですcalledtestまたは、さらに操作を適用して、テスト可能な別の状態を公開する必要があります。

たとえば、予想される動作は次のとおりです。

  • foo()が呼び出された後called()に呼び出された場合、Enabled真ですが、
  • foo()が呼び出されずcalled()に呼び出された場合はEnabledfalse です。

foo()したがって、テストでは、外部からテスト可能な状態にする前に、テスト対象のクラスに対していくつかの操作 ( の呼び出しなど) を実行する必要があります。

var test = new test();
test.foo();
Assert(test.Enabled, Is.False);

var test = new test();
test.start();
test.foo();
Assert(test.Enabled, Is.True);
于 2013-08-25T12:54:32.740 に答える