В продолжение предыдущего блога о тестировании программного обеспечения: “Тестирование как часть процесса по разработке” команда тестировщиков ALG расскажет об изолированном тестировнии ПО.
Если разрабатываемый компонент зависит от другого компонента, который параллельно разрабатывается кем-то другим, возникает проблема: пока чужой компонент не будет готов – нельзя тестировать свой. Проблема остается даже в том случае, если чужой компонент готов к использованию: его поведение может варьироваться в зависимости от многих факторов. Например, данные канала фондового рынка могут меняться каждую минуту, поэтому компонент, использующий их, будет трудно тестировать.
Решением будет изолировать компонент, подменяя зависимость не настоящей реализацией. Подмена симулирует настоящее поведение в достаточной мере для того, чтобы тесты могли выполняться. Термины «заглушка», «имитация» и «оболочка» иногда используются для определения конкретного вида подмены.
Вся суть в том, чтобы определить интерфейс для зависимости. Разрабатывать компонент следует так, чтобы в момент создания передавать ему экземпляр класса, реализующего интерфейс. Пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
C# // Этот интерфейс позволяет не зависеть от канала данных рынка: public interface IStockFeed { int GetSharePrice(string company); } // Тестируемый модуль: public class StockAnalyzer { private IStockFeed stockFeed; // Конструктор принимающий канал данных рынка: public StockAnalyzer(IStockFeed feed) { stockFeed = feed; } // Некоторые методы использующие канал данных рынка: public int GetContosoPrice() { ... stockFeed.GetSharePrice(...) ... } } |
Благодаря такому проектированию компонента, во время тестирования можно использовать подмененную, а в готовом приложении настоящую реализацию канала данных фондового рынка. Главное, чтобы подмена и настоящая реализация поддерживали один и тот же интерфейс.
Такое разделение компонентов называется “внедрение интерфейса”. Оно полезно тем что, делает один компонент менее зависимым от другого – код становится более гибким.
Можно сделать FakeStockFeed просто отдельным классом:
1 2 3 4 5 |
// В тестовом проекте. class FakeStockFeed : IStockFeed { public int GetSharePrice (string company) { return 1234; } } |
А затем, во время теста, компоненту передается экземпляр подмены:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
[TestClass] public class StockAnalysisTests { [TestMethod] public void ContosoPriceTest() { // Подготовка: var componentUnderTest = new StockAnalyzer(new FakeStockFeed()); // Действие: int actualResult = componentUnderTest.GetContosoPrice(); // Проверка: Assert.AreEqual(1234, actualResult); } } |
Однако, можно воспользоваться удобным механизмом Microsoft Fakes, благодаря которому можно легко создать подмену и не терять времени на написание лишнего кода.
Microsoft Fakes
В Visual Studio 2012 классы заглушки можно генерировать, используя MSTest. В Обозревателе Решений (Solution Explorer), нужно открыть ссылки тестового проекта, и выбрать модуль или компонент, для которого нужно создать заглушки — в нашем примере, Stock Feed. Можно выбрать другой проект или используемый ресурс, включая системный. В меню быстрого вызова нужно выбрать Add Fakes Assembly. И пересобрать проект.
Теперь тест можно написать вот так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
C# [TestClass] class TestStockAnalyzer { [TestMethod] public void TestContosoStockPrice() { // Подготовка: // Создание подмены stockFeed: IStockFeed stockFeed = new StockAnalysis.Fakes.StubIStockFeed() // Сгенерирован с помощью Fakes. { // Определение каждого метода: // Первоначальное имя метода + имена типов параметров: GetSharePriceString = (company) => { return 1234; } }; // В готовом приложении, stockFeed будет настоящим: var componentUnderTest = new StockAnalyzer(stockFeed); // Действие: int actualValue = componentUnderTest.GetContosoPrice(); // Проверка: Assert.AreEqual(1234, actualValue); } ... } |
Обращаем внимание на класс StubIStockFeed. Для каждого публичного типа в используемом компоненте, Microsoft Fakes генерирует классы заглушки. Имя типа такое же, как и оригинала, только добавлен префикс “Stub”. У сгенерированного класса есть представление для каждого метода, определенного в интерфейсе. Имя представления состоит из имени метода + имена типов параметров. Реализацию метода можно определять прямо в коде. Благодаря этому не нужно писать полноценный класс.
Заглушки также генерируются для методов получения и установки полей, для событий, и для параметризированных методов. К сожалению, IntelliSense не подсказывает новые имена при наборе, поэтому, чтобы проверить имя, нужно открыты категорию Fakes в Object Browser.
В проект также добавляется файл с расширением .fakes. Его можно редактировать для указания типов, для которых нужно создать заглушки. Более подробно – смотрите по ссылке Isolating Unit Test Methods with Microsoft Fakes.
«Имитации»
Имитация – это подмена, у которой могут быть разные состояния. Имитация может больше, чем просто возвращать одно и то же значение при вызове метода. Поведение имитаций можно менять в зависимости от модульного теста, в котором она используется. Имитации также могут регистрировать вызовы, сделанные тестируемыми компонентами. Пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
C# [TestClass] class TestMyComponent { public void TestVariableContosoPrice() [TestMethod] { // Подготовка: int priceToReturn; string companyCodeUsed; var componentUnderTest = new StockAnalyzer(new StubIStockFeed() { GetSharePriceString = (company) => { // Зарегистрировать значение параметра: companyCodeUsed = company; // Вернуть значение указанное в тесте: return priceToReturn; }; }; priceToReturn = 345; // Действие: int actualResult = componentUnderTest.GetContosoPrice(priceToReturn); // Проверка: Assert.AreEqual(priceToReturn, actualResult); Assert.AreEqual("CTSO", companyCodeUsed); } ... } |
В следующих заметках мы продолжим тему тестирования и отладки ПО; и расскажем об “оболочках” в Visual Studio, IntelliTrace и многом другом.