Pull to refresh

Юнит-тесты, BDD и сила текучих утверждений (fluent assertions) в 1С

Website development *Programming *TDD *

Немного истории


Благодаря классному дядьке Кенту Беку (Kent Beck) родилась замечательная методология test-driven development. Не смотря на необычность подхода, переворачивающего привычный процесс написания кода с ног на голову (тест на функционал создается до реализации), сейчас уже можно сказать, что разработка через тестирование стала стандартом де-факто. Практически в любых вакансиях фигурирует требование к знанию и опыту использования методики TDD и соответствующих инструментов. Почему, казалось бы, ломающая привычную парадигму мышления методология прижилась и стандартизировалась? Потому что “Жизнь слишком коротка для ручного тестирования”, а писать авто-тесты на существующий код иногда просто не возможно, ведь код, написанный в обычной парадигме, как правило совершенно тесто-не-пригодный.

Стоит отметить, что за время своего существования методология успела обзавестись ответвлением (fork) в виде BDD. Дэн Норт (Dan North) в своей статье (Introducing BDD) указал на сложности внедрения TDD среди разработчиков и для решения обозначенных проблем предложил практику, которая называется behaviour-driven development. Основной фишкой BDD можно назвать микс из TDD и DDD, которая в начале выражалась в правильном именовании тестовых методов (названия тестовых методов должны быть предложениями). Апогеем BDD, на текущий момент, можно считать рождение языка Gherkin и инструментария, который его использует (Cucumber, RSpec и т.п.).

К чему я веду и при чем тут 1С?


В мире 1С TDD только только начинает набирать популярность. Я еще не видел вакансий разработчиков 1С с требованием знания TDD. Стоит признать, что существенным препятствием является отсутствие в ядре платформы 1С инструментов для написания тестов до кода.
Так что же у нас есть на текущий момент для разработки через тестирование в мире 1С?
  • xUnitFor1C — вполне себе зрелый проект, позволюящий разрабатывать в стиле TDD.
  • Vanessa-behavoir — спецификации на языке Gherkin и т.п., пока что не в релизном состоянии.

А теперь вопрос, который должен возникать у любого уважающего себя члена общества: “Как лично я могу помочь… (в моем случае — миру 1С разработки перейти на передовые методологии)?”.

Прежде чем ответить на этот вопрос, я хочу коснуться темы хорошо написанных утверждений в тестах. Утверждения обозначают ожидаемое поведение нашего кода. Одного взгляда на утверждения должно быть достаточно, чтобы понять, какое поведение тест пытается до нас донести. К сожалению, классические утверждения не позволяют этого достичь. Зачастую нам приходится долго вчитываться и расшифровывать замысел автора теста.
К счастью, в последнее время появилась тенденция к применению текучих интерфейсов (fluent interface), что очень положительно сказывается на наглядности и интуитивной понятности кода. Инструментарий для тестирования так же не остался в стороне.оявились текучие утверждения, называемые так же утверждениями в стиле BDD. Они позволяют формулировать утверждения в более естественной, удобной и выразительной манере.
Впервые я столкнулся с подобным подходом в NUnit в модели утверждений на основе ограничений (Constraint-Based Assert Model).
Много позже я познакомился со связкой mocha.js + chai.js, которая у меня вызвала полнейший восторг.

Так вот, мой ответ на вопрос “Как лично я могу помочь миру 1С разработки перейти на передовые методологии?” — текучие утверждения… для начала.

Разработка текучих утверждений для платформы 1С


Как заправский разработчик через тестирование, я начал разработку с теста. Первый тестовый метод содержал всего 1 строку:

Ожидаем.Что(5).Равно(5);

Реализация оказалась на удивление простой. Переменная Ожидаем содержит объект ВнешняяОбработка (далее объект-утверждения), у этого объекта есть экспортные методы:
  • Что(ПроверяемоеЗначение) — сохраняет в контексте объекта-утверждения проверяемое значение;
  • Равно(ОжидаемоеЗначение) — проверяет на равенство ранее сохраненное значение с переданным ожидаемым значением. В случае неравенства выбрасывается исключение с описанием ошибки утверждения.

Каждый метод возвращает тот же самый объект-утверждения.

Следующим шагом, сигнатура метода Что была расширена необязательным параметром Сообщение, которое делает выбрасываемые утверждениями исключения более информативными.

Далее я задумался над тем, что делать с утверждением НеРавно. Должно ли быть такое утверждение? В классических утверждениях так и есть, почти каждое утверждение имеет своего антипода (Равно/НеРавно, Заполнено/НеЗаполнено и т.д.). Но только не в текучих утверждениях! Так родился тест №2:

Ожидаем.Что(5).Не.Равно(7);

