О Unit-тестах замолвите слово. Часть 2

Пара практических примеров написания Unit-тестах с использованием фреймворка Vanessa-ADD.

Подготовка

Тесты будем писать с использованием фреймворка Vanessa-ADD.

Самый простой путь к его установке — менеджер пакетов OneScript. Скачать его можно тут: http://oscript.io/.

После того как будет установлен OneScript нам будет достаточно открыть командную строку и выполнить:

opm install add

После выполнения команды в папке с библиотеками OneScript (C:Program Files (x86)OneScriptLib) появится папка add. В ней будут лежать компоненты фреймворка Vanessa-Add.

Нас прежде всего интересуют файлы, относящиеся к части xUnit (фреймворк для Unit-тестирования):

  • Внешняя обработка xddTestRunner.epf — обработка для запуска Unit-тестов
  • Набор плагинов, который располагается в папке add/plugins

Подробнее о запуске тестов и использовании плагинов можно почитать в документации тут.

Организация хранения тестов и состав набора тестов

Для того чтобы практические примеры лучше читались, разберем как тесты хранятся и из чего состоят наборы тестов.

Тесты располагаются в модулях внешних обработок, каждая обработка может содержать несколько тестов, разделенных на группы.

Т.к. Unit-тесты пишутся на процедуры и функции, то удобно разбивать тесты на блоки со следующей иерархией:
— метаданные

— модуль менеджера/модуль объекта либо функциональная область

Например, такая структура файлов и папок:
— Документ.Заказ клиента

— Тест_ЗаказКлиентаОбъект.epf

— Тест_ЗаказКлиентаМенеджер.epf

Каждая внешняя обработка состоит из следующих частей:

Инструментарий

В основном в тестах мы будем использовать следующие плагины:

  • ТекучиеУтверждения — плагин предоставляющий методы для проверки утверждений
// Пример проверки значения
// Ожидаем - плагин ТекучиеУтверждениея
// Что() - передаем плагину значение для проверки
// Равно() - вызываем процедуру сравнения переданного значения с эталоном
Ожидаем.Что(ПроверямоеЗначение).Равно(1);

// Пример проверки метода
// Что() - передаем плагину расположения проверяемого метода
// Метод() - передаем плагину имя метода и его параметры
// ВыбрасываетИсключение() - провермя, что метод выбросил определенное исключение
Ожидаем.Что(ОбщийМодуль).Метод(ИмяМетода).ВыбрасываетИсключение("Наше исключение");
  • Данные — плагин для генерации данных, необходимых для теста

// Данные - плагин
// НачатьСоздание() - Объявляем какой объект нужно создать Справочник, Документ, Набор записей регистра накопления  или сведений
// Реквизит() - объявляем, что у нашего объекта будет заполнен реквизит определенным значением
// ШапкаТабличнойЧасти() - объявляем, что у создаваемого объекта будет заполнены табличная часть и некоторые из её колонок
// СтрокаТЧ() - описываем какими именно значениями будет заполнена строка табличной части
// Создать() - завершаем создание объекта, по умолчанию объект записывается в базу и возвращается ссылка на него
Данные.НачатьСоздание("Документ.ДокументСДвижениями")
.Реквизит("РеквизитПростойСправочник")
.ШапкаТабличнойЧасти("ТЧ","Реквизит1", "РесурсЧисло")
.СтрокаТЧ("Элемент1", 10)
.СтрокаТЧ("Элемент2", 15).Создать();

Более подробно о плагинах можно прочитать на страницах документации.

К сожалению не для всех плагинов есть документация, но в них можно легко разобраться, открыв сами плагины в каталоге add/plugins =)

Пример теста: Распределение значений по базе

У нас реализована собственная подсистема Бюджетирования. Нам была необходима функция, которая будет распределять затраты, которые были сформированы в одних подразделениях на другие подразделения, в зависимости от их показателей.

Рассмотрим пример проверки функции распределения:

 // Создадим тестовые подразделения первичных затрат
Подразделение1 = Данные.СоздатьЭлементСправочника("СтруктураПредприятия");
Подразделение2 = Данные.СоздатьЭлементСправочника("СтруктураПредприятия");

// Тут мы подготавливаем описание колонок таблиц, которые будут передваться нашей функции
Колонка_АналитикаДоходовРасходов = Данные.ОписаниеКолонкиТЧ("АналитикаДоходовРасходов", Новый ОписаниеТипов("СправочникСсылка.СтруктураПредприятия"));
Колонка_Ресурс1 = Данные.ОписаниеКолонкиТЧ("Ресурс1", Новый ОписаниеТипов("Число"));
Колонка_Ресурс2 = Данные.ОписаниеКолонкиТЧ("Ресурс2", Новый ОписаниеТипов("Число"));
Колонка_АналитикаРасходов = Данные.ОписаниеКолонкиТЧ("АналитикаРасходов", Новый ОписаниеТипов("СправочникСсылка.СтруктураПредприятия"));
Колонка_Коэффициеннт = Данные.ОписаниеКолонкиТЧ("Коэффициент", Новый ОписаниеТипов("Число"));

