1

考虑以下类:

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

    internal protected virtual void called()
    {

    }
}

我想进行if (true)测试。我最初的想法是使用最小起订量来验证是否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- 它应该向所有 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发送短信。此时,您想要(可能)测试是否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对象是如何变化的,那么也许你需要考虑一下called正在做的事情的逻辑。或者,您需要对其应用进一步的操作,test这将暴露一个可测试的不同状态。

例如,也许预期的行为是:

  • 如果foo()在被调用后被called()调用,Enabled则为真,但
  • 如果foo()在没有called()被调用的情况下被调用,Enabled则为假。

因此,在您的测试中,您必须先对您的被测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 回答