Вечер.
Хочется порассуждать о тестировании глобально, посасывая белое мартини из трубочки. Именно мартини, потому, что «массандровский» херес меня не восхитил, а коньяка в офисе уже мало.
Рассуждать хочу долго и о ситах.
Си́то — устройство для разделения сыпучих масс по величине их составляющих (зёрен, круп, песка и т.п.).
Сперва послушаем историков:
Человеческие жертвоприношения из ряда тестировщиков до и после релиза были распространённым явлением у менеджеров проектов, живших на полуострове Юкатан до нашествия конкистадоров. Тестировщиков приносили в жертву через повешение, утопление, отравление, избивание, а также посредством захоронения заживо в груде документации по проекту.
Наиболее жестоким видом жертвоприношения являлось, как и у девелоперов, вспарывание живота и вырывание из груди ещё бьющегося сердца тестировщика, который пропустил баг.
В жертву приносились как тестировщики, перекупленные в ходе войн представители других племён (корпораций), так и члены собственной группы, в том числе и высший слой, сениоры.
Выбор времени, очерёдности и способа жертвоприношения тестировщиков до сих пор не ясен. Точно установлено, что в жертвоприношение в огромных масштабах приносились тестировщики, выявленные в офисе после демонстрации продакшн-релизов представителям заказчика. Однако до сих пор неясно, вели ли менеджеры проектов кровопролитные войны для получения большего количества тестировщиков с целью принесения их в будущем в жертву.
Итак, утопление.
В начале ХХ века в центральной Америке было модно заниматься археологическими раскопками в «подземных» озерах. Для тех, кто в эти озера когда-то нырял, это довольно мрачное место. А для археолога ХХ века подобное озеро — кладезь информации. На дне, в многовековых наслоениях грязи, лежат никем не разворованные украшения, кости, черепа и прочие интересные археологические штучки.
Как все это добро достать?
Любое движение водолаза по дну такого жилищного массива баламутит всю грязь, и видимость склоняется к нулю.
А водолаз без движения уподобляется тем, кто уже никогда не выныривал из этого озера.
Выход — использовать насосы с ситами разного калибра. Положим три сита друг на друга. Наверху будет сито с самыми крупными ячейками. В середине — среднее. Снизу — самое мелкоячеистое.
Насос начинает выкачивать грязь из подземного озера. Грязь вываливается на сита, и археологам надо только подставлять свои любопытные руки под это месиво.
- Все крупные предметы будут лежать на верхнем сите.
- Самые мелкие тоже не пропадут, потому что их пропустит среднее сито, но задержит самое мелкое.
- Все бусины буду сосчитаны и уложены в ящики. Если их не пропьют помощники археологов, то позже их можно будет выкрасть из европейских музеев.
В идеальном, грамотном процессе девелопмента применим тот же принцип просеивания тонны грязи софта сквозь три сита. Но, поскольку у нас тут не все как у людей, то порядок расположения сит изменен. Сверху лежит самое мелкоячеистое, потом среднее, и внизу — сито с самой крупноячеистой сеткой.
Сделаем музыкальную паузу.
Прочти, раскрась, запомни:
Тестирование в Agile-way особенно сильно тем, что всё, что можно автоматизировать, автоматизируется до и во время разработки, а не отдельно от неё.
Продолжим сосать мартини и рассуждать.
Каждое «сито» в девелопменте имеет свое имя.
- Test Driven Development — сито мелкоячеистое.
- Acceptance Test Driven Development — сито среднеячеистое.
- Functional Testing — сито крупноячеистое.
Первые два сита — епархия программистов. Сам я существенно разбираюсь только в третьей теме.
Тестировщики и прочий рабочий люд тоже могут запускать акксептанс-тесты одной кнопкой, но по-настоящему полезно и мощно этот инструмент работает в руках программистов, которые могут писать, запускать и апдейтить акксептанс-тесты в ходе разработки, а не после или вместо неё.
Рассуждать о трех ситах следует абстрактно, с точки зрения процесса, не вдаваясь в детальки.
Например, понятие сита — уже абстракция.
Абстрактность тут нужна потому, что большинству людей понятие TDD поначалу не «поддаётся», и неимоверно трудным оказывается процесс понимания всего этого. Но когда во все врубился и вгрыззся, становится странным, что до сих пор все это не использовал…
Unit testing
Внятный пример — LoveUT.
Цитата из статьи:
Тихо мурлыкая под нос, Анна продолжает кодировать свой класс.
Суть «разработки через тестирование» проста:
- Сначала программист пишет тест для проверки функциональности, которую собирается написать. Этот тест называется «unit-test», потому, что он проверяет только одну единицу функционала.
- Затем программист пишет функциональность.
- И постоянно проверяет ее посредством ранее написанного, специально для нее, теста.
- Как только функционал удовлетворяет требованиям этого теста, разработка функции считается завершенной. Переходим к следующей.
- Во время разработки других функций, которые связаны с уже существующей, программисту можно и следует запускать юнит-тесты чаще, чем его легкие всасывают в себя один литр воздуха. Если все «горит зеленым» — программист счастлив. Если красное — увы.
Юнит-тестирование (на абстрактном уровне) позволяет достаточно быстро проверить, не привело ли очередное изменение кода к регрессу всей системы, то есть к ухудшению качества софта, а также существенно облегчает локализацию и устранение таких ошибок. Все это может ускорить разработку.
Особенности юнит-тестов:
- Неподготовленный человек не может их читать и понимать, не видя код.
- Уровень абстракции в юнит-тестировании неимоверно нулевой. Тесты обрабатывают определенный код, и будучи отстраненными от кода просто не находят смысла в своем существовании. Цель юнит-тестирования — изолировать отдельные части программы и показать, что по отдельности эти части — работоспособны.
- Когда функция меняется, юнит-тесты меняются в первую очередь.
Дальше надо сидеть рядом и показывать, как это работает. Этот момент в одиночку вряд ли одолеть, поэтому пропускаем подробности и едем дальше.
Acceptance testing
Внятное описание на английском языке: wikipedia.org.
Акксептанс-тесты — второй шаг после юнит-тестов. Это уже существенный уровень абстрактности от кода.
На предыдущем уровне проверялось и доказывалось, что каждая отдельная часть программы работоспособна. А на этом уровне проверяются взаимосвязи между отдельными частями программы, а также то, что программа выполняет заранее определенные задачи в определенном виде.
Мне когда-то казалось, что акксептанс-тестирование означает проверку соответствия отдельных модулей заявленным критериям или достижение заранее и очень точно определенных целей, что можно делать вручную. Я ошибался. Это изумительно работает в руках разработчиков. В остальных руках это работает или очень просто, или никак.
Особенности акксептанс-тестов:
- Неподготовленный человек вполне может их читать и понимать, не видя код.
- Уровень абстракции в юнит-тестировании неимоверно большой. Тесты обрабатывают не определенный код, а определенные абстракции, которые должны быть воплощены в коде.
- Функция меняется как угодно. Акксептанс-тесты для нее не меняются.
Неординарность подобного тестирования в том, что оно относится к методам чернокнижников тестирования черного ящика, когда про код мы знаем только то, что он где-то существует. Но тестирование происходит путем нажатия одной кнопки и прогона каких-то тест-кейсов, которые написаны на более-менее машинном языке, что неимоверно роднит эти тесты с юнит-тестами.
Парадкос в том, что подобные проверки хоть и абстрактны, но изрядно привязаны к существующему коду. Ведь абстракции проверяются работающим кодом, не так ли?
Для сочувствующих agile development уточняем: акксептанс-тесты проверяют юзер-сториз. А юзер-сториз — вне кода, вне технологий. Получается, что у нас есть high level tests, которые запускаются нажатиями кнопок.
Пример, пример!
Приводится стандартный пример из учебного проекта, который видит каждый, кто умудряется запустить FitNesse.
В примере проверялась такая юзер-стори:
«User want to perform money operation from my account to another one to pay/receive money to/from another User»
посредством следующего приемочного критерия:
«After money operation one of the accounts is increased and another is decreased by the same amount of money».
Критерии акксептанс-тестов задают (а в идеальном мире — пишут) заказчики софта, а не разработчики. Например, пресловутый заказчик просит* следующего:
- в базе должны существовать аккаунты разных юзеров.
- у каждого юзера на счету есть какие-то деньги.
- после нажатия кнопки «Сделать офигенно», со счета юзера №1 на счет юзера №2 должны передаваться какие-то суммы.
- * имхо, неимоверно удачно выбранный глагол.
Как выглядит такой тест, написанный в wiki-разметке в фреймворке «FitNesse»
Как эта таблица выглядит в формате wiki:
Make sure that there are no accounts in the bank.
!|ensure|clean accounts|
Create two different accounts.
!|create account for user|Bob|with amount|5|
!|create account for user|John|with amount|7|
и тд
Как это выглядит после прогона теста:
Как и почему это работает — не уточним, чтобы не переходить в детали и не разрушать сказки и абстракции. Но стоит сказать, что написание подобных тестов требует интеллекта и какого-то времени. Все равно, что стихи писать…
Предварительное резюме
Тестирование на уровне юнит-тестов показывает, что кусочки кода по-отдельности работают, как ожидалось.
Тестирование на уровне приемочных-тестов показывает, что кусочки кода по-отдельности работают, как ожидалось, а также то, что взаимосвязи между ними работают и выполняются, как ожидалось.
Сравним это с муравьями. Пропустим муравьев через мелкое сито. Если каждый в отдельности муравей не хромает и бодро шевелит своими шестью лапками — это отличный муравей. Но это не гарантирует нам то, что муравейник, большая система взаимоотношений между муравьями, будет работать.
Систему взаимоотношений мы начинаем проверять средним ситом. В ячейки попадают по-несколько муравьев, например, отсортированных по принципам выполнения задач. Отдельно — фуражиры, отдельно — охранники, отдельно — муравьи-мамки, отдельно — все остальное. Но сцепленное между собой по какой-то логике.
Functional testing
Сито с самыми крупными ячейками. Оно отлавливает логические взаимодействия, которые посредством проверки работоспособности мелких муравьев проверить невозможно.
Тут мы руками или головой проверяем, что будет, если мимо муравейника проедет танк, и кто победит, если муравьи нападут на танк. Ставлю сто баксов на муравьев…
Тут мы глазами проверяем, что случится, если все этажи муравейника соединены между собой проходами, и по ним все двигаются, как положено.
Тут мы узнаем, что бывает, если муравьев в системе слишком много или слишком мало.
Это можно делать как всеми органами осязания, воззрения и осмысления, так и заранее документируя свои действия (тест-кейсы).
Иногда проверки на этом уровне можно автоматизировать, но это не та волшебная автоматизация, которая присуща предыдущим уровням. Это грубое вмешательство с мечтательной целью избавиться от необходимости шерстить софт руками 🙂
Окончательное прозрение
Если
- проект только начинается
- разработка ведется в стиле on-going (неизвестны ни конечный результат, ни дата полного финала)
- код пишется «с нуля»
- заказчик умеет работать в agile-стиле
- разработчики умеют работать в agile-стиле
- инженеры «болеют» тестированием
- все три сита постоянно применяются в процессе разработки
тогда
- проект вполне может завершиться выпуском идеального продукта
- все риски, которые влечет неполное тестирование, могут быть предупреждены и преодолены
- скорость разработки возрастает в неимоверные разы
- каждый гребёт свою кучу денег и убегает домой пить пиво и смотреть футбол с любимой женой.
ниче так…
расскажи подробнее о FitNesse