// Создадим тестовые подразделения, на которые необходимо распределить затраты
ПодразделениеРаспределения3 = Данные.СоздатьЭлементСправочника("СтруктураПредприятия");
ПодразделениеРаспределения4 = Данные.СоздатьЭлементСправочника("СтруктураПредприятия");

// Формируем таблицу первоначальных затрат
ИсходнаяТаблица = Данные.НачатьСоздание("ТаблицаЗначений")
.ШапкаТабличнойЧасти(, Колонка_АналитикаДоходовРасходов, Колонка_Ресурс1, Колонка_Ресурс2)
.СтрокаТЧ(Подразделение1, 10, 10)
.СтрокаТЧ(Подразделение2, 20, 10).Создать();

// Формируем таблицу коэффициентов
ТаблицаКоэффицентов = Данные.НачатьСоздание("ТаблицаЗначений")
.ШапкаТабличнойЧасти(, Колонка_АналитикаРасходов, Колонка_Коэффициеннт)
.СтрокаТЧ(ПодразделениеРаспределения3, 0.4)
.СтрокаТЧ(ПодразделениеРаспределения4, 0.6).Создать();

// Формируем таблицу эталон, для проверки результата работы функции
Эталон = Данные.НачатьСоздание("ТаблицаЗначений")
.ШапкаТабличнойЧасти(, Колонка_АналитикаДоходовРасходов, Колонка_Ресурс1, Колонка_Ресурс2, "АналитикаДоходовРасходовИсточник")
.СтрокаТЧ(ПодразделениеРаспределения3, 4, 4, Подразделение1)
.СтрокаТЧ(ПодразделениеРаспределения4, 6, 6, Подразделение1)
.СтрокаТЧ(ПодразделениеРаспределения3, 8, 4, Подразделение2)
.СтрокаТЧ(ПодразделениеРаспределения4, 12, 6, Подразделение2).Создать();


// Выполняем функцию
Справочники.ИсточникиДанныхБюджета.РаспределитьПоКоэффициентам(ИсходнаяТаблица, ТаблицаКоэффицентов);

// Сравниваем эталон и результат работы нашей функции
СравнениеТаблиц.ПроверитьРавенствоТаблиц(Эталон, ИсходнаяТаблица);

Стоит отметить, что коэффициенты и значения затрат подобраны не очень хорошо, т.к. при таких значениях не возникает проблемы копеек.

Пример теста: разбор информации об обновлениях мобильного приложения

На складе компании используется приложение на мобильной платформе 1С. Из-за политики безопасности мы не можем распространять обновления через GooglePlay. Поэтому мы написали приложение, которое проверяет наличие обновлений на сервере (сравнение версии установленного приложения с настройками, хранящимися на сервере) и устанавливает обновление, если оно есть. 

Мы рассмотрим тест на разбор файла настроек, которые загружаются с сервера:


Процедура Тест_ЧтениеИнформациисСервера_НесколькоНастроек() Экспорт


Настройка = ШаблонНастройки("version_несколько_настроек");

// Процедура читает файл переданной настройки и заполняет справочник "Конфигурации"
ОбновлениеКонфигурацийВызовСервера.ЗагрузитьОписаниеВерсийССервера(Настройка, Ложь);

// Проверяем, что настройка прочитана корректно
ЗагруженнаяНастройка = Справочники.Конфигурации.НайтиПоНаименованию("ru.yarvet.mw");
Ожидаем.Что(ЗагруженнаяНастройка.ИмяФайлаОбновления).Равно("тест")
.Что(ЗагруженнаяНастройка.ВерсияСервера).Равно("2.1.0");

ЗагруженнаяНастройка = Справочники.Конфигурации.НайтиПоНаименованию("ru.yarvet.launcher");
Ожидаем.Что(ЗагруженнаяНастройка.ИмяФайлаОбновления).Равно("тест2")
.Что(ЗагруженнаяНастройка.ВерсияСервера).Равно("1.1.0");

КонецПроцедуры

// Функция возвращает путь до файла с тетсовой настройки
Функция ШаблонНастройки(Настройка)

// ИспользуемоеИмяФайла - путь до обработки теста, стандартный реквзит внешней обработки
ОписаниеТеста = Новый Файл(ИспользуемоеИмяФайла);
Возврат ОписаниеТеста.Путь + ПолучитьРазделительПути() + Настройка+".json";

КонецФункции

Собственно сам файл настройки выглядит так:

[
{
"application": "ru.yarvet.mw",
"version":"2.1.0",
"updateFile":"тест"
},
{
"application": "ru.yarvet.launcher",
"version":"1.1.0",
"updateFile":"тест2"
}
]

Почему мы писали тест на казалось бы такой простой код —  чтобы полностью проверить его в пользовательском режиме нужно было настроить: сервер, установить приложение на мобильное устройство, подключить его к нужной сети, ввести настройки сервера и загрузить настройки. В случае нахождения ошибки нужно было скомпилировать приложение, обновить его на устройстве и проверить еще раз. Поэтому намного легче было написать тест =)

P.S.

Рассуждение о том, в каких случаях лучше писать unit-тесты было тут.

18 Comments

  1. artbear

    (0) Хорошая статья, живые примеры!

    Спасибо!

    Reply
  2. artbear

    мелкая опечатка бросилась в глаза

    >путь до файла с тетсовой настройки

    Reply
  3. artbear

    (0) В примере теста Тест_ЧтениеИнформациисСервера_НесколькоНастроек() не видно, что проверяемые данные (Спр.Конфигурации) предварительно очищены. Без этой очистки боевой код может переиспользовать существующие элементы 🙁

    Для примера это неважно, а вот в разработке важно.

    Reply
  4. Сурикат

    (3) Спасибо, за замечание =)

    В других случаях можно делать параметризуемые шаблоны и формировать наименование случайным образом во избежании проблем с совпадением данных =)

    Reply
  5. ImHunter

    Не видел, чтобы кто-то упоминал про расширения, создаваемые в помощь тестированию.

    А ведь это круто — расшаривать приватные методы в специальном расширении и писать авто-тесты для того, что скрыто за кадром.

    Reply
  6. Сурикат

    (5)

    Очень здоровский комментарий! Спасибо!

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

    Reply
  7. artbear

    (5) Писать тесты для приватных методов — это зло.

    если вдруг появилась такая потребность, подумайте, возможно, АПИ системы немного неверно 🙂 и стоит его доработать.

    Но идея с расширениями, конечно, полезная и уже есть примеры реализаций.

    Reply
  8. artbear

    (6)Да, расширения и для мокирования вполне удобно использовать, давно об этом думаю.

    Reply
  9. ImHunter

    (7) Да ладно — зло:))

    Как протестировать работу какого-то достаточно сложного метода? Правильно! Написать интеграционный тест!

    Но блин, в 50% случаев, нужно потратить на это кучу времени. А если приватные составляющие не протестированы — можно ведь и вообще надолго залипнуть.

    В общем-то, я тоже сначала размышлял — заниматься ли таким хаком. Практика показала, что так спокойнее процесс проходит.

    Reply
  10. ImHunter

    И еще трюк:) Иногда пишу самотестируемые внешки.

    Т.е., это целевая функциональная внешка. В ней еще дописан интерфейс для xUnit/ADD.

    Reply
  11. artbear

    (10) я с этого начинал много лет назад. По ТДД создавал тесты и код в одной обработке.

    но потом ушел от этого, чтобы максимально разнести публичный интерфейс функциональности и тесты, чтобы случайно не воспользоваться приватными функциями, реквизитами.

    так легче и разрабатывать, и сопровождать.

    приходится придумывать и реализовывать более точное АПИ.

    Reply
  12. artbear

    (9) А может быть, не делать сложные методы ? 🙂

    а создавать несколько публичных интерфейсов/классов/модулей/обработок и тестировать их по отдельности.

    как раз и получится и чистый ТДД по отдельным модулям/классам.

    Reply
  13. ImHunter

    (12) Не всегда ведь нужно публиковать составляющие какого-то метода. Ибо нефиг.

    А так, пользуясь хаком, «приколачиваю» внутреннее поведение к заданным паттернам. Если кто-то что-то переделает — будет детальная картина, где сломались механизмы.

    Reply
  14. for_sale

    Очень хорошая статья, большое спасибо!

    Вопрос — можно ли где-то со всем этим функционалом ознакомиться более детально?

    Ведь для данных примеров просто взяты некоторые функции, но их там, по идее, в разы больше. Где-то (кроме сурскода) можно почитать описание возможностей? Плагины, инициализация и т.п.

    Reply
  15. Сурикат

    (14)

    https://github.com/silverbulleters/add/tree/master/doc/xdd — для части плагинов есть описание. Для части только читать исходники =(

    Также можно поизучать тесты на модули — https://github.com/silverbulleters/add/tree/master/tests/xunit/Plugins. Тесты даже лучше, чем документация =)

    Reply
  16. for_sale

    (15)

    Спасибо за ссылки! По поводу документации — давно присматриваюсь и к АДД, и к ВА, первое время просто не мог поверить, что такие титанические труды не имеют никакой официальной документации!

    Reply
  17. artbear

    (16) Документация есть, но, конечно, ее очень не хватает.

    Кодить мы все любим, а с документацией не дружим.

    Reply
  18. artbear

    По Vanessa-ADD есть уже немало статей.

    И здесь на Инфостарте есть цикл статей,

    и в документации на гитхабе Vanessa-ADD https://github.com/silverbulleters/add/blob/master/doc/README.mdе сть первые же ссылки — «почитать статьи, посмотреть видео»

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *