Модульное тестирование / Как утверждать метод без фиксации назывался / C # / Rhino Mocks / NUnit

У меня есть MailService, который позволяет мне отправлять электронную почту, которая реализует следующий интерфейс.

public interface IMailService
  {
    bool SendRegisteringEmail(RegisterModel registerModel);
    bool SendMail(MailMessage mailMessage);
    MailMessage CreateRegisteringMailMessage(RegisterModel registerModel);

    IAppSettingsRepository AppSettingsRepository { get; set; }

    ISmtpClient SmtpClient { get; set; }
  }

Функция SendRegisteringEmail должна вызвать CreateRegisteringMailMessage, затем передать ответное сообщение MailMessage функции SendMail, а SendRegisteringEmail должен вернуть возвращаемое логическое значение SendMail.

Я использую NUnit и Rhino Mocks для выполнения своего теста, я новичок в тестировании (1 неделя) и практикую TDD (по крайней мере, я пытаюсь). Моя проблема в том, что я не знаю, как утверждать, что CreateRegisteringMailMessage был вызван, когда я вызываю SendRegisteringEmail, потому что MailService не является имитацией объекта. Вот мой тест:

[Test]
public void SendRegisteringEmail_ShouldCallCreateRegisteringMailMessage()
{
  //Arrange
  //MailMessage mailMessage = new MailMessage();
  IMailService mailService = new MailService { AppSettingsRepository = CreateAppSettingsMockReposotory() };
  //ISmtpClient smtpClientStub = MockRepository.GenerateStub<ISmtpClient>();
  //mailService.SmtpClient = smtpClientStub;
  //smtpClientStub.Stub(f => f.Send(mailMessage)).Return(true);


  //Act
  mailService.SendRegisteringEmail(ValidRegisterModel);

  //Assert
  mailService.AssertWasCalled(f => f.CreateRegisteringMailMessage(ValidRegisterModel));
}

При запуске теста появляется следующая ошибка: FtpWeb.Tests.MailServiceTests.SendRegisteringEmail_ShouldCallCreateRegisteringMailMessage: System.InvalidOperationException: объект FtpWeb.Models.MailService не является имитацией.

Я понимаю, почему я получаю эту ошибку, но теперь как проверить свой звонок. Поскольку это один и тот же объект, я не могу издеваться над ним, чтобы проверить. Если кто-нибудь может дать мне какое-то руководство, чтобы решить эту проблему.

Спасибо.


person Luuna    schedule 26.08.2010    source источник


Ответы (3)


Методы CreateRegisteringMailMessage и SendMail кажутся на более низком уровне абстракции, чем SendRegisteringEmail. Вы можете подумать о создании класса более высокого уровня, содержащего SendRegisteringEmail, и этот класс будет использовать IMailService, который вы можете имитировать и утверждать как обычно.

Если первое решение не подходит, то вы должны рассматривать SendRegisteringEmail в целом, так что эффективно вы должны протестировать побочные эффекты как CreateRegisteringMailMessage, так и SendMail в одном тесте (немного запаха кода, поэтому я предлагаю извлечь еще один уровень косвенности) - см. ответ Джона.

person Grzenio    schedule 26.08.2010

Я не уверен, что вам следует проверять, что вызывается CreateRegisteringMailMessage. Вам следует просто проверить результат вызова SendRegisteringEmail. Что это должно делать с вашей MailService реализацией? Предположительно это повлияет на другую службу - так что проверьте это.

Это был бы странный дизайн интерфейса, который настаивал на том, что для вызова одного метода потребуется вызов другого метода в интерфейсе - например, довольно часто вы можете заставить оба метода вызывать один частный общий метод.

person Jon Skeet    schedule 26.08.2010
comment
Я должен удалить метод SendMail из своего интерфейса и сделать его закрытым? Может, я должен сделать то же самое с CreateRegisteringMailMessage? Если я это сделаю, я больше не смогу его тестировать, поскольку у меня есть некоторая логика в последнем, который мне нужно проверить. - person Luuna; 26.08.2010
comment
@Luuna: Я недостаточно знаю об интерфейсе, чтобы знать ... захочет ли кто-нибудь публично вызвать CreateRegisteringMailMessage? Похоже, это не должно быть для меня частью публичного интерфейса. Обратите внимание, что разумно (IMO) тестировать методы, которые не являются частью интерфейса - вам не нужно ссылаться на тестируемый класс через интерфейс, вы можете использовать конкретное имя класса. - person Jon Skeet; 26.08.2010

Это часто называют «ощущением» внутри тестируемого класса: вы должны найти способ узнать, был ли вызван метод. Подобные вещи хорошо освещены в книге ' Эффективная работа с устаревшим кодом ', который должен иметь каждый (отказ от ответственности: я не связан с автором книги, я просто думаю, что это хорошая книга ).

Что делает метод CreateRegisteringMailMessage? Влияет ли это на содержимое экземпляра RegisterModel, переданного во время вызова?

person Dr Herbie    schedule 26.08.2010
comment
Единственная книга о тестировании, которая у меня есть, - это «Искусство модульного тестирования» Роя Ошерова. Я думал о покупке этого, но не сделал этого, возможно, мне стоит. CreateRegisteringMailMessage не изменяет содержимое RegisterModel, он создает MailMessage, заполняет некоторую информацию с помощью RegisterModel и возвращает MailMessage. - person Luuna; 26.08.2010
comment
Итак, можете ли вы почувствовать активность вызова CreateRegisteringMailMessage в содержимом MailMessage? - person Dr Herbie; 26.08.2010
comment
@ Доктор Херби: Извините, но я не понимаю, что вы мне говорите. CreateRegisteringMailMessage будет вызываться только SendRegisteringEmail для создания MailMessage, это то, что вы просили? - person Luuna; 26.08.2010
comment
Да - если бы ваш тест мог получить доступ к MailMessage, мог бы он выяснить, был ли вызван CreateRegisteringMailMessage? Если да, то рассмотрите способы проверки отправленного почтового сообщения. - person Dr Herbie; 26.08.2010