Як писати документацію, а отримати тести на Flutter

💡 Усі статті, обговорення, новини про Mobile — в одному місці. Приєднуйтесь до Mobile спільноти!

Привіт! Мене звати Олександр. Вже близько 10 років я створюю кросплатформні мобільні додатки, а ще я — GDE у Flutter та Dart. Сьогодні я пропоную вам ознайомитися з підходом до написання тестів через документацію. Тільки-но уявіть — ви можете створити звичайний документ англійською мовою, пошарити його з командою (або ще краще — створити його разом), обговорити функціонал та надати документацію для QA, а бонусом отримати widget та/або інтеграційні тести! Ця стаття може бути корисна не тільки розробникам, але й менеджерам, тестувальникам, продакт-оунерам, та й взагалі усім членам команди, що займається розробкою мобільного додатку на Flutter.

У чому проблема

З документації можна довідатися, що Flutter підтримує три види тестів: юніт, віджет та інтеграційні.

Юніт-тести доводять, що ваш код працює, інтеграційні — що працює додаток. Це зовсім не одне й те ж, якщо подумати. Логіка коду може не покривати усі користувацькі сценарії, адже фокусуючись на коді, ми можемо «забути» про деякі кейси. Робота додатку визначається не тільки тим, як написани його компоненти, але й тим, як злагоджено вони працюють один з одним. Проте на рівні, коли ми можемо тестувати інтеграції між компонентами, часто буває складно охопити граничні значення вхідних параметрів цих самих окремих компонентів.

Віджет-тести знаходяться десь посередині між юніт та інтеграційними. Вони швидкі, як юніт-тести, мають можливість оперувати не тільки інтерфейсом, а й класами, отже з ними можна легко покрити усі граничні значення. У той же час вони сфокусовані на UI, і це робить їх ближчими по своїй суті до інтеграційних тестів.

Коли ви створюєте новий Flutter-проєкт, то у каталозі test можете знайти саме віджет-тест. Але, з моєї практики роботи з багатьма командами, на реальних проєктах пишуться лише юніт-тести. Добре, кого я обманюю, зазвичай тестів немає взагалі. Чому так?

Можливо, це тому, що написання тестів — це окремий навик, який вимагає трохи експериментів та практики. В інтернеті не дуже багато рекомендацій, як писати тести ефективно, яку структуру під них створювати, як почати писати додатки, використовуючи легендарний підхід «test-first», та що робити, якщо додаток вже було написано, і тепер потрібно якось покрити його тестами. Треба багато чому навчитись, інакше ви ризикуєте приєднатися до девелоперів, які вважають, що «писати тести — складно», «у мене немає часу переписувати тести після зміни кожного рядка коду», що зрештою призведе до думки «мій клієнт не платить мені за тести».

Давайте мріяти

Окей, гугл, як спростити собі життя? Наприклад, можна ж найняти людину, яка буде нам усе тестувати. Достатньо один раз пояснити, як працює додаток, а далі цей тестувальник вже хай би робив свою справу і не заважав нам кодити. От якби існувала така людина з ідеальною памʼяттю, яка б дуже-дуже швидко, а головне безкоштовно, робила регресійне тестування всього додатку будь-коли, варто вам тільки про це сказати... Давайте трохи пофантазуємо, як ми могли би описати функціонал додатку у тому ж вигляді, який би ви пошарили з тестувальником через мессенджер.

Функціонал додатку ми будемо називати фічі (Features). Для того, щоб описати фічу, ми будемо використовувати сценарії (Scenario), а кожен сценарій будемо описувати одним або декількома кроками.

Та чому б, наприклад, не взяти той самий Counter app, який так знайомо генериться при створенні нового Flutter-проєкту. Коли маєте час та натхнення, можете повторювати за мною — просто створіть новий Flutter-додаток.

Отже, ми маємо Counter app. Створімо новий файл для нового тесту. Ми будемо використовувати англійську мову для опису функціоналу, а не Dart, отже нам потрібно створити не .dart-файл, а щось інше. Будемо описувати фічі каунтера, тож так файл і назвемо — counter.feature.

Наприклад, спочатку ми би хотіли перевірити, що після старту нашого додатку користувач бачить 0. Використовуючи термінологію, на яку ми погодились трохи вище, хай цей файл виглядає так:

Feature: Counter
  Scenario: Initial counter value is 0
    Given the app is running
    Then I see {'0'} text

Погляньте, цей файл вже має цінність! Його можна пошарити з кимось, хто буде робити мануальні тести, його можна використовувати для дискусій з Product Owner щодо функціоналу, а можна вести у ньому документацію.

Втілюємо мрію у реальність

