В последнее время появилось и продолжает появляться довольно много публикаций на тему разработки через тестирование.
Тема довольно интересная и стоит уделить немного времени ее исследованию.
В нашей команде мы уже год используем модульное тестирование.
В этой статье я хочу рассказать о том, что произошло и какой опыт мы в итоге получили.
Сразу оговорюсь, что примеры даны применительно к языку C# и платформе .
NET. Соответственно, подходы и реализации могут отличаться на других языках/платформах.
Так… Какими должны быть модульные тесты? Помимо того, что юнит-тесты должны соответствовать функциональности программного продукта, основным требованием является скорость.
Если после запуска набора тестов разработчик может сделать перерыв (в моем случае на перекур), то такие запуски будут происходить все реже (опять же в моем случае из-за боязни получить передозировку никотином).
В результате может случиться так, что юнит-тесты вообще не запустятся и, как следствие, будет потерян смысл их написания.
Программист должен иметь возможность запускать весь набор тестов в любой момент времени.
И этот набор должен быть завершен как можно быстрее.
Какие требования необходимо соблюдать, чтобы обеспечить скорость выполнения модульных тестов? Тесты должны быть небольшими В идеале на каждый тест приходится одно утверждение.
Чем меньшая часть функциональности покрывается модульным тестом, тем быстрее будет выполняться тест. Кстати, о дизайне.
Мне очень нравится подход, который формулируется как «организовать-действовать-утвердить».
Его суть заключается в четком определении предварительных условий в модульном тесте (инициализация тестовых данных, предварительные настройки), действие (собственно то, что тестируется) и постусловия (то, что должно быть в результат действия).
Такая конструкция повышает читаемость теста и упрощает его.
использовать в качестве документации для тестируемой функциональности.
Если в вашей разработке используется ReSharper от JetBrains, то очень удобно настроить шаблон, с помощью которого вы будете создавать шаблон для тестового примера.
Например, шаблон может выглядеть так:
И тогда разработанный таким образом тест может выглядеть примерно так (все имена вымышлены, совпадения случайны):[Test] public void Test_$METHOD_NAME$() { //arrange $END$ //act //assert Assert.Fail("Not implemented"); }
[Test]
public void Test_ForbiddenForPackageChunkWhenPackageNotFound()
{
//arrange
var packagesRepositoryMock = _mocks.Create<IPackagesRepository>();
packagesRepositoryMock
.
Setup(r => r.FindPackageAsync(_packageId))
.
Returns(Task<DatabasePackage>.
Factory.StartNew(() => null));
Register(packagesRepositoryMock.Object);
//act
var message = PostChunkToServer(new byte[] { 1, 2, 3 });
//assert
_mocks.VerifyAll();
Assert.That(message.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden));
}
Тесты должны быть изолированы от среды (базы данных, сети, файловой системы).
Этот пункт, пожалуй, самый спорный из всех.
В литературе часто обсуждаются примеры использования TDD для таких задач, как разработка калькулятора или телефонного справочника.
Понятно, что эти задачи оторваны от реальности и разработчик, возвращаясь к своему проекту, не знает, как применить полученные знания и навыки в повседневной работе.
Простейшие случаи, подобные пресловутому калькулятору, покрываются юнит-тестами, и весь остальной функционал развивается по тому же принципу.
В результате нет понимания, зачем тратить время на юнит-тесты, если простой код и так можно отладить, а сложный код все равно не покрывается тестами.
На самом деле нет ничего плохого в модульных тестах, использующих базу данных или файловую систему.
Так, по крайней мере, вы можете быть уверены в работоспособности того функционала, который по большому счету составляет ядро системы.
Вопрос только в том, как наиболее эффективно использовать внешнюю среду, сохраняя баланс между изоляцией тестов и скоростью выполнения тестов? Случай 1. Уровень доступа к данным (MS SQL Server) Если при разработке проекта используется сервер MS SQL, то ответом на этот вопрос может быть использование установленного экземпляра сервера MS SQL (Express, Enterprise или Developer Edition) для развертывания тестовой базы данных.
Такую базу данных можно создать с помощью стандартных механизмов, используемых в MS SQL Management Studio, и разместить в проекте с модульными тестами.
Общий подход к использованию такой базы данных заключается в том, чтобы перед выполнением теста развернуть тестовую базу данных (например, в методе, отмеченном атрибутом SetUp в случае использования NUnit), заполнить базу тестовыми данными и проверить работоспособность репозитории или шлюзы на основе этих известных тестовых данных.
Причем тестовую базу данных можно развернуть как на жестком диске, так и в памяти, используя приложения, создающие и управляющие RAM-диском.
Допустим, проект, над которым я сейчас работаю, использует приложение SoftPerfect RAM-диск .
Использование RAM-диска в модульных тестах позволяет сократить задержки, возникающие во время операций ввода-вывода, которые могут возникнуть при развертывании тестовой базы данных на жестком диске.
Конечно, такой подход не идеален, поскольку требует внедрения стороннего ПО в среду разработчика.
С другой стороны, если учесть, что среда разработки разворачивается, как правило, один раз (ну или совсем редко), то это требование не кажется таким уж обременительным.
И выгода от использования такого подхода весьма заманчива, ведь появляется возможность контролировать корректность работы одного из важнейших слоев системы.
Кстати, если в модульных тестах есть возможность использовать LINQ2SQL и SMO для MS SQL Server, то для тестирования уровня доступа к данным можно использовать следующий базовый класс: Код public abstract class DatabaseUnitTest<TContext> where TContext : DataContext
{
[TestFixtureSetUp]
public void FixtureSetUp()
{
CreateFolderForTempDatabase();
}
[SetUp]
public void BeforeTestExecuting()
{
RestoreDatabaseFromOriginal();
RecreateContext();
Теги: #C++ #.
NET #tdd #.
NET #tdd #C++
-
Надувной Модуль Мкс
19 Oct, 24 -
[Usr] Простые Декодеры
19 Oct, 24 -
Фабрика Виджетов Для Mpa
19 Oct, 24