- PVSM.RU - https://www.pvsm.ru -
Евгений Сафронов, Senior Developer, DataArt
«Тестирование можно использовать для того, чтобы доказать наличие ошибок в программе, и никогда — для того чтобы доказать их отсутствие!»
Эдсгер Дейкстра
Тестирование — это прикладная, стандартизированная, инженерная практика, которая применима в большинстве отраслей человеческой жизни. Тестирование, как философия, метрика или практика, существует намного дольше, чем программирование. К примеру, мы выковали меч. Чтобы проверить достаточно ли острым он получился, его испытывают. В некоторые эпохи даже на живом человеке, скажем, рабе.
Тестирование — это проверка работоспособности программы, предмета или любой промышленной разработки. Как и в любом деле, здесь есть свои тонкости и своя философия. Она, наверное, ближе тестировщикам, которые на произведенные нами вещи смотрят деструктивно — они с самого начала думают о том, как сломать предложенный разработчиками продукт. Это не очень типично для пользователей, которые более предсказуемы и обычно находят ошибки, случайно пытаясь сделать с нашей программой что-то нетипичное. У разработчиков подход к программам в принципе другой, но мы должны помнить: тестировщики должны ломать то, что мы создали — это их хлеб.
Разработчики не мыслят через тестирование, мы мыслим созидательно — и это может быть источником проблем. Когда нас просят написать программу, мы прежде всего думаем над концептами, структурами данных, их описанием и взаимодействием. В результате мы представляем решение — готовое, пусть и переполненное багами. Обычно мы слабо представляем, что будет, если изменятся входные данные, если пользователь будет в большом количестве совершать нетипичные операции.
При этом практика показывает, самые дорогостоящие ошибки случаются в результате небольших изменений. Потому что, если вы плохо написали кусок кода, более опытный коллега попросит вас его переделать. Обладая опытом, вы сами будете замечать неудачные фрагменты практически сразу. Но когда ошибки минимальны — отдельный символ, точка, знак финансовой операции, ошибка округления — найти их очень сложно, причем на любом этапе.
Рис. 1. Форма авторизации и количество вариантов ее заполнения.
Давайте посмотрим, почему возникает такая сложность в тестировании. На рисунке мы видим простую форму из трех полей. В первых двух полях можно ввести от 1 до 255 букв, в третьем от 1 до 20 символов. Можно также оставить строки пустыми. Внизу мы видим число возможных комбинаций, заметно превышающее количество элементарных частиц во вселенной. Я думаю, это убедительное доказательство того, что проверить все возможные кейсы нереально. Да и пытаться это сделать, наверное, нецелесообразно.
Рис. 2. Схема распределения ошибок по типам, согласно данным книги Стива Макконнелла «Совершенный код».
Примерно 25% общего объема приходится на структурные ошибки. Они возникают еще на этапе проектирования, когда вы создаете структуры данных и пишете реализации манипуляций с ними, то есть создаете некий «клей», скрепляющий эти структуры. Это огромный пласт фундаментальных ошибок.
Далее идут ошибки данных, связанные с реализацией функциональности и т. д. Интересно, что архитектура находится на последнем месте. Это можно объяснить тем, что, создав неправильную архитектуру, очень сложно в принципе выпустить конечный продукт.
Рис. 3. Распределение ошибок по стадиям разработки. Схема Стива Макконнелла.
На этап конструирования и проектирования приходится основной пласт ошибок и дефектов. Здесь работает известное правило Парето: 80 % дефектов локализованы лишь в 20 % вашего кода. Как правило, это какие-то корнер-кейсы. Если вы пишете операции с математической, например, с финансовой логикой, очень много ошибок может заключаться в пределах пограничных значений, при округлении чисел и т. п. Большинство ваших кейсов будут работать, но основная часть дефектов будет локализована в небольшом участке кода.
Вне зависимости от того, применяете ли вы ручное или юнит-тестирование, нельзя утверждать, что покрыв тестами 50 % кода, вам удалось предотвратить 50 % возможных дефектов. Если вы написали 100 юнит-тестов нельзя сказать, что итоговое качество вашего продукта повысилось, например, вдвое. Потому что разработчик, который не мыслит через призму тестирования, проверяет самые легкие кейсы. Нужные формы заполняются стандартными именем, фамилией и логином — разработчик идет дальше. Он с большой долей вероятности не станет проверять случаи, когда одно из полей остается пустым, или когда в него действительно вбили имя длиной в 255 знаков. Не будет он экспериментировать с нестандартными символами, комбинациями строчных и заглавных букв.
Тестирование — самая популярная методика управления качеством. Если вспомнить наш пример с мечом, то прежде чем использовать его в реальном бою, вы точно попросите испытывать его подольше. Еще лучше если на этапе производства вы договоритесь с кузнецом, чтобы тот испытал еще и металл, из которого он будет ковать оружие. Чтобы повысить качествоа программы, нам, соответственно, желательно увеличить объем тестирования и при этом использовать best practices. Пропорционально усилиям будет возрастать и качество нашего продукта.
Тестировать продукт могут все: разработчик, тестировщик, менеджер, заказчик. Иногда в небольших проектах роль тестировщика может выполнять менеджер или разработчик. Но все же все четыре роли здесь очень важны — каждый участник процессе смотрит на объект со своей точки зрения. Разработчик — через призму того, как работает код. У него с самого начала есть большая часть информации о том, как сломать интерфейс. Для тестировщика найти ошибки и сломать продукт — прямая задача. Заказчик смотрит на тесты совершенно иначе: ему интересно, как написать меньше тестов и заплатить меньше денег, все равно получив в итоге качественный продукт. Но самая интересная роль у менеджера, который обычно может опираться на случаи из собственной практики. Впрочем, как правило, он служит мостом между разработчиками, тестировщиками и заказчиком, а его основная задача сводится к презентации продукта. Но именно менеджер отвечает за качество продукта на выходе.
Классификация тестирования в виде анализа белого, серого и черного ящиков, интересна нам тем, что разработчик смотрит на собственный код как на белый ящик. А тестировщик и заказчик рассматривают программу как черный ящик — они до конца не понимают, как именно работает программа, знают только, какие фичи важны и должны работать обязательно.
Баг на сайте интернет-магазина, из-за которого какой-то товар не попадает в корзину, для разработчика — минорная проблема. Достаточно что-то закоммитить и раскоммитить, поменять переменные в одном месте — и все отлично заработает. Для тестировщика и тем более заказчика такой баг оказывается критическим, поскольку не позволяет клиенту купить нужный товар.
Но самый интересный ящик — серый. С ним работают те, кого я называю умными тестировщиамки, которые глубоко вникают в код и то, как работает продукт. Их интересует, как происходит деплой и коммуникация с базами данных.
Рис. 4. Пирамида модульного тестирования.
Основа пирамиды — юнит-тестирование — желательно, чтобы юнит-тестов в проекте было много. Далее следуют интеграционное тестирование наших модулей, Acceptance Tests и непосредственное UI-тестирование конкретных фич.
F.I.R.S.T. — методология описания требованиий, которым должны отвечать тесты. Прежде всего, модульные, но в принципе эти характеристики можно экстраполировать и на автоматические тесты. Ее создатель — известный дядюшка Боб — автор многих практик программирования.
Рис. 5. Схема TDD-процесса.
Две базовых методологии юнит-тестирования — похожие концепты TDD и BDD. BDD-подход основывается на TDD и призван устранить небольшие недостатки, которые присутствуют в TDD. Синтаксис BDD-тестов в большей степени ориентирован на бизнес и понятен заказчику и вообще технически менее подкованному человеку. TDD — о том, как делать вещи правильно. BDD — о том, как делать правильные вещи.
Первым делом вы пишете тест для кода, который еще не написан. Соответственно, видите, что он не работает. Чтобы он заработал, код ещё нужно написать. Потом вы все-таки пишете код, запускаете тест и убеждаетесь в том, что тест работает. Далее вы модифицируете свой тест (уточняете требования, добавлете проверку граничных условий и т. п.). После этого ваш тест перестает работать, и вы снова приходите к необходимости модифицировать ваш код. Этот процесс зациклен до того момента, пока ваш тест не будет совершенно четко отвечать конечным критериям и соответствовать задачам, которые перед вами стоят. Предположим, если вы решаете квадратное уравнение, ваша задача — написать функцию, которая находит корни квадратного уравнения. Предположим, вы написали тест, и он хорошо работает на базе действительных чисел. Но если пользователь захочет найти комплексное решение, то ничего не получится, тест завалится и потребует модификации. Поэтому эта схема должна быть в крови у разработчиков: нужно действовать по ней, пока ваша фича не будет соответствовать всем требованиям.
suite('#factorial()', function() {
test('equals 1 for sets of zero length', function() {
assert.equal(1, factorial(0));
});
test('equals 1 for sets of length one', function() {
assert.equal(1, factorial(1));
});
test('equals 2 for sets of length two', function() {
assert.equal(2, factorial(2));
});
test('equals 6 for sets of length three', function() {
assert.equal(6, factorial(3));
});
});
Пример написан на JavaScript, но думаю, в синтаксическом плане он будет примерно также выглядеть для PHP или Java. Все достаточно очевидно. Открыв тест, можно легко сказать, какие моменты протестированы не были. Можно легко и быстро что-то добавить. И таким образом проверить, корректно ли работает ли ваша функция.
BDD очень похож на TDD, но отличается тем, что формулировки в описании немного лучше адаптированы для людей, менее плотно связанных с разработкой: менеджеров или бизнес-аналитиков.
describe('#factorial()', function() {
it('should return 1 when given 0', function() {
factorial(0).should.equal(1);
});
it('should return 1 when given 1', function() {
factorial(1).should.equal(1);
});
it('should return 2 when given 2', function() {
factorial(2).should.equal(2);
});
it('should return 6 when given 3', function() {
factorial(3).should.equal(6);
});
});
Рис. 6. Инструменты юнит-тестирования.
QUnit — библиотека от разработчиков jQuery, позволяющая писать юнит-тесты в TDD-стиле, с механизмом assert. Вы пишете qunit.test, название теста, и что вы хотите протестировать.Затем запускаете его в отдельном файле, который должен видеть ваш код, и можете убедиться в том, что код работает.
Mocha — фреймворк для тестирования, позволяющий писать тесты в TDD и BDD-формате. Как правило, он используется совместно с другими инструментами для того, чтобы полностью реализовать TDD-подход в работе. То есть он позволяет запускать и описывать тесты в нужном формате, а к примеру за обработку проверок утверждений (asserts) отвечает другая библиотека (чаще всего Chai).
Sinon— инструмент для создания Mocks, stubs и spies, который очень часто используется в современных успешных проектах. Он содержит набор функций и модулей, которые здорово помогают и решают большое количество типичных проблем, которые возникают у разработчика во время создания юнит-тестов.
Jasmine — популярный BDD-библиотека, которая фактически стала стандартом в экосистеме самого распространенного Javascript-фреймворка Angular.
Автор: DataArt
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/267990
Ссылки в тексте:
[1] мышление: http://www.braintools.ru
[2] Источник: https://habrahabr.ru/post/342098/
Нажмите здесь для печати.