Автор: Sergey Teplyakov
DISCLAIMER: исходный код Verification Fakes можно найти на github, а скомпилированная версия доступна через nuget.org/packages/VerificationFakes.
В прошлой заметке мы рассмотрели примеры использования Microsoft Fakes для создания стабов заглушек, возвращающих требуемые данные. Однако в некоторых случаях нам все же нужно проверить, что в определенных условиях тестируемый класс обращается к некоторой зависимости. Такой вид тестирования называется тестированием поведением и именно такой вид подделок каркасом Microsoft Fakes практически не поддерживается.
Все что можно сделать в Microsoft Fakes это установить наблюдатель с помощью StubBase.InstanceObserver, но пользоваться таким подходом вручную довольно сложно.
Предположим, что нам у нас есть все тот же интерфейс ILogWriter, который затем передается через аргументы конструктора классу Logger и мы хотим убедиться в том, что в нужные моменты времени Logger таки вызывает нужные методы своей зависимости.public interface ILogWriter{void Write(string message),void Write(int value),}public class Logger{private readonly ILogWriter _logWriter,public Logger(ILogWriter logWriter) { _logWriter = logWriter, }public void Write(string message) { _logWriter.Write(message), }public void Write(int value) { _logWriter.Write(value), }}
Так, имея заглушку интерфейса ILogWriter, сгенерированную Microsoft Fakes, мы можем написать тест, который будет проверять, что Logger при вызове метода Write вызывает соответствующий метод своей зависимости.
Как уже говорилось выше, для этого вначале нужно реализовать интерфейс IStubObserver и передать его сгенерированному стабу:class CustomObserver : IStubObserver{public string CalledMethodName,public object CalledMethodArgument,public void Enter(Type stubbedType, Delegate stubCall, object arg1) { CalledMethodName = stubCall.Method.Name, CalledMethodArgument = arg1, }// Остальные перегрузки методы Enter опущены}[Test]public void Logger_Write_Calls_For_Enter_Method(){// Arrange var stub = new StubILogWriter(),var customObserver = new CustomObserver(),// Providing custom observer to the stub stub.InstanceObserver = customObserver,var logger = new Logger(stub),// Act logger.Write('Message'),// Assert Assert.That(customObserver.CalledMethodName, Is.StringContaining('ILogWriter.Write')),Assert.That(customObserver.CalledMethodArgument, Is.EqualTo('Message')),}
Очевидно, что подобный код ничем не отличается от создания моков вручную. Но поскольку точка расширения в Microsoft Fakes все же существует, я написал легковесную оболочку вокруг этой библиотеки, которая прячет всю существующую логику и предоставляет возможности по тестированию поведения элегантным образом.
Тестирование поведения с помощью Verification Fakes
Verification Fakes это легковесная оболочка над Microsoft Fakes, которая предоставляет интерфейс для тестирования поведения, аналогичный библиотеке Moq. Поэтому если у вас есть опыт использования Moq, то примеры Verification Fakes будут очень знакомыми.
Тогда, если мы сгенерируем фейки для этих интерфейсов, то сможем тестировать поведение с помощью Verification Fakes. Для этого из сгенерированного стаба нужно получить экземпляр Mock<,T>, с помощью методов расширения AsMock или AsStrictMock:
1. Метод Logger.Write(message) вызывает ILogWriter.Write с любым аргументом:var stub = new StubILogWriter(),Mock<,ILogWriter>, mock = stub.AsMock(),var logger = new Logger(stub),// Вызываем метод тестируемого классаlogger.Write('Hello, logger!'),// Проверяем, что был вызван метод Write с любым аргументомmock.Verify(lw =>, lw.Write(It.IsAny<,string>,())),
Теперь, если мы закомментируем строку с вызовом метода logger.Write, то метод mock.Verify сгенерирует исключение VerificationException с соответствующим сообщением об ошибке:
VerificationFakes.VerificationException: Expected invocation on the mock 1 times, but was 0 times. Expected call: lw =>, lw.Write(It.IsAny<,string>,()) Actual calls: No invocations performed.
2. Метод LogWriter.Write вызывается с заданным аргументом:// Вызываем метод тестируемого классаlogger.Write('Hello, logger!'),// Проверяем, что вызван метод с конкретными аргументамиmock.Verify(lw =>, lw.Write('Hello, logger!')),
В этом случае, если будет вызван метод Write с другим аргументом мы получим следующую ошибку:
Expected invocation on the mock 1 times, but was 0 times.Expected call: lw =>, lw.Write('Hello, logger!')Actual calls:VerificationFakes.Samples.ILogWriter.Write('Some other message')
3. Метод LogWriter.Write вызывается в точности один раз:// Вызываем метод тестируемого классаlogger.Write('Hello, logger!'),// Проверяем, что метод вызван в точности один разmock.Verify(lw =>, lw.Write(It.IsAny<,string>,()),Times.Once()),
ПРИМЕЧАНИЕАналогично библиотеке Moq, Verification Fakes поддерживает возможность указать, сколько раз был вызван метод. Для этого служат фабричные методы класса Times: AtLeast(int), AtMost(int), Exactly и Between.
4. Метод LogWriter.Write(int) не вызывался при вызове Logger.Write(string):// Вызываем метод тестируемого классаlogger.Write('Hello, logger'),// Проверяем, что метод Write(int) не был вызванmock.Verify(lw =>, lw.Write(It.IsAny<,int>,()), Times.Never()),
5. Метод LogWriter.Write(int) вызван с аргументом в указанном диапазоне:// Вызываем метод тестируемого классаlogger.Write(42),// Проверяем, что аргумент метод Write(int) находится в указанном диапазонеmock.Verify(lw =>, lw.Write(It.IsInRange(40, 50))),
6. Использование методов Setup и Verify для указания нескольких ожидаемых вызовов:var stub = new StubILogWriter(),var mock = stub.AsMock(),mock.Setup(lw =>, lw.Write(It.IsAny<,string>,())),mock.Setup(lw =>, lw.Write(It.IsAny<,int>,())),var logger = new Logger(mock.Object),// Вызываем два метода тестируемого классаlogger.Write('Hello, logger!'),logger.Write(42),// Проверяем, что были вызваны все методы, // указанные путем вызова метода Setupmock.Verify(),
7. Пример использование строгого мока:var stub = new StubILogWriter(),var mock = stub.AsStrictMock(),mock.Setup(lw =>, lw.Write('Foo')),// Вызываем метод тестируемого классаvar logger = new Logger(mock.Object),logger.Write('Foo'),
В этом случае, при попытке вызова любого метода LogWriter помимо метода Write с указанным аргументом, мы получим VerificationException. Так, в случае вызова метода logger.Write(42) мы получим следующее сообщение:
Following invocations failed with mock behavior Strict:VerificationFakes.Samples.ILogWriter.Write(42)Expected invocations:lw =>, lw.Write('Foo'), 1 times.
Заключение
Я честно признаюсь, что отношусь к тестированию поведения с осторожностью. При интенсивном использовании возникает опасность создания множества хрупких тестов, которые проверяют не абстрактное поведение, а завязаны на внутреннюю реализацию тестируемого класса.
Тем не менее, иногда такие тесты вполне полезны, особенно если они не завязаны на точное значение аргументов и проверяют действительно ключевое поведение тестируемых классов.
Для меня же Verification Fakes был весьма интересным домашним проектом, и, возможно, он будет интересен тем двоим программистам, которые используют Microsoft Fakes в своих проектахJ
Дополнительные ссылки
- Microsoft Fakes. Тестирование состояния
- Verification Fakes on github
- Verification Fakes on nuget