Выглядит красиво, но не реализуемо на языке 1С. Еще попытка:

Ожидаем.Что(5).Не().Равно(7);

По прежнему красиво и казалось бы реализуемо. Нужно всего лишь взвести флаг отрицания в контексте объекта-утверждения, и затем любое следующее по цепи утверждение проверять с учетом этого флага. По сути нужен был XOR, на языке 1С это выглядит вот так:

РезультатУтверждения = ФлагОтрицания <> ЛогическоеВыражениеУтверждения;

Но платформа отказалась компилировать объект с методом Не(). Дело в том, что Не — зарезервированное слово, ограничение на его использование распространяется в т.ч. и на имя метода. Мозговой штурм с коллегами не позволили красиво обойти эту проблему, поэтому финальный вариант с отрицанием выглядит так:

Ожидаем.Что(5).Не_().Равно(7);

Если кто-то предложит лучшее решение обозначенной проблемы, я буду очень признателен. Вариант замены русских букв на латиницу не предлагать!

В итоге родился следующий API


Что(ПроверяемоеЗначение, Сообщение = "") — сохраняет в контексте внешней обработки проверяемое значение и дополнительное сообщение для исключений выбрасываемых утверждениями.

Не_() — отрицает любое утверждение следующее по цепи.

ЭтоИстина() — утверждает, что проверяемое значение является Истиной.

ЭтоЛожь() — утверждает, что проверяемое значение является Ложью.

Равно(ОжидаемоеЗначение) — утверждает, что проверяемое значение равно ожидаемому.

Больше(МеньшееЗначение) — утверждает, что проверяемое значение больше, чем переданное в утверждение.

БольшеИлиРавно(МеньшееИлиРавноеЗначение) / Минимум(МинимальноеЗначение) — утверждает, что проверяемое значение больше или равно переданному в утверждение.

МеньшеИлиРавно(БольшееИлиРавноеЗначение) / Максимум(МаксимальноеЗначение) — утверждает, что проверяемое значение меньше или равно переданному в утверждение.

Меньше(БольшееЗначение) — утверждает, что проверяемое значение меньше, чем переданное в утверждение.

Заполнено() — утверждает, что проверяемое значение отличается от значения по умолчанию того же типа.

Существует() — утверждает, что проверяемое значение не Null и не Неопределено.

ЭтоНеопределено() — утверждает, что проверяемое значение это Неопределено.

ЭтоNull() — утверждает, что проверяемое значение это Null.

ИмеетТип(Тип) — утверждает, что проверяемое значение имеет переданный в утверждение тип или имя типа.

Между(НачальноеЗначение, КонечноеЗначение) — утверждает, что проверяемое значение находится между переданными в утверждение значениями.

Содержит(ИскомоеЗначение) — утверждает, что проверяемое значение содержит переданное в утверждение. Применяется для строк и коллекций.

ИмеетДлину(ОжидаемаяДлина) — утверджает, что проверяемое значение имеет длину переданную в утверждение. Применяется для строк и коллекций.

Примеры использования


Ожидаем.Что(1 > 0).ЭтоИстина();
Ожидаем.Что(13 = 2).Не_().ЭтоИстина();
Ожидаем.Что(5 = 7).ЭтоЛожь();
Ожидаем.Что(5).Равно(5);
Ожидаем.Что(4).Больше(2);
Ожидаем.Что(7).БольшеИлиРавно(7);
Ожидаем.Что(НекийМассив.Количество()).Минимум(9);
Ожидаем.Что(90).МеньшеИлиРавно(100);
Ожидаем.Что(СтрДлина(НекаяСтрока)).Максимум(90);
Ожидаем.Что(55).Меньше(56);
Ожидаем.Что(1).Заполнено();
Ожидаем.Что(Новый Массив).Существует();
Ожидаем.Что(Неопределено).ЭтоНеопределено();
Ожидаем.Что(ВыборкаИзБД.НекоеПоле).ЭтоNull();
Ожидаем.Что("").ИмеетТип("Строка");
Ожидаем.Что(7).Между(1, 10);
Ожидаем.Что("Некая строка").Содержит("стр");
Ожидаем.Что("Некая строка").ИмеетДлину(12);

Пример немного сложнее:

Ожидаем.Что("Некая строка")
		.Существует()
		.Не_().ИмеетТип("Число")
		.ИмеетДлину(12)
		.Не_().Содержит("!!!");

Послесловие


Разработка доступна на github. Как, наверное, заметил читатель с пытливым умом, ссылка ведет на нечто большее, чем просто на библиотеку утверждений. Но это уже материал для следующей статьи.
Tags:
Hubs:
Total votes 17: ↑15 and ↓2 +13
Views 17K
Comments 48
Comments Comments 48

Posts