Чтобы по-настоящему понять Моксы, сначала нужно понять четыре понятия
Что такое тестирование взаимодействия
Что такое каркасы изоляции (например, издевательства над носорогом)
Что такое подделка
Что такое заглушка
И, наконец, что такое издевательство
Тестирование взаимодействия заключается в проверке, вызывает ли определенный метод (тот, который вы тестируете), другой метод в другом классе (внешняя зависимость) с параметрами, которые он должен использовать.
Представьте, что у вас есть метод в некотором классе, который регистрирует каждый раз, когда он вызывается с недопустимыми параметрами. Чтобы прояснить разницу между заглушками и макетами, я добавил 2 внешних зависимости (IStringAnalyzer и ILooger):
class SomeClass
{
IStringAnalyzer stringAnalizer;
ILogger logger;
public SomeClass(IStringAnalyzer stringAnalyzer, ILogger logger)
{
this.logger = logger;
this.stringAnalyzer = stringAnalyzer;
}
public void SomeMethod(string someParameter)
{
if (stringAnalyzer.IsValid(someParameter))
{
logger.Log("Invalid string");
}else
{
//do something with someParameter
}
}
}
В этом примере вы хотите проверить, вызывает ли вызов метода SomeClass SomeMethod с недопустимым параметром метод журнала в ILogger со строковым параметром «Недопустимая строка».
Вы не не хотите использовать ваши "настоящие" реализации IStringAnalyzer и ILogger, потому что они могут иметь ошибки, и поскольку это unit тестирование, вы просто хотите протестировать одну вещь за раз, если вы проверяете, как тестировать несколько вещей одновременно, то вы действительно делаете интеграционное тестирование. Причина, по которой тестируется только одна вещь, заключается в том, что, если ваш тест не пройден, вы сразу же знаете, что он терпит неудачу из-за единственного, что вы тестируете.
Вам нужно предоставить две альтернативные реализации, одну для IStringAnalyzer и одну для ILogger, чтобы вы могли правильно выполнить этот тест. В этих альтернативных реализациях будет разница с точки зрения того, что им нужно делать. Для IStringAnalyzer вы просто хотите, чтобы при вызове он возвращал false, чтобы тестируемый метод проходил путь кода, который вы хотите протестировать. Вы действительно не заботитесь о значении параметра (someParameter).
Для метода журнала ILogger вы хотите знать, что он был вызван и что он был вызван с помощью «неверной строки», чтобы вы могли подтвердить это в своем тесте.
Эти две альтернативные реализации IStringAnalyzer и ILogger оба называются «подделками» (потому что они подделывают внешние зависимости), но одна из них является заглушкой (IStringAnalyzer), а другая - имитатором (ILogger). Заглушка предназначена только для того, чтобы привести вас туда, куда вам нужно пойти в тесте (в этом случае метод IsValid IStringAnalyzer возвращает false). Это макет, позволяющий вам проверить, правильно ли было выполнено взаимодействие с внешней зависимостью (в данном случае ILogger). Некоторые люди называют насмешки (или насмешки такого типа) тестовым шпионом (на мой взгляд, бесконечно лучшим именем). И да, есть другие виды издевательств (хотя я никогда не использовал их). Хороший источник для этого - Работа с унаследованным кодом от Michael Feathers и Роя Ошерова «Искусство модульного тестирования».
Вы можете создать заглушку и макет "вручную", например:
class StringAnalyzerStub : IStringAnalyzer
{
public bool whatToReturn;
public StubStringAnalyzerStub(bool whatToReturn)
{
this.whatToReturn = whatToReturn;
}
public bool IsValid(string parameter)
{
return whatToReturn;
}
}
class LoggerMock : ILogger
{
public string WhatWasPassedIn;
public void Log(string message)
{
WhatWasPassedIn = message;
}
}
А вот и тест:
[Test]
public void SomeMethod_InvalidParameter_CallsLogger
{
IStringAnalyzer s = new StringAnalyzerStub(false); //will always return false when IsValid is called
ILogger l = new LoggerMock();
SomeClass someClass = new SomeClass(s, l);
someClass.SomeMethod("What you put here doesnt really matter because the stub will always return false");
Assert.AreEqual(l.WhatWasPassedIn, "Invalid string");
}
Смысл выполнения этого вручную состоит в том, что он подвержен ошибкам и сложен в обслуживании, поэтому необходима изоляция сред, таких как Rhino Mocks. Они позволяют динамически создавать эти макеты и заглушки. Вот как будет выглядеть тот же тест с использованием Rhino Mocks (с использованием синтаксиса упорядочения, действия, утверждения):
[Test]
public void SomeMethod_InvalidParameter_CallsLogger
{
Rhino.Mocks.MockRepository mockRepository = new Rhino.Mocks.MockRepository();
IStringAnalyzer s = mockRepository.Stub<IStringRepository>();
s.Expect(s => s.IsValid("something, doesnt matter").IgnoreParameters().Return(false);
ILogger l = mockRepository.DynamicMock<ILogger>();
l.Log("Invalid string");
SomeClass someClass = new SomeClass(s, l);
mockRepository.ReplayAll();
someClass.SomeMethod("What you put here doesnt really matter because the stub will always return false");
l.AssertWasCalled(l => l.Log("Invalid string"));
}
Термины не определены сами по себе:)
Отказ от ответственности: я написал все это в текстовом редакторе, поэтому в коде могут быть некоторые синтаксические ошибки ...