Перевод подготовлен в рамках онлайн-курса "Flutter Mobile Developer".
Приглашаем всех желающих на бесплатный двухдневный интенсив «Создаем приложение на Flutter для Web, iOS и Android». Узнать подробности и зарегистрироваться можно здесь.
Честно говоря, мне никогда особо не нравилось тестирование — оно затягивает разработку в целом и нередко усложняет обновление кодовой базы. После череды катастрофических поражений, я, наконец, взялся за матчасть и разобрался, как на самом деле работает тестирование. Забавно, но в результате я полюбил тесты, чего я даже не мог представить в прошлом.
Кроме того, какое-то время я не писал статьи, поэтому мне показалось отличной идеей вернуться к перу и рассказать, как мои неудачи в определенном деле в конечном счете привели к тому, что оно стало неотъемлемой частью разработки.
Давайте начнем.
Тестирование виджетов — что же это такое на самом деле?
Flutter-приложение имеет множество виджетов, предназначенных для самых разных целей. Виджеты имеют специфические макеты и интерактивные элементы, при этом у каждого свое назначение. В процессе разработки приложения в какой-то момент ты начинаешь путаться в его элементах. Из-за моей любви к физике, пускай и безответной, мне нравится одна фраза:
Может ли взмах крыльев бабочки в Бразилии вызвать торнадо в Техасе?
Эта фраза относится к теории хаоса, но мне нравится думать, что точно так же одно какое-то внесенное вами изменение в приложение, вроде никак не связанное с остальным кодом, может незаметно для вас нарушить его работу. Благодаря тестированию виджетов вы сможете проверять элементы своего приложения, обычно отдельные виджеты, в специальной среде и тем самым гарантировать, что упомянутый выше «торнадо в Техасе» не попадет в запланированный на ближайшую пятницу производственный релиз.
Обычная практика заключается в том, что пишется несколько групп тестов, проверяющих различные функциональные особенности виджета, например: на месте ли визуальные элементы, дают ли интерактивные элементы желаемый результат, работает ли анимация и т. д.
Кроме того, можно написать «золотые» тесты — тесты, которые проверяют, что ваши виджеты по-прежнему совершенны до последнего пикселя после нескольких месяцев полуночного кодинга и спринтов по программированию на кофеиновом допинге. Мы рассмотрим все это, но сначала о главном.
Основы тестирования
Тесты обычно находятся за пределами удобной и уютной папки lib и располагаются в собственной папке test.
Тесты виджетов выполняются в тестовой среде Flutter, а не на физическом или эмулируемом устройстве, поэтому не следует забывать о связанных с этим ограничениях.
Давайте посмотрим на структуру теста:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_app/main.dart';
void main() {
testWidgets(
'Test description',
(WidgetTester tester) async {
// Write your test here
},
);
}
Если пройтись по верхам, все выглядит довольно просто:
В функции
main()
, судя по всему, находятся тесты.Функция
testWidgets()
, как следует из названия, содержит сам тест.Внутри функции
testWidgets()
имеется описание теста и место для написания собственно кода теста.
Я отношусь к тем людям, которые учатся вождению только после того, как разберутся с принципом работы двигателя внутреннего сгорания. Так что давайте подробнее рассмотрим эту функцию и посмотрим, что она делает.
Разбор функции testWidgets()
Начнем с testWidgets
— а почему бы и нет?
Давайте посмотрим, какие возможности скрывает эта функция.
Пропуск теста целиком
Давайте представим, что некий Нэш, кем бы он ни был, попросил вас написать слишком много тестов, и вы понимаете, некоторые получились совсем корявыми. Не беда — вы можете пропустить их целиком, используя флаг skip.
testWidgets(
'Test description',
(WidgetTester tester) async {
// Write your test here
},
skip: true,
);
То есть тест не может показать неудачные результаты, если вы пропустите его. ¯\_(ツ)_/¯
Добавление тайм-аутов в тест
Это чуть более сложный прием, так как имеется два параметра для установки тайм-аута для теста.
testWidgets(
'Test description',
(WidgetTester tester) async {
// Write your test here
},
timeout: Timeout(Duration(minutes: 1)),
initialTimeout: Duration(seconds: 15),
);
Не вдаваясь в подробности, почему это делается именно таким образом, опишем суть этого действия: initialTimeout
— основной используемый тайм-аут, который может быть увеличен, но на значение, НЕ ПРЕВЫШАЮЩЕЕ значение параметра timeout
.
Для увеличения тайм-аута мы можем сделать так:
testWidgets(
'Test description',
(WidgetTester tester) async {
// Write your test here
// To increase timeout
tester.binding.addTime(Duration(seconds: 5));
},
timeout: Timeout(Duration(minutes: 1)),
initialTimeout: Duration(seconds: 15),
);
Таким образом добавляется время к начальному тайм-ауту, но если прибавка превысит значение параметра timeout
, то превышение не будет учитываться.
Небольшое отступление: изучение функций setUp() и tearDown()
Перед тем как перейти к дальнейшему рассмотрению функции testWidgets()
, узнаем побольше об одной полезной особенности среды тестирования Flutter — возможности задавать необходимые настройки перед тестированием и выполнять необходимую очистку после завершения тестирования.
Это делается с помощью четырех функций:
void main() {
setUpAll(() {
// This is called once before ALL tests
});
setUp(() {
// This is called once before EVERY test
});
tearDownAll(() {
// This is called once after ALL tests
});
tearDown(() {
// This is called once after EVERY test
});
testWidgets(
'Test description',
(WidgetTester tester) async {
// Write your test here
},
);
}
setUpAll()
и tearDownAll()
вызываются один раз — до и после выполнения тестов соответственно. setUp()
и tearDown()
вызываются до и после КАЖДОГО теста. Эти функции помогают с подготовкой и очисткой среды.
Важно помнить, что одни и те же функции вызываются для каждого теста.
Обратно к testWidgets(): изучение вариантов тестов
Иногда один тест необходимо выполнить для нескольких значений, каждое из которых требует своей настройки и очистки, но при одном и том же тестовом коде.
Как мастер плохих примеров, предложу следующий: допустим, у нас есть три цвета, в отношении которых мы должны выполнить один и тот же тест. Давайте поместим их в перечисление (enum):
enum WidgetColor {
red,
blue,
green,
}
Далее мы создаем вариант теста, который позволяет запустить один и тот же тест для нескольких значений:
class ColorVariant extends TestVariant<WidgetColor> {
@override
String describeValue(WidgetColor value) {
// TODO: implement describeValue
throw UnimplementedError();
}
@override
Future<Object> setUp(WidgetColor value) {
// TODO: implement setUp
throw UnimplementedError();
}
@override
Future<void> tearDown(WidgetColor value, covariant Object memento) {
// TODO: implement tearDown
throw UnimplementedError();
}
@override
// TODO: implement values
Iterable<WidgetColor> get values => throw UnimplementedError();
}
Мы видим знакомые нам функции setUp()
и tearDown()
, хотя и с разными параметрами, и можем выполнить настройку для каждого значения, однако самая важная вещь здесь — это get values
.
Теперь мы можем добавить значения WidgetColor
в вариант теста:
class ColorVariant extends TestVariant<WidgetColor> {
@override
String describeValue(WidgetColor value) {
return value.toString();
}
@override
Future<Object> setUp(WidgetColor value) {
// Do setup here
}
@override
Future<void> tearDown(WidgetColor value, covariant Object memento) {
// Do teardown here
}
@override
// TODO: implement values
Iterable<WidgetColor> get values => WidgetColor.values;
}
В результате этот вариант может запустить тест для всех значений WidgetColor
. Теперь мы можем передать это в наш тест с помощью параметра variant
:
void main() {
testWidgets(
'Test description',
(WidgetTester tester) async {
// Write your test here
},
variant: ColorVariant(),
);
}
При запуске этого теста он будет выполнен три раза для всех значений WidgetColor
:
Теперь, когда мы ознакомились с основной функцией, мы можем заняться изучением тестирования более подробно. Это будет во второй части статьи.
Узнать подробнее о курсе "Flutter Mobile Developer".
Участвовать в интенсиве «Создаем приложение на Flutter для Web, iOS и Android»