diff --git a/documentation/docs/recommendations/avaliable-code.md b/documentation/docs/recommendations/avaliable-code.md new file mode 100644 index 000000000..7f903e99b --- /dev/null +++ b/documentation/docs/recommendations/avaliable-code.md @@ -0,0 +1,14 @@ +# Ограничения тестируемого кода + +## Только синхронных код + +YAxUnit выполняет тесты последовательно, вызов каждого теста это синхронный вызов тестового метода, в связи с этим не поддерживается: + +* Тестирование методов построенных на обработчиках +* Тестирование асинхронных методы + +## Тестирование форм + +YAxUnit плохо подходит для тестирования форм. Формы, это в первую очередь про взаимодействие с пользователем, с другими объектами системы. +Для их проверки лучше подходят такие инструменты как [vanessa-automation]([vanessa-automation](https://github.com/Pr-Mex/vanessa-automation)), [add](https://github.com/vanessa-opensource/add) или [tester](https://github.com/grumagargler/tester). Они позволяют проще и комплексно проверить работу форм. +Если же в форме расположена сложная логика слабо связанная с отображением, то ее можно вынести в общий модуль, который будет вызываться из формы и тестировать уже методы этого общего модуля. \ No newline at end of file diff --git a/documentation/docs/recommendations/common-recommendations.md b/documentation/docs/recommendations/common-recommendations.md new file mode 100644 index 000000000..c03386b76 --- /dev/null +++ b/documentation/docs/recommendations/common-recommendations.md @@ -0,0 +1,469 @@ +--- +tags: [Начало, Рекомендации] +sidebar_position: 1 +--- + +# Общие рекомендации по модульному тестированию + +:::tip Перевод +Перевод (gpt) и адаптация статьи [Unit testing best practices with .NET Core and .NET Standard](https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices) by [John Reese](https://reese.dev/) +::: + +:::warning +Рекомендации помогут при написании модульных тестов, чтобы обеспечить устойчивость и удобство их понимания. +Ситуации бывают разные и иногда приходится отступать от рекомендаций, но это не повод ими пренебрегать. +::: + +## Избегайте зависимостей от инфраструктуры + +Старайтесь не вводить зависимости от инфраструктуры при написании юнит-тестов. Эти зависимости делают тесты медленными и ненадежными, и их следует оставлять для интеграционных тестов. + +## Название ваших тестов + +Имя вашего теста должно состоять из трех частей: + +* Название тестируемого метода. +* Сценарий, в котором он тестируется. +* Ожидаемое поведение при вызове сценария. + +:::note Почему это важно? +Стандарты именования важны, поскольку они явно выражают цель теста. Тесты — это не просто проверка работоспособности вашего кода, но и документация. Посмотрев на набор юнит-тестов, вы должны иметь возможность понять поведение вашего кода, не заглядывая в сам код. Кроме того, когда тесты не проходят, вы точно видите, какие сценарии не соответствуют вашим ожиданиям. +::: + +```bsl title="Плохо" +Процедура Тест_ОднаСтрока() Экспорт + + Результат = ЮТСтроки.ДобавитьСтроку("Иванов", ""); + + ЮТест.ОжидаетЧто(Результат) + .Равно("Иванов"); + +КонецПроцедуры +``` + +```bsl title="Лучше" +Процедура ДобавитьСтроку_БезДополнения_ВернетТужеСтроку() Экспорт + + Результат = ЮТСтроки.ДобавитьСтроку("Иванов", ""); + + ЮТест.ОжидаетЧто(Результат) + .Равно("Иванов"); + +КонецПроцедуры +``` + +## Организация ваших тестов + +**Подготовка**, **Действие**, **Проверка** — это распространённый паттерн в юнит-тестировании. Как следует из названия, он состоит из трех основных действий: + +* **Подготовьте** объекты, создайте их и настройте по мере необходимости. +* Выполните **действие** над объектом. +* **Проверьте**, что всё соответствует ожиданиям. + +:::note Почему это важно? + +* Такой подход четко разделяет тестируемую логику и этапы подготовки и проверки. +* Снижает вероятность смешивания утверждений с кодом действия. +::: + +Читаемость — один из важнейших аспектов при написании тестов. Разделение этих действий в тесте ясно подчеркивает зависимости, необходимые для вызова вашего кода, как именно ваш код вызывается и что вы пытаетесь проверить. Хотя возможно объединить некоторые шаги и уменьшить размер теста, основной целью является максимальная читаемость теста. + +```bsl title="Плохо" +Процедура ДобавитьСтроку_БезДополнения_ВернетТужеСтроку() Экспорт + + // Подготовка + ИсходнаяСтрока = "Иванов"; + ПустаяСтрока = ""; + + // Проверка + ЮТест.ОжидаетЧто(ЮТСтроки.ДобавитьСтроку(ИсходнаяСтрока, ПустаяСтрока)) + .Равно(ИсходнаяСтрока); + +КонецПроцедуры +``` + +```bsl title="Лучше" +Процедура ДобавитьСтроку_БезДополнения_ВернетТужеСтроку() Экспорт + + // Подготовка + ИсходнаяСтрока = "Иванов"; + ПустаяСтрока = ""; + + // Действие + Результат = ЮТСтроки.ДобавитьСтроку(ИсходнаяСтрока, ПустаяСтрока); + + // Проверка + ЮТест.ОжидаетЧто(Результат) + .Равно(ИсходнаяСтрока); + +КонецПроцедуры +``` + +## Пишите минимально проходящие тесты + +Входные данные для юнит-теста должны быть как можно проще, чтобы проверить поведение, которое вы сейчас тестируете. + +:::note Почему это важно? + +* Тесты становятся более устойчивыми к будущим изменениям в кодовой базе. +* Они ближе к тестированию поведения, а не реализации. +::: + +Тесты, которые включают больше информации, чем требуется для прохождения, могут содержать больше ошибок и затруднять понимание намерения. При написании тестов важно сосредоточиться на поведении. Установка дополнительных свойств в моделях или использование ненулевых значений, когда это не требуется, отвлекает от того, что вы пытаетесь доказать. + + + +```bsl title="Плохо" +Процедура ДобавитьСтроку_БезДополнения_ВернетТужеСтроку() Экспорт + + // Подготовка + ИсходнаяСтрока = "Иванов"; + + // Действие + Результат = ЮТСтроки.ДобавитьСтроку(ИсходнаяСтрока, "", ";"); + + // Проверка + ЮТест.ОжидаетЧто(Результат) + .Равно(ИсходнаяСтрока); + +КонецПроцедуры +``` + +```bsl title="Лучше" +Процедура ДобавитьСтроку_БезДополнения_ВернетТужеСтроку() Экспорт + + // Подготовка + ИсходнаяСтрока = "Иванов"; + + // Действие + Результат = ЮТСтроки.ДобавитьСтроку(ИсходнаяСтрока, ""); + + // Проверка + ЮТест.ОжидаетЧто(Результат) + .Равно(ИсходнаяСтрока); + +КонецПроцедуры +``` + +## Избегайте "магических" строк + +Именование переменных в юнит-тестах так же важно, если не более важно, чем в рабочем коде. Юнит-тесты не должны содержать магических строк. + +:::note Почему это важно? + +* Устраняет необходимость читателю теста проверять основной код, чтобы понять, что делает значение особенным. +* Явно показывает, что вы пытаетесь *проверить*, а не *выполнить*. +::: + +"Магические" строки могут вызывать путаницу у читателя ваших тестов. Если строка выглядит необычно, может возникнуть вопрос, почему для параметра или возвращаемого значения выбрано определенное значение. Такие строки могут отвлекать внимание от теста и заставлять заглядывать в детали реализации. + +:::tip Совет +При написании тестов стремитесь выразить как можно больше намерений. В случае с магическими строками хорошим подходом будет присваивать эти значения переменным, либо использовать случайные значения. +::: + +```bsl title="Плохо" +Процедура ДобавитьСтроку_БезДополнения_ВернетТужеСтроку() Экспорт + + Результат = ЮТСтроки.ДобавитьСтроку("Иванов", ""); + + ЮТест.ОжидаетЧто(Результат) + .Равно("Иванов"); + +КонецПроцедуры +``` + +```bsl title="Лучше" +Процедура ДобавитьСтроку_БезДополнения_ВернетТужеСтроку() Экспорт + + ИсходнаяСтрока = ЮТест.Данные().СлучайнаяСтрока(); + ПустаяСтрока = ""; + + Результат = ЮТСтроки.ДобавитьСтроку(ИсходнаяСтрока, ПустаяСтрока); + + ЮТест.ОжидаетЧто(Результат) + .Равно(ИсходнаяСтрока); + +КонецПроцедуры +``` + +## Избегайте логики в тестах + +При написании юнит-тестов старайтесь избегать ручной конкатенации строк, логических условий (таких как `Если`, `Пока`, `Для`, `#Если`) и других условий. + +:::note Почему это важно? + +* Меньше шансов внедрить ошибку в тесты. +* Фокус на конечном результате, а не на деталях реализации. +::: + +Введение логики в тесты значительно увеличивает риск ошибок. Меньше всего вам нужна ошибка в наборе тестов. Вы должны быть уверены, что ваши тесты работают, иначе не сможете им доверять. Тесты, которым вы не доверяете, не имеют ценности. Когда тест не проходит, вы хотите быть уверены, что что-то не так с вашим кодом, и это нельзя игнорировать. + +:::tip Совет +Если логика в тесте кажется неизбежной, подумайте о разделении теста на два или более отдельных теста. +::: + +```bsl title="Плохо" +Процедура ДобавитьСтроку() Экспорт + + ИсходнаяСтрока = ЮТест.Данные().СлучайнаяСтрока(); + ДополнительнаяСтрока = ЮТест.Данные().СлучайнаяСтрока(); + РазделительПоУмолчанию = ";"; + + Варианты = ЮТест.Варианты("ИсходнаяСтрока, ДополнительнаяСтрока, Разделитель, Результат") + .Добавить(ИсходнаяСтрока, "", Неопределено, ИсходнаяСтрока) + .Добавить(ИсходнаяСтрока, ДополнительнаяСтрока, Неопределено, ИсходнаяСтрока + РазделительПоУмолчанию + ДополнительнаяСтрока) + .Добавить(ИсходнаяСтрока, ДополнительнаяСтрока, ";", ИсходнаяСтрока + ";" + ДополнительнаяСтрока) + .Добавить("", ДополнительнаяСтрока, Неопределено, ДополнительнаяСтрока) + ; + + Для Каждого Вариант Из Варианты.СписокВариантов() Цикл + + Если Вариант.Разделитель = Неопределено Тогда + Результат = ЮТСтроки.ДобавитьСтроку(Вариант.ИсходнаяСтрока, Вариант.ДополнительнаяСтрока); + Иначе + Результат = ЮТСтроки.ДобавитьСтроку(Вариант.ИсходнаяСтрока, Вариант.ДополнительнаяСтрока, Вариант.Разделитель); + КонецЕсли; + + ЮТест.ОжидаетЧто(Результат) + .Равно(Вариант.Результат); + + КонецЦикла; + +КонецПроцедуры +``` + +```bsl title="Лучше" +Процедура ДобавитьСтроку_БезДополнения_ВернетТужеСтроку() Экспорт + + ИсходнаяСтрока = ЮТест.Данные().СлучайнаяСтрока(); + + Результат = ЮТСтроки.ДобавитьСтроку(ИсходнаяСтрока, ""); + + ЮТест.ОжидаетЧто(Результат) + .Равно(ИсходнаяСтрока); + +КонецПроцедуры + +Процедура ДобавитьСтроку_БезИсходной_ВернетТужеСтроку() Экспорт + + ДополнительнаяСтрока = ЮТест.Данные().СлучайнаяСтрока(); + + Результат = ЮТСтроки.ДобавитьСтроку("", ДополнительнаяСтрока); + + ЮТест.ОжидаетЧто(Результат) + .Равно(ИсходнаяСтрока); + +КонецПроцедуры + +Процедура ДобавитьСтроку_СДополнением_ВернетОбъединениеСтрок() Экспорт + + ИсходнаяСтрока = ЮТест.Данные().СлучайнаяСтрока(); + ДополнительнаяСтрока = ЮТест.Данные().СлучайнаяСтрока(); + + Результат = ЮТСтроки.ДобавитьСтроку(ИсходнаяСтрока, ДополнительнаяСтрока); + + ЮТест.ОжидаетЧто(Результат) + .НачинаетсяС(ИсходнаяСтрока) + .ЗаканчиваетсяНа(ДополнительнаяСтрока) + ; + +КонецПроцедуры + +Процедура ДобавитьСтроку_СУказаннымРазделителем_ВернетОбъединениеСтрокИУказанногоРазделителя() Экспорт + + ИсходнаяСтрока = ЮТест.Данные().СлучайнаяСтрока(); + ДополнительнаяСтрока = ЮТест.Данные().СлучайнаяСтрока(); + Разделитель = ЮТест.Данные().СлучайнаяСтрока(); + ОжидаемыйРезультат = СтрШаблон("%1%2%3", ИсходнаяСтрока, ДополнительнаяСтрока, Разделитель) + + Результат = ЮТСтроки.ДобавитьСтроку(ИсходнаяСтрока, ДополнительнаяСтрока, Разделитель); + + ЮТест.ОжидаетЧто(Результат) + .НачинаетсяС(ИсходнаяСтрока) + .Содержит(Разделитель) + .ЗаканчиваетсяНа(ДополнительнаяСтрока) + .Равно(ОжидаемыйРезультат) + ; + +КонецПроцедуры +``` + +## Предпочитайте вспомогательные методы для настройки и очистки + +Если вам требуется похожий объект или состояние для ваших тестов, используйте вспомогательные методы, а не обработчики событий `ПередХХХХХХ` и `ПослеХХХХХХ`. + +:::note Почему это важно? + +* Меньше путаницы при чтении тестов, так как весь код виден в каждом тесте. +* Снижается вероятность избыточной или недостаточной настройки для конкретного теста. +* Уменьшается вероятность совместного использования состояния между тестами, что создает нежелательные зависимости. +::: + +В метод `ПередКаждымТестом` вызывается перед каждым тестом в вашем наборе тестов. Хотя некоторые могут считать это полезным инструментом, на практике это часто приводит к раздуванию тестов и снижению их читаемости. Каждый тест обычно имеет разные требования для успешного выполнения. К сожалению, `ПередКаждымТестом` заставляет использовать одинаковые требования для всех тестов. + +Тоже самое касается методов `ПередТестовымНабором` и `ПередВсемиТестами`. + +```bsl title="Плохо" +Процедура ПередКаждымТестом() Экспорт + + // Создание номенклатуры + + // Установка цены + +КонецПроцедуры + +Процедура КонтрольОстатков_НаличиеОстатков_Успешно() Экспорт + + // Алгоритм проверки + +КонецПроцедуры + +Процедура КонтрольОстатков_ОтсутствиеОстатков_Ошибка() Экспорт + + // Алгоритм проверки + +КонецПроцедуры + +Процедура ПроверкаЗаполнения_НетЦены_Ошибка() Экспорт + + // Алгоритм проверки + +КонецПроцедуры +``` + +```bsl title="Лучше" +Процедура КонтрольОстатков_НаличиеОстатков_Успешно() Экспорт + + Номенклатура = НоваяНоменклатура(); + УстановитьЦенуНоменклатуры(Номенклатура); + + // Алгоритм проверки + +КонецПроцедуры + +Процедура КонтрольОстатков_ОтсутствиеОстатков_Ошибка() Экспорт + + Номенклатура = НоваяНоменклатура(); + УстановитьЦенуНоменклатуры(Номенклатура); + + // Алгоритм проверки + +КонецПроцедуры + +Процедура ПроверкаЗаполнения_НетЦены_Ошибка() Экспорт + + Номенклатура = НоваяНоменклатура(); + // Алгоритм проверки + +КонецПроцедуры + +Функция НоваяНоменклатура() Экспорт + // Алгоритм создания +КонецФункции + +Процедура УстановитьЦенуНоменклатуры(Номенклатура, Цена = 10) Экспорт + // Алгоритм установки +КонецПроцедуры +``` + +## Избегайте нескольких действий + +При написании тестов старайтесь включать только одно действие на тест. Распространенные подходы для этого: + +* Создайте отдельный тест для каждого действия. +* Используйте параметризованные тесты. + +:::note Почему это важно? + +* При сбое теста ясно, какой сценарий завершается ошибкой +* Гарантирует, что тест сосредоточен только на одном случае. +* Позволяет получить полное представление о причинах неудач тестов. +::: + +Несколько действий требуют индивидуальной проверки, и не гарантируется, что все проверки будут выполнены. В большинстве платформ модульного тестирования сбой одного из проверочных утверждений в модульном тесте приводит к тому, что все последующие тесты автоматически считаются неудачными. Это может вызвать путаницу, так как работающая функциональность будет считаться сломанной. + +```bsl title="Плохо" +Процедура ДобавитьСтроку() Экспорт + + ИсходнаяСтрока = ЮТест.Данные().СлучайнаяСтрока(); + ДополнительнаяСтрока = ЮТест.Данные().СлучайнаяСтрока(); + РазделительПоУмолчанию = ";"; + + Варианты = ЮТест.Варианты("ИсходнаяСтрока, ДополнительнаяСтрока, Разделитель, Результат") + .Добавить(ИсходнаяСтрока, "", Неопределено, ИсходнаяСтрока) + .Добавить(ИсходнаяСтрока, ДополнительнаяСтрока, Неопределено, ИсходнаяСтрока + РазделительПоУмолчанию + ДополнительнаяСтрока) + .Добавить(ИсходнаяСтрока, ДополнительнаяСтрока, ";", ИсходнаяСтрока + ";" + ДополнительнаяСтрока) + .Добавить("", ДополнительнаяСтрока, Неопределено, ДополнительнаяСтрока) + ; + + Для Каждого Вариант Из Варианты.СписокВариантов() Цикл + + Если Вариант.Разделитель = Неопределено Тогда + Результат = ЮТСтроки.ДобавитьСтроку(Вариант.ИсходнаяСтрока, Вариант.ДополнительнаяСтрока); + Иначе + Результат = ЮТСтроки.ДобавитьСтроку(Вариант.ИсходнаяСтрока, Вариант.ДополнительнаяСтрока, Вариант.Разделитель); + КонецЕсли; + + ЮТест.ОжидаетЧто(Результат) + .Равно(Вариант.Результат); + + КонецЦикла; + +КонецПроцедуры +``` + +```bsl title="Лучше" +Процедура ДобавитьСтроку_БезДополнения_ВернетТужеСтроку() Экспорт + + ИсходнаяСтрока = ЮТест.Данные().СлучайнаяСтрока(); + + Результат = ЮТСтроки.ДобавитьСтроку(ИсходнаяСтрока, ""); + + ЮТест.ОжидаетЧто(Результат) + .Равно(ИсходнаяСтрока); + +КонецПроцедуры + +Процедура ДобавитьСтроку_БезИсходной_ВернетТужеСтроку() Экспорт + + ДополнительнаяСтрока = ЮТест.Данные().СлучайнаяСтрока(); + + Результат = ЮТСтроки.ДобавитьСтроку("", ДополнительнаяСтрока); + + ЮТест.ОжидаетЧто(Результат) + .Равно(ИсходнаяСтрока); + +КонецПроцедуры + +Процедура ДобавитьСтроку_СДополнением_ВернетОбъединениеСтрок() Экспорт + + ИсходнаяСтрока = ЮТест.Данные().СлучайнаяСтрока(); + ДополнительнаяСтрока = ЮТест.Данные().СлучайнаяСтрока(); + + Результат = ЮТСтроки.ДобавитьСтроку(ИсходнаяСтрока, ДополнительнаяСтрока); + + ЮТест.ОжидаетЧто(Результат) + .НачинаетсяС(ИсходнаяСтрока) + .ЗаканчиваетсяНа(ДополнительнаяСтрока) + ; + +КонецПроцедуры + +Процедура ДобавитьСтроку_СУказаннымРазделителем_ВернетОбъединениеСтрокИУказанногоРазделителя() Экспорт + + ИсходнаяСтрока = ЮТест.Данные().СлучайнаяСтрока(); + ДополнительнаяСтрока = ЮТест.Данные().СлучайнаяСтрока(); + Разделитель = ЮТест.Данные().СлучайнаяСтрока(); + ОжидаемыйРезультат = СтрШаблон("%1%2%3", ИсходнаяСтрока, ДополнительнаяСтрока, Разделитель) + + Результат = ЮТСтроки.ДобавитьСтроку(ИсходнаяСтрока, ДополнительнаяСтрока, Разделитель); + + ЮТест.ОжидаетЧто(Результат) + .НачинаетсяС(ИсходнаяСтрока) + .Содержит(Разделитель) + .ЗаканчиваетсяНа(ДополнительнаяСтрока) + .Равно(ОжидаемыйРезультат) + ; + +КонецПроцедуры +``` diff --git a/documentation/docs/recommendations/images/amongus.png b/documentation/docs/recommendations/images/amongus.png new file mode 100644 index 000000000..81fe33ef5 Binary files /dev/null and b/documentation/docs/recommendations/images/amongus.png differ diff --git a/documentation/docs/recommendations/index.md b/documentation/docs/recommendations/index.md new file mode 100644 index 000000000..bd0a4e0ae --- /dev/null +++ b/documentation/docs/recommendations/index.md @@ -0,0 +1,26 @@ +# Рекомендации + +Модульные тесты - это инструмент разработчика улучшающий качества работы. + +* Модульные тесты это код. +* Тесты идут совместно с доработками (при использовании git) +* Быстрый ответ +* Высокая скорость реализации и, соответственно, низкая стоимость. На проверку небольшой функции уходит всего несколько секунд. Изолированность юнитов позволяет проверять работоспособность нескольких модулей одновременно. +* Простота автоматизации. Unit тест исследует ответ кода на ввод данных и определенные действия. Он не требует проиграть сценарий взаимодействия конечного пользователя с новой функцией, поэтому автоматизация процесса не отнимает много сил и времени. + +На больших и сложных проектах стопроцентного покрытия кода тестами достичь сложно. К тому же, это нерационально. Показатель 70–90% считается хорошим. Он позволяет выявить максимальное количество ошибок. Мы собрали несколько практических советов по увеличению процента покрытия кода: + +* Пишите unit тест на каждый новый код сразу же. +* Используйте готовые решения – тестовые фреймворки. +* Создавайте тесты для проверки только одной функции. +* Используйте негативные тесты для проверки поведения программы в случае ввода неправильных данных. +* Используйте мутационные фреймворки с функцией изменения констант и условий для проверки качества unit тестов. +* Проверяйте тесты на стабильность. +* Следите за скоростью выполнения теста. + + +## Рекомендации по модульному тестированию с использованием YAxUnit + +Кроме [общих рекомендаций](common-recommendations.md) + +* Структура тестовых модулей: Модуль тестового набора должен соответствовать объекту решения, который он будет тестировать. Для этого мы предлагаем [схему наименования модулей](../getting-started/structure.md#схема-наименования-модулей) \ No newline at end of file diff --git a/documentation/docs/recommendations/links.md b/documentation/docs/recommendations/links.md new file mode 100644 index 000000000..0136e0300 --- /dev/null +++ b/documentation/docs/recommendations/links.md @@ -0,0 +1,5 @@ +# Полезные ссылки + +* [Код без тестов — легаси](https://habr.com/ru/companies/dododev/articles/544110/) + О легаси, о его изменении и покрытии тестами. +* https://less.works/ru/less/technical-excellence/unit-testing \ No newline at end of file diff --git a/documentation/docs/recommendations/what-to-test.md b/documentation/docs/recommendations/what-to-test.md new file mode 100644 index 000000000..8f18bca00 --- /dev/null +++ b/documentation/docs/recommendations/what-to-test.md @@ -0,0 +1,25 @@ +# Что тестировать + +:::warning +Это мой субъективный опыт, возможно он расходится с вашим. В этом случае вы можете или доработать данную статью или разместить свою рядом. +::: + +Ответ простой, тестируем то что дорабатываем. Начинайте с интеграционных (компонентных) тестов вашей задачи которые проверяют основные кейсы по задаче. + +В процессе написания тестов не требуется учитывать все возможные сценарии поведения программы. Рекомендуется сосредоточиться на ключевых задачах, а остальные вносить (дополнять) по мере необходимости. + +Идеальный подход: + +1. На основании технического задания необходимо выделить тест-кейсы. +2. Написать тесты реализующие эти кейсы, чаще всего это будут компонентные (интеграционные) тесты. +3. Реализовать функциональность задания. +4. При реализации добавляем новые тест-кейсы для упущенных и сложных моментов. +5. Добиваемся зеленого прохождения тестов. +6. Проверяем задачу вручную. +7. Задаем задачу. + +## Провокация: пишите компонентные тесты, а не unit тесты + +Заголовок выше противоречит основной мысли из многих современных книг и учебников. Компонентные тесты гораздо дольше выполняются и при этом не показывают четкое место поломки кода. Несмотря на это, мы говорим о них как об “оплоте стабильности”. Дело в том, что для качественного покрытия unit тестами требуется гораздо больше времени, чем для написания хорошего компонентного теста. Хорошее покрытие unit тестами не гарантирует вам правильность взаимодействия классов между собой. И это крайне дорогое удовольствие. На их разработку и поддержку требуется очень много времени. В реальном проекте программисту, как правило, не выделяют время на написание unit тестов. Получается, что если на проекте выбрана именно политика unit тестов, то эти тесты не отражают реальные сценарии использования приложения - проверяется “сферический конь в вакууме”, причем не во всех возможных состояниях системы. В итоге такая политика разработки тестов рано или поздно приводит к тому, что тесты перестают защищать от дефектов приложения. + +[Источник](https://habr.com/ru/companies/axenix/articles/724318/) \ No newline at end of file diff --git a/documentation/docs/recommendations/why-xunit.md b/documentation/docs/recommendations/why-xunit.md new file mode 100644 index 000000000..72bdf60b7 --- /dev/null +++ b/documentation/docs/recommendations/why-xunit.md @@ -0,0 +1,62 @@ +--- +tags: [Начало, Рекомендации] +sidebar_position: 0 +--- + +# Почему именно модульные тесты + +:::tip Перевод +Перевод (gpt) и адаптация статьи [Unit testing best practices with .NET Core and .NET Standard](https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices) by [John Reese](https://reese.dev/) +::: + +Существует несколько причин использования модульных тестов. + +
+![image](images/amongus.png) +
+ +## Сокращение времени функционального тестирования + +Функциональные (сценарные) тесты требуют значительных ресурсов. Обычно они включают запуск приложения и выполнение ряда шагов, которые вам (или другому сотруднику) необходимо пройти для проверки ожидаемого поведения. Тестировщик не всегда может знать все необходимые действия, и в таких случаях ему может понадобиться помощь более опытного коллеги. Время тестирования варьируется: оно может занять всего несколько секунд при проверке небольших изменений или растянуться на несколько минут при более крупных изменениях. Этот процесс нужно повторять при каждом внесении изменений в систему. + +Модульные тесты, наоборот, выполняются за миллисекунды, запускаются одним нажатием кнопки и не требуют глубокого понимания всей системы. + +## Защита от регрессии + +Регрессионные дефекты — это дефекты, которые появляются при внесении изменений в приложение. Обычно тестировщики проверяют не только новую функциональность, но и уже существующие функции, чтобы убедиться, что ранее реализованные возможности работают корректно. + +С модульными тестами можно запускать весь набор тестов после каждой сборки или даже после изменения одной строки кода. Это **дает уверенность** в том, что новый код не нарушает существующую функциональность. + +## Исполняемая документация + +Не всегда очевидно, что делает конкретный метод или как он себя ведет с определенными входными данными. Вы можете задаться вопросом: как метод работает, если передать ему пустую строку? А что если передать `Неопределено`? + +Когда у вас есть набор модульных тестов с понятными названиями, каждый тест может четко объяснить ожидаемый результат для заданных входных данных. Кроме того, он проверяет, что метод действительно работает правильно. + +## Меньшая связность кода + +Когда код слишком тесно связан, его трудно тестировать модульно. Без написания тестов связи в коде могут быть не такими очевидными. + +Создание тестов для вашего кода естественным образом уменьшает его связность, поскольку иначе его было бы сложнее тестировать. + +## Характеристики хорошего юнит-теста + +**Быстрый**: Не редкость, что у зрелых проектов есть тысячи юнит-тестов. Юнит-тесты должны выполняться за короткое время, в миллисекундах. + +**Изолированный**: Модульные тесты являются автономными, могут выполняться в изоляции и не зависят от каких-либо внешних факторов, таких как файловая система, внешние сервисы или база данных. + +**Повторяемый**: Выполнение модульного теста должно быть согласовано с результатами, то есть всегда возвращает тот же результат, если вы ничего не измените между выполнением. + +**Самопроверяемый**: Тест должен автоматически определять, прошел он или провалился, без вмешательства человека. + +**Соизмеримый**: Написание юнит-теста не должно занимать непропорционально много времени по сравнению с тестируемым кодом. Если написание тестов занимает значительно больше времени, чем написание самого кода, следует рассмотреть более удобный для тестирования дизайн решения. + +**Устойчивость к рефакторингу**: Тесты должны быть устойчивы к рефакторингу кода. Если производится рефакторинг, тесты не должны изменяться. Тогда успешное завершения всех модульных тестов после этапа рефакторинга поможет вам убедиться, что рефакторинг не испортил функционал приложения. + +**Простота поддержки**: Тесты должны быть просты в поддержке. Этот пункт выполняется, если выполняется предыдущий пункт. Должно быть легко добавлять новые тестовые сценарии - поддержка тестов в актуальном состоянии не должна стать тяжелым грузом для команды разработки. + +## Кодовое покрытие + +Высокий процент кодового покрытия часто ассоциируется с более высоким качеством кода. Однако само измерение не может определить качество кода. Установка слишком амбициозной цели по проценту кодового покрытия может оказаться контрпродуктивной. Представьте сложный проект с тысячами условных ветвлений и целью в 95% покрытия кода. Если в настоящее время проект поддерживает 90% покрытия, то затраты времени на учёт всех крайних случаев в оставшихся 5% могут оказаться огромными, а ценность этого может быстро снизиться. + +Высокий процент кодового покрытия не является индикатором успеха и не подразумевает высокое качество кода. Это просто отражает количество кода, покрытого юнит-тестами. Для получения дополнительной информации смотрите раздел о покрытии кода в юнит-тестировании. \ No newline at end of file diff --git a/documentation/docs/recommendations/yaunit-recommendations.md b/documentation/docs/recommendations/yaunit-recommendations.md new file mode 100644 index 000000000..e69de29bb diff --git "a/documentation/docs/recommendations/\320\262\320\275\320\265\320\264\321\200\320\265\320\275\320\270\320\265-\321\202\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\320\275\320\270\321\217.md" "b/documentation/docs/recommendations/\320\262\320\275\320\265\320\264\321\200\320\265\320\275\320\270\320\265-\321\202\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\320\275\320\270\321\217.md" new file mode 100644 index 000000000..c6407e4d5 --- /dev/null +++ "b/documentation/docs/recommendations/\320\262\320\275\320\265\320\264\321\200\320\265\320\275\320\270\320\265-\321\202\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\320\275\320\270\321\217.md" @@ -0,0 +1,76 @@ +# Внедрение тестирования в существующую разработку (Legacy) + +Legacy-код — это код, который был разработан в прошлом, все еще используется, но его сложно поддерживать и изменять. Причины продолжения работы с такими системами обычно связаны с затратами и временем, необходимыми для создания новой аналогичной системы, хотя иногда также существует недостаток осознания будущих последствий текущих усилий по написанию кода. + +Факт в том, что со временем код начинает "портиться". Это может происходить из-за изменений требований, плохо продуманного дизайна, применения антипаттернов или отсутствия соответствующих тестов. В конечном итоге код становится трудным для поддержки и сложным для изменения. + +Существует много подходов для предотвращения «гниения» кода, но одним из самых эффективных может быть написание тестируемого кода с последующим созданием достаточного количества модульных тестов для этого кода. Модульные тесты выполняют роль агентов, которые непрерывно проверяют систему на незатронутые пути, выявляют проблемные ошибки и показывают, оказывают ли внесенные изменения в подсистему положительное или отрицательное влияние на все программное обеспечение. Модульные тесты дают разработчику уверенность в том, что любые изменения кода не вызовут регрессий. + +Однако создание и поддержка хорошего набора модульных тестов может стать самостоятельной проблемой. Можно оказаться в ситуации, когда для набора тестов пишется больше кода, чем для самого тестируемого кода. Еще одной проблемой являются зависимости внутри кода. Чем сложнее решение, тем больше зависимостей, вероятно, будет между классами. Моки и стабы — признанный способ устранения этих зависимостей и тестирования кода в изоляции, но для их создания требуются дополнительные знания и опыт, чтобы разработать эффективные модульные тесты. + +## Legacy и тесты + +Legacy системы сложно поддерживать и дорабатывать. Создавать для нее тесты также проблемная и затратная затея. Код сложный в поддержке также сложно покрывать тестами. Покрытие тестами заставит вас пересмотреть дизайн системы или ее компонентов, что приведет к рефакторингу, что с большой долей вероятности приведет к возникновению дефектов (багов). + +Из этого следует, что при покрытии тестами каких-то компонентов Legacy системы вам придется выполнять те же действия как и при рефакторинге (тесты и проверки доработанных и зависимых подсистем, учитывать риски возникновения неполадок и т.д.) + +Поэтому не тестируйте Legacy системы просто так, включайте тестирование в задачи на доработку или рефакторинг с соответствующим рабочим процессом. + +В отличии от тестирования новой системы внедрение тестирования в устаревших системах будет дороже. + +## Стоимость внедрения + +Стоимость в основном складывается из двух составляющих: + +* Стоимость внедрения тестового фреймворка в проекте: обучение, настройка CI, выработка регламентов. +* Существующая кодовая база не готова для тестирования. Код был разработан не принимая во внимание необходимость его тестирования. Применение модульного тестирования в таком коде часто влечёт за собой улучшение существующего дизайна системы. Это не только увеличивает стоимость создания каждого теста, но также может привести к появлению новых ошибок в связи с изменением дизайна. Таким образом, добавление модульного теста к существующей кодовой базе должно сочетаться с другими мероприятиям, которые требуют изменений в тестируемом коде, как если бы вам потребовалось изменить этот фрагмент кода независимо. + +Кроме рефакторинга для тестирования решений на платформе 1С:Предприятие вам придется создавать множество тестовых данных. Разработчику придется потратить время на анализ и формирование валидных тестовых данных, это также придется добавить к стоимости внедрения. Чем сложнее объекты и взаимосвязи между ними, тем выше стоимость. + +Ниже я опиши несколько рекомендаций как сделать процесс внедрения менее болезненным. А пока, чтобы подсластить пилюлю поговорим о том что даст вам внедрение тестирования в легаси-системы. + +## Выгода от внедрения модульных тестов. + +Тесты нельзя начать писать по приказу, я про это писал в прошлой статье. Это как минимум не принесет пользы. + +* Фиксация требований +* Защита от регресса +* Документация +* Отладка +* Рефакторинг + +Для успешного внедрения тестов, особенно в легаси, вам необходимо продать эту идею разработчикам, кто-то поймет и дойдет самостоятельно, а кого-то необходимо будет направлять и подталкивать. В первую очередь необходимо заручиться поддержкой техлида или другого "продвинутого" члена команды. Продать идею в первую очередь ему, чтобы уже он прорастил ее в команде, был опорой и помощником для всей команды при внедрении. + +Дальше нужно проработать план, регламенты и прочие организационные моменты. + +## Рекомендации + +* Тестируем только дорабатываемый код. +* Не стремимся к 100% покрытию. На первых этапах достаточно одного, двух кейсов на задачу. +* Устаивайте встречи, обсуждения по процессу внедрения, собирайте обратную связь, не стесняйтесь пересматривать подходы. +* Заранее решите, как будет обрабатывать ситуации, когда тесты находят ошибки. Иногда исправление ошибки может создать [немалые проблемы](https://pikabu.ru/story/chuzhoy_kod_5762938?utm_source=linkshare&utm_medium=sharing). + +## План работ + +* Обучение работе с тестовым движком, лучше на самых простых примерах, например на новой пустой базе. + + +* В первую очередь стоит ознакомиться с [основными рекомендациями](yaunit-recommendations.md) по написанию модульных тестов +* Для начала основная цель писать максимально простые и полезные тесты. +* Выбираем новые с минимальными зависимостями объекты/методы. +* пишем тесты там где они очевидно нужны, там где нет, мы не заставляем их себя писать + +Пару месяцев поработать в таком ключе, только очевидные тесты, от души исходят которые. Главное не делать тесты там где они вредят: + +Не начинайте тестировать класс, который знает про множество других классов и общается с ними напрямую +Не пытайтесь тестировать поведение, типа сколько раз вызвался метод если клиент активный, тестируйте только результат, там где можно сравнить $a == $b +Не пишите много ассертов в одном тесте +Не бойтесь дублирования в тестах, дублирование ради выразительности теста - добро, а не зло. Тест нам помогает понять как рабтать с нашим классом. +Тесты нужно учиться писать, не старайтесь протестировать весь ваш легаси сразу, начните писать сначала очевидные тесты. + +Использованные материалы: + +* [Еще раз про тестирование](https://otis22.github.io/tdd,/unit,/integration/2021/01/28/about-testing.html) +* [Еще раз про тестирование 2(Как тестировать legacy)](https://otis22.github.io/tdd,/unit,/integration/2021/02/01/about-testing-2.html) +* https://bureau.ru/soviet/20170511/ +* https://dzen.ru/a/YiSVvZq2dn409d1y \ No newline at end of file diff --git a/documentation/docusaurus.config.js b/documentation/docusaurus.config.js index 987ccc1db..26360da38 100644 --- a/documentation/docusaurus.config.js +++ b/documentation/docusaurus.config.js @@ -42,8 +42,7 @@ const config = { sidebarPath: require.resolve('./sidebars.js'), // Please change this to your repo. // Remove this to remove the "edit this page" links. - editUrl: - 'https://github.com/bia-technologies/yaxunit/', + editUrl: (args)=>'https://github.com/bia-technologies/yaxunit/edit/develop/documentation/docs/' + args.docPath, }, blog: { blogTitle: 'Заметки', @@ -63,7 +62,6 @@ const config = { path: 'api', routeBasePath: 'api', sidebarPath: require.resolve('./sidebarsAPI.js'), - // ... other options }, ], [ @@ -73,7 +71,7 @@ const config = { path: 'lessons', routeBasePath: 'lessons', sidebarPath: require.resolve('./sidebarsLessons.js'), - // ... other options + editUrl: (args)=>'https://github.com/bia-technologies/yaxunit/edit/develop/documentation/lessons/' + args.docPath, }, ], [ @@ -83,7 +81,7 @@ const config = { path: 'contributing', routeBasePath: 'contributing', sidebarPath: require.resolve('./sidebarsContributing.js'), - // ... other options + editUrl: (args)=>'https://github.com/bia-technologies/yaxunit/edit/develop/documentation/contributing/' + args.docPath, }, ] ],