Проте наша мета — зробити так, щоб ідеальний та безкорисливий тестувальник використовував цей файл в якості acceptance criteria. Щоб це сталося, додайте бібліотеку bdd_widget_test до dev_dependencies у файлі pubspec.yaml. Не певні, як саме це зробити? Ось мануал. Цей плагін використовує кодогенерацію, отже додайте також build_runner. На реальному проєкті він, найімовірніше, вже є у ваших залежностях через freezed, injectable, json_serializable, або безліч інших корисних плагінів.

Щоб запустити кодогенерацію, виконайте цю команду, знаходячись у корені вашого проекту:

flutter packages pub run build_runner watch --delete-conflicting-outputs

Коли команда завершиться, ви маєте побачити новий counter_test.dart файл з тестами! Дозвольте мені зізнатися — ми зовсім не вигадували нову мову для опису тестів. Наша фіча описана мовою Gherkin. Це — стандарт галузі для BDD (behaviour-driven development) методу розробки додатків через комунікацію з командою. Плагін bdd_widget_test парсить файли з фічами та на їх основі створює Flutter віджет-тести, які ви можете запускати так само, як і усі інші проєктні тести. Давайте тепер подивимось, що там нам згенерилось.

counter_test.dart:

// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: unused_import, directives_ordering

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

import './step/the_app_is_running.dart';
import './step/i_see_text.dart';

void main() {
  group('Counter', () {
    testWidgets('Initial counter value is 0', (tester) async {
      await theAppIsRunning(tester);
      await iSeeText(tester, '0');
    });
  });
}

step/the_app_is_running.dart:

import 'package:flutter_test/flutter_test.dart';
import 'package:bdd_showcase_app/main.dart';

Future<void> theAppIsRunning(WidgetTester tester) async {
  await tester.pumpWidget(MyApp());
}

step/i_see_text.dart:

import 'package:flutter_test/flutter_test.dart';

Future<void> iSeeText(WidgetTester tester, String text) async {
  expect(find.text(text), findsOneWidget);
}

counter_test.dart — це файл з віджет-тестами, дуже схожий за своєю суттю та змістом на той, що ви вже маєте у проєкті. Імʼя нашої фічі стало імʼям групи тестів, імʼя сценарію стало імʼям тесту, а наші кроки стали функціями. Для кожної з них плагін створив окремий файл з імплементацією.

Ці файли знаходяться у каталозі steps. the_app_is_running.dart містить функцію, яка робить саме те, що стверджує — запускає додаток MyApp у тестовому середовищі. Інший файл i_see_text.dart використовує фреймворк віджет-тестів для пошуку тексту на екрані.

bdd_widget_test відокремив сценарії від конкретних кроків, таким чином той самий крок може бути використано у декількох сценаріях. Чим більше сценаріїв ви створите, тим рідше будуть зʼявлятися нові кроки, і тим легше та швидше вам буде писати нові тести.

Спробуйте запустити тест прямо з IDE, або через консоль, використовуючи команду flutter test.

Коли ви зміните опис фічі у counter.feature файлі, відповідний файл тестів counter_test.dart буде перезаписано, але імплементації кроків залишаться незмінними. Давайте подивимось, що буде, коли ми додамо ще один сценарій:

Feature: Counter
  Scenario: Initial counter value is 0
    Given the app is running
    Then I see {'0'} text

  Scenario: Tap the Plus icon increments the counter
    Given the app is running
    When I tap {Icons.add} icon
    Then I see {'1'} text

Тільки-но ви зберегли файл з фічами, як одразу ж файл з тестом був створений наново, маючи і старий, і новий тест усередині.

Висновок

Цей підхід можна використовувати як для маленьких, так і для дуже великих додатків. Тепер можна описувати фічі, обговорювати їх з командою (бо тепер не тільки розробники можуть прочитати тести), та не боятись рефакторингу, адже ідеальний тестувальник буде невтомно перевіряти кожну фічу. Як бонус, ми маємо той самий «test-first» підхід, адже тепер дуже легко й інтуїтивно можна спочатку описати фічу, переконатися, що ми розуміємо, що саме ми зараз будемо писати, а на додачу відразу отримати червоний тест. Тепер усе, що залишається — це зробити так, щоб тест став зеленим.

Якщо ви повторювали за мною, то вже маєте приклад використання bdd_widget_test на вашому компʼютері. Якщо ні — ось тут лежить репозиторій з кодом.

Якщо вам цікаво подивитись, як можна додати BDD-підхід до існуючого додатку, подивіться моє відео «BDD in Flutter #2 — adding tests to a completed project», там будуть посилання на більш просунуті кейси та деякі tips & tricks.

Оригінал статті можете знайти тут.

Дякую, що дочитали статтю! Любіть тести та бережіть себе!

👍ПодобаєтьсяСподобалось9
До обраногоВ обраному3
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Підписатись на коментарі