滥用Moq来测试if条件
本文关键字:if 条件 测试 Moq 滥用 | 更新日期: 2023-09-27 18:29:06
考虑以下类别:
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是不是很糟糕,或者我应该使用另一个框架来进行这种测试?或者这是我的代码设计中出现问题的结果?
是否应该为调用验证编写测试存在一些争论,我试图避开它们,我宁愿测试外部行为。你可以测试一些东西,看看是否达到了预期的结果,而不需要深入内部。当然,这并不总是可能的。
话虽如此,我还是要给你举个例子(尽我所能)。假设我们有一个名为Greeter
的类,它应该向所有stackerflow用户发送一条烦人的短信。现在,假设为了发送短信,你已经在其他地方编写了一些其他基础设施代码(已经测试过了)。假设这段代码将是一个名为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));
}
同样,很抱歉,这可能不是最好的例子,但这是漫长的一天,这是我能想到的最好的例子:)。
我希望它能有所帮助。
我相信您要问的真正问题是如何测试方法called
在test
类中执行?
要回答这个问题,你必须问自己,"执行方法called
后,test
对象会有什么不同?"然后,你写一个单元测试,以间接的方式验证对象test
是否以预期的方式发生了变化。
和其他人所说的一样,Moq在某种程度上被用来隔离对特定测试不重要的代码。在您的情况下,您不想创建mock——您需要测试实际的代码!
我的答案是,如果通过调用called
无法看到test
对象是如何变化的,那么您可能需要思考called
正在做什么的逻辑。或者,您需要对test
应用进一步的操作,这些操作将公开可测试的不同状态。
例如,可能预期的行为是:
- 如果在调用
called()
之后调用foo()
,则Enabled
为是的,但是 - 如果调用
foo()
而不调用called()
,则Enabled
为false
因此,在测试中,您必须对测试中的类执行几个操作(如调用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);
一个有意义的最小例子是:
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());
}
}
重新表述我的评论:
你不应该测试类的内部——测试外部行为。