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

Публикация № 1085875

Программирование - Инструментарий

Unit-тесты

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

Подготовка

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

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

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

opm install add

После выполнения команды в папке с библиотеками OneScript (C:\Program Files (x86)\OneScript\Lib\) появится папка 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-тесты было тут.

24

Скачать файлы

Наименование Файл Версия Размер
Шаблон обработки для написания Unit-теста
.epf 5,12Kb
15.08.19
0
.epf 5,12Kb Скачать

См. также

Специальные предложения

Комментарии
Избранное Подписка Сортировка: Древо
1. artbear 1143 22.07.19 15:45 Сейчас в теме
(0) Хорошая статья, живые примеры!

Спасибо!
2. artbear 1143 22.07.19 15:45 Сейчас в теме
мелкая опечатка бросилась в глаза

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

Для примера это неважно, а вот в разработке важно.
4. Сурикат 251 22.07.19 18:44 Сейчас в теме
(3) Спасибо, за замечание =)

В других случаях можно делать параметризуемые шаблоны и формировать наименование случайным образом во избежании проблем с совпадением данных =)
5. ImHunter 155 23.07.19 10:00 Сейчас в теме
Не видел, чтобы кто-то упоминал про расширения, создаваемые в помощь тестированию.
А ведь это круто - расшаривать приватные методы в специальном расширении и писать авто-тесты для того, что скрыто за кадром.
artbear; Сурикат; +2 Ответить
6. Сурикат 251 23.07.19 11:14 Сейчас в теме
(5)
Очень здоровский комментарий! Спасибо!

Еще ведь можно заменять вызов каких-то процедур, например обращение к файловой системе или выполнение сложного запроса с помощью расширения
8. artbear 1143 23.07.19 11:51 Сейчас в теме
(6)Да, расширения и для мокирования вполне удобно использовать, давно об этом думаю.
7. artbear 1143 23.07.19 11:50 Сейчас в теме
(5) Писать тесты для приватных методов - это зло.

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

Но идея с расширениями, конечно, полезная и уже есть примеры реализаций.
9. ImHunter 155 23.07.19 11:58 Сейчас в теме
(7) Да ладно - зло:))

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

В общем-то, я тоже сначала размышлял - заниматься ли таким хаком. Практика показала, что так спокойнее процесс проходит.
12. artbear 1143 23.07.19 12:16 Сейчас в теме
(9) А может быть, не делать сложные методы ? :)

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

как раз и получится и чистый ТДД по отдельным модулям/классам.
13. ImHunter 155 23.07.19 12:28 Сейчас в теме
(12) Не всегда ведь нужно публиковать составляющие какого-то метода. Ибо нефиг.
А так, пользуясь хаком, "приколачиваю" внутреннее поведение к заданным паттернам. Если кто-то что-то переделает - будет детальная картина, где сломались механизмы.
10. ImHunter 155 23.07.19 12:02 Сейчас в теме
И еще трюк:) Иногда пишу самотестируемые внешки.

Т.е., это целевая функциональная внешка. В ней еще дописан интерфейс для xUnit/ADD.
11. artbear 1143 23.07.19 12:12 Сейчас в теме
(10) я с этого начинал много лет назад. По ТДД создавал тесты и код в одной обработке.

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

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

приходится придумывать и реализовывать более точное АПИ.
14. for_sale 764 28.07.19 16:14 Сейчас в теме
Очень хорошая статья, большое спасибо!
Вопрос - можно ли где-то со всем этим функционалом ознакомиться более детально?
Ведь для данных примеров просто взяты некоторые функции, но их там, по идее, в разы больше. Где-то (кроме сурскода) можно почитать описание возможностей? Плагины, инициализация и т.п.
15. Сурикат 251 28.07.19 21:22 Сейчас в теме
(14)

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

Также можно поизучать тесты на модули - https://github.com/silverbulleters/add/tree/master/tests/xunit/Plugins. Тесты даже лучше, чем документация =)
for_sale; +1 Ответить
16. for_sale 764 29.07.19 16:20 Сейчас в теме
(15)
Спасибо за ссылки! По поводу документации - давно присматриваюсь и к АДД, и к ВА, первое время просто не мог поверить, что такие титанические труды не имеют никакой официальной документации!
17. artbear 1143 29.07.19 18:14 Сейчас в теме
(16) Документация есть, но, конечно, ее очень не хватает.
Кодить мы все любим, а с документацией не дружим.
18. artbear 1143 29.07.19 18:16 Сейчас в теме
По Vanessa-ADD есть уже немало статей.
И здесь на Инфостарте есть цикл статей,
и в документации на гитхабе Vanessa-ADD https://github.com/silverbulleters/add/blob/master/doc/README.mdе сть первые же ссылки - "почитать статьи, посмотреть видео"
Оставьте свое сообщение