TDD и AndroidПри разработке любого относительно сложного программного комплекса (особенно при разработке систем, выполняющих сложные расчеты) программист большую часть времени тратит не на написание программного кода, а на его отладку. О том, как потратить это время с пользой мы сегодня тебе расскажем. В отладке программного обеспечения действует так называемый принцип Парето — в данном случае он может быть сформулирован как "На отладку 20%-ного кода уходит 80% общего времени отладки". Попробуем разобраться, почему так происходит. Предположим, существует программа, выполняющая некоторые расчеты. Также предположим, что в изначальной версии программы третий метод не был реализован (и, соответственно, не был учтен при проектировании архитектуры ПО). Однако в следующей версии программы понадобилось его добавить. Как видно из блок-схемы, третий метод очень трудно отлаживается, и в случае, если программист допустит ошибку, найти ее при отладке будет очень сложно, так как из-за условного оператора этот метод будет вызываться крайне редко. Одним из выходов может быть написание небольшой отдельной программы для тестирования этого метода. Однако он не профессионален и зачастую слишком сложен — к примеру, если этот метод зависит от других классов. Для того чтобы избежать таких проблем, следует использовать модульное тестирование. Модульное тестирование (англ. unit testing) — один из методов тестирования программного обеспечения, при котором пишется отдельный набор тестов для каждого класса, состоящий из тестов для каждого метода, объявленного в тестируемом классе. Подобная техника заменяет большую часть отладки и значительно упрощает разработку приложения как на конечных, так и на начальных стадиях, потому что позволяет проверить все случаи поведения каждого метода тестируемого класса, что зачастую не представляется возможным при "ручной" отладке. К примеру, если требуется перебрать все значения у аргумента функции типа int32, — у модульного теста это займет несколько минут, а тестировщик вряд ли вообще сможет справиться с этим заданием. Для еще большего упрощения и ускорения разработки рекомендуется использовать методологию разработки, называемую "Разработка через тестирование". Разработка через тестирование, или TDD (англ. Test Driven Development), — один из видов экстремального программирования. Если в классических методах разработки сначала пишется программный код, а потом (при условии использования модульных тестов) — тесты, то в TDD сначала пишутся модульные тесты, и только потом тестируемые классы и методы реализуются в программном коде. Несмотря на то, что этот метод относится к экстремальному программированию, и, на первый взгляд, кажется абсурдным, он все чаще используется при разработке крупных программных продуктов. Требования, предъявляемые к программному коду при разработке через тестирование
Преимущества разработки через тестирование
Роберт Мартин, известный специалист в области экстремального программирования, предлагает использовать следующий алгоритм TDD: "Сначала нужно добиться, чтобы код хоть как-то работал, и только потом улучшать его". На практике эта рекомендация выглядит следующим образом:
Когда не рекомендуется использовать модульные тесты?В задачах, выполняющихся слишком долго. К примеру, метод, выполняющий запрос к БД, лучше исключить из списка тестируемых, потому что все тесты должны выполняться каждый раз при запуске тестирования, и если тест выполняется долго — программист будет стремиться отключить его. Если нужно протестировать метод, разбирающий ответ от БД, то лучше отделить этот метод непосредственно от запроса, и передавать ему заранее подготовленные "фальшивые" данные. Как это выглядит на практике?В качестве примера мы напишем приложение (и модульные тесты к нему) для ОС Android. Этот пример частично актуален не только для Android, но и для любой платформы, поддерживающей Java. Тестовое приложение будет принимать от пользователя массив точек и принимать решение, расставлены ли они в правильном порядке, возвращая true или false. Логика расстановки проясняется, если представить, что на первой точке написано, к примеру, "1", на второй — "2", на третьей — "3" и т.д. Точки могут располагаться в ряд, в столбик или в смешанном порядке. Для начала нужно создать проект для Android (подразумевается, что у тебя уже установлена среда разработки, к примеру, Eclipse, а также плагин ADT и Android SDK) и тестовый проект. Для того чтобы создать тестовый проект, нужно в диалоге создания проекта нажать кнопку "Next" и отметить чекбокс "Create a test project". Теперь можно приступить к написанию модульного теста. В тестировании Java-приложений стандартом де-факто считается JUnit. JUnit также включен в Android SDK, соответственно, тестирование приложений на Android производится именно при помощи этой библиотеки. Тестирование логикиСначала создается TestSuite для проекта (его код модифицировать не нужно), а потом — TestCase для каждого класса. В нашем случае класс всего один, и в нем содержится всего один открытый метод (статический). Напишем два теста — первый будет передавать заведомо верный список точек, второй — заведомо неверный.
Прошу обратить внимание, что каждый тест должен начинаться со слова test, иначе он не будет распознан JUnit как… ну, в общем, как тест :). Чтобы запустить выполнение тестов, нужно нажать <Ctrl> + <F11>. Однако тесты даже не запустятся, потому что класс Matrix не содержит метода orderIsRight(). Теперь следует написать заглушку для этого метода, состоящую из одной строчки: "return false". Тесты запустятся, но их поведение будет немного странным: первый тест не будет пройден, а второй — будет. Сначала нужно добиться прохождения первого теста, и только потом браться (если, конечно, потребуется) за второй. Реализуем метод orderIsRight() следующим образом:
Метод pointsAreInWrongOrder() — закрытый статический, состоящий из одной строки:
Нетрудно догадаться, что он возвращает true, если первая точка находится правее, чем вторая (либо перекрывает ее). Запускаем тесты — они проходят. Отлично, на этом тестирование логики приложения можно считать законченным. Теперь можно приступить к тестированию графического интерфейса. Тестирование GUIГрафический интерфейс — крайне важная часть любого ПО. Если пользователю не понравится GUI — он, скорее всего, не будет использовать программу, какая бы мощная "начинка" не содержалась внутри. Тестирование GUI должно быть не менее тщательным, чем тестирование логики программы, причем тестирование желательно максимально автоматизировать — только так можно покрыть наибольшее количество возможных вариантов действий пользователя. Конечно же, программист может протестировать интерфейс вручную, но здесь есть один нюанс. Дело в том, что программист тестирует приложение с точки зрения программиста, а не пользователя, и ему, в отличие от пользователя, может просто не прийти в голову, что можно ввести символьную строку в поле для ввода чисел. В Android SDK есть инструментарий для тестирования графического интерфейса, в который включен класс ActivityInstrumentationTestCa se2. Его следует наследовать при написании модульных тестов для GUI. Стоит обратить внимание на то, что ActivityInstrumentationTest Case2 — это шаблонный класс, т.е. использовать его следует в виде class MainActivityTest extends ActivityInstrumentationTestCase2<Mai nActivity>. Наше приложение содержит одну Activity (MainActivity), ее мы и будем тестировать. Как она выглядит, можно увидеть на рисунке на предыдущей странице. Я использую отладку на устройстве с Android 2.3.4 через Wi-Fi. Кстати говоря, отладка по Wi-Fi очень удобна по сравнению с отладкой по кабелю и, тем более, в эмуляторе. Для того чтобы отлаживать приложения подобным образом, нужно установить виджет Adb over Wi-Fi из Android Market (подходит только для телефонов с правами root). После запуска нужно подключиться к устройству. Например, следующим образом:
Тестирование GUI выглядит приблизительно так же, как и тестирование логики приложения. Создается класс, в нем создается метод setUp(), в котором инициализируются объекты. Объекты, представляющие Activity, поля для ввода текста и кнопку следует сделать закрытыми полями:
Инициализируются они следующим образом:
В первую очередь нужно проверить, создались ли элементы интерфейса:
Этот тест проходит без ошибок, но лучше перестраховаться и выполнять его всегда — он может не проходить, если, к примеру, у какого-либо элемента неправильно задано какое-то свойство. В полях для ввода указываются координаты точки через пробел, а в textView появляется результат (OK или NOT OK). Напишем модульный тест для просчета:
Он не проходит, потому что редактирование не обрабатывается. Напишем обработчик для события смены фокуса на EditText’ах — и тест будет пройден. Код обработчика приводиться не будет из-за ограниченности объема статьи, к тому же, он абсолютно тривиален. Также необходимо проверить, правильно ли обрабатываются неверные данные - это задание останется на твоей совести, тем более, что оно практически аналогично предыдущему тесту :). При запуске тестов можно наблюдать, как сами нажимаются элементы интерфейса, переключаются Activity (для каждого теста Activity запускается заново) — забавное зрелище :). Мораль сей басни таковаВ данной статье были рассмотрены как теоретическая, так и практическая части разработки через тестирование. В качестве примера приведен код приложения на Java под ОС Android с использованием инструментария JUnit, однако принципы тестирования приблизительно одинаковы на любой ОС. Конечно же, разработка через тестирование не исключает тестирования приложения из цикла разработки, однако существенно облегчает как последующее тестирование, так и разработку в целом, позволяя избежать досадных ошибок, на исправление которых уходят многие часы. |