Упрощение тестирования с Approval Tests в Dart
Approval Tests представляют собой альтернативный подход к традиционным утверждениям при тестировании программного обеспечения. Они особенно полезны при работе со сложными объектами, такими как длинные строки, коллекции или объекты с большим количеством свойств. Захватывая результат вывода и сравнивая его с утвержденной версией, утверждающие тесты упрощают процесс проверки того, что ваш код ведет себя так, как ожидается. В этой статье мы познакомимся с Dart-реализацией Approval Tests и продемонстрируем их использование на примере.
Что такое Approval Testing?
Тесты утверждения предлагают другой способ выполнения утверждений в тестах. Традиционные утверждения сравнивают результат непосредственно с ожидаемым значением, например:
expect(a, equals(b));
В отличие от них, тесты утверждений используют подход проверки:
Approvals.verify(a);
Вот как это работает:
Если результаты полностью совпадают с утвержденным файлом, тест пройден.
Если есть разница, инструмент reporter подчеркивает несоответствие, и тест не проходит, предоставляя визуальное и текстовое представление изменений.
Если вы исправляете ошибку (или добавляете функцию), это изменит ожидаемое поведение. Поэтому при запуске теста он будет провален.
Зачем использовать Approval Tests?
Тесты утверждения упрощают цикл обратной связи, экономят время разработчиков, выделяя только то, что изменилось, а не требуя от них разбора всего вывода. Такой подход особенно полезен для сложных выводов, где традиционные утверждения могут быть громоздкими.
Dart реализация для Approval Tests
Чтобы использовать Approval Tests в своем проекте Dart, добавьте следующее в файл pubspec.yaml
:
dependencies:
approval_tests: ^1.0.0
Начало работы
Установите и откройте стартовый проект: Approvaltests.Dart.StarterProject
Этот проект включает:
Файл
.gitignore
для исключения артефактов одобрения.Линтер со всеми установленными правилами.
GitHub Actions для запуска тестов, при этом статус теста отображается на бейдже
README.md
.
Чтобы использовать Approval Tests:
Настройте тест: Импортируйте библиотеку Approval Tests в свой код.
Настройте репортер (по желанию): Репортеры выделяют различия между утвержденными и полученными файлами, когда тест проваливается. По умолчанию используется репортер
CommandLineReporter
. Вы также можете использоватьDiffReporter
для сравнения файлов в вашей IDE. В настоящее время пакет поддерживаетVS Code
иAndroid Studio
. Если по какой-то ошибке или другой причине вы хотите создать свой собственный, есть опцияcustomDiffInfo
.Управление файлом «approved»: Когда тест запускается в первый раз, автоматически создается файл одобрения. Этот файл представляет собой ожидаемый результат. Как только результаты теста станут удовлетворительными, обновите утвержденный файл, чтобы отразить эти изменения.
Утверждение результатов
Утверждение результатов заключается в сохранении файла .approved.txt
с желаемыми результатами. Распространенные подходы включают:
Использование diff tools для перемещения текста слева направо и сохранения результата.
Использование свойства
approveResult
вOptions
для автоматического утверждения после выполнения теста.Переименование файла
.received
в.approved
.
Репортеры
Репортеры запускают инструменты diff, когда что-то не совпадает, что позволяет легко увидеть изменения. Доступные репортеры включают:
CommandLineReporter
: Выводит diff в терминал.
DiffReporter
: Открывает инструмент Diff Tool в вашей IDE.
Пример: Ката «Gilded Rose»
Следующий пример демонстрирует, как использовать Approval Tests для рефакторинга кода GildedRose
из Gilded Rose Kata:
final class GildedRose {
final List<Item> items;
GildedRose({required this.items});
void updateQuality() {
for (int i = 0; i < items.length; i++) {
if (items[i].name != "Aged Brie" &&
items[i].name != "Backstage passes to a TAFKAL80ETC concert") {
if (items[i].quality > 0) {
if (items[i].name != "Sulfuras, Hand of Ragnaros") {
items[i].quality = items[i].quality - 1;
}
}
} else {
if (items[i].quality < 50) {
items[i].quality = items[i].quality + 1;
if (items[i].name == "Backstage passes to a TAFKAL80ETC concert") {
if (items[i].sellIn < 11) {
if (items[i].quality < 50) {
items[i].quality = items[i].quality + 1;
}
}
if (items[i].sellIn < 6) {
if (items[i].quality < 50) {
items[i].quality = items[i].quality + 1;
}
}
}
}
}
if (items[i].name != "Sulfuras, Hand of Ragnaros") {
items[i].sellIn = items[i].sellIn - 1;
}
if (items[i].sellIn < 0) {
if (items[i].name != "Aged Brie") {
if (items[i].name != "Backstage passes to a TAFKAL80ETC concert") {
if (items[i].quality > 0) {
if (items[i].name != "Sulfuras, Hand of Ragnaros") {
items[i].quality = items[i].quality - 1;
}
}
} else {
items[i].quality = items[i].quality - items[i].quality;
}
} else {
if (items[i].quality < 50) {
items[i].quality = items[i].quality + 1;
}
}
}
}
}
}
final class Item {
final String name;
int sellIn;
int quality;
Item(this.name, {required this.sellIn, required this.quality});
@override
String toString() => 'Item{name: $name, sellIn: $sellIn, quality: $quality}';
}
Код теста:
import 'package:approval_tests/approval_tests.dart';
import 'package:starter_project/starter_project.dart';
import 'package:test/test.dart';
void main() {
// Определяем все случаи
const allTestCases = [
[
"foo",
"Aged Brie",
"Backstage passes to a TAFKAL80ETC concert",
"Sulfuras, Hand of Ragnaros",
],
[-1, 0, 5, 6, 10, 11],
[-1, 0, 1, 49, 50],
];
group('Approval Tests for Gilded Rose', () {
test('verify all combinations', () {
Approvals.verifyAllCombinations(
allTestCases,
// options: const Options(
// reporter: DiffReporter(),
// ),
processor: processItemCombination,
);
});
});
}
// Функция для обработки каждой комбинации и создания вывода для проверки
String processItemCombination(Iterable<List<dynamic>> combinations) {
final receivedBuffer = StringBuffer();
for (final combination in combinations) {
final String itemName = combination[0] as String;
final int sellIn = combination[1] as int;
final int quality = combination[2] as int;
// Создаем объект Item, представляющий текущую комбинацию
final Item testItem = Item(itemName, sellIn: sellIn, quality: quality);
final GildedRose app = GildedRose(items: [testItem]);
// Изменение качества testItem
app.updateQuality();
// Добавление обновленного элемента в ожидаемые Items
receivedBuffer.writeln(testItem.toString());
}
// Возвращаем строковое представление обновленного элемента
return receivedBuffer.toString();
}
И на выходе мы получаем 120 различных комбинаций, на основе которых можно приступать к рефакторингу кода. Конечно, можно разделить генерацию комбинаций на части и убрать ненужные, но это лишь пример.
Заключение
Approval Tests для Dart - это отличная альтернатива традиционным утверждениям, особенно для сложных объектов и выводов. Упрощая процесс проверки, они помогают разработчикам сосредоточиться на том, что изменилось, сокращая время и усилия, необходимые для понимания влияния изменений в коде. Дополнительную информацию, примеры и способы участия можно найти в репозитории Approval Tests Dart.
Прошу обратить внимание, что это моя первая статья и первая публичная библиотека. Я активно общаюсь с создателем Approval Tests Ллевелином Фалько по поводу улучшений библиотеки. Ваши предложения, проблемы и запросы на исправление очень приветствуются. Давайте вместе сделаем эту библиотеку еще лучше! ?
Поставьте звезду на репозиторий, чтобы поддержать проект! ?
По всем вопросам обращайтесь через Telegram или по электронной почте yelaman.yelmuratov@gmail.com.