Итак, вы написали программу, она компилируется и даже работает! Что теперь?
Это зависит от нескольких вещей. Если вы написали свою программу так, чтобы ее можно было запустить один раз и выбросить, то всё готово. В этом случае то, что ваша программа работает не для всех случаев, может не иметь значения – если она работает для одного случая, в котором она вам нужна, и вы собираетесь запустить ее только один раз, то всё готово.
Если ваша программа полностью линейна (не имеет условных выражений, таких как операторы if или switch ), не принимает входных данных и дает правильный ответ, то всё готово. В этом случае вы уже протестировали всю программу, запустив ее и проверив вывод.
Но более вероятно, что вы написали программу, которую собираетесь запускать много раз, и которая использует циклы и условную логику и принимает какие-либо пользовательские входные данные. Возможно, вы написали функции, которые можно будет повторно использовать в других будущих программах. Возможно, вы столкнулись с неконтролируемым разрастанием, когда вы добавили новые возможности, которые изначально не планировались. Возможно, вы даже собираетесь распространять свою программу среди других людей (которые, вероятно, попробуют то, о чем вы даже не задумывались). В этом случае вам действительно следует убедиться, что ваша программа работает так, как вы думаете, в самых разных условиях – и это требует некоторого упреждающего тестирования.
Лекция 113: Тестирование программ
Тот факт, что ваша программа работала для одного набора входных данных, не означает, что она будет работать правильно во всех остальных случаях.
Проверка программного обеспечения (также известная как тестирование программного обеспечения) – это процесс определения того, работает ли программное обеспечение должным образом во всех случаях.
Задача тестирования
Прежде чем мы поговорим о некоторых практических способах тестирования вашего кода, давайте поговорим о том, почему комплексное тестирование программы – это трудная задача.
Рассмотрим следующую простую программу:
#include void compare(int x, int y) < if (x >y) std::cout int main() < std::cout ; std::cin >> x; std::cout ; std::cin >> y; compare(x, y); return 0; >
Предполагая, что int занимает 4 байта, непосредственное тестирование этой программы со всеми возможными комбинациями входных данных потребует выполнения программы 18 446 744 073 709 551 616 (~ 18 квинтиллионов) раз. Понятно, что эта задача невыполнима!
Каждый раз, когда мы запрашиваем у пользователя входные данные или добавляем условие в наш код, мы увеличиваем количество возможных способов выполнения нашей программы с помощью некоторого мультипликативного коэффициента. Для всех программ, кроме простейших, непосредственное тестирование каждой комбинации входных данных становится невозможным практически сразу.
Теперь ваша интуиция должна подсказывать вам, что вам на самом деле не нужно запускать показанную выше программу 18 квинтиллионов раз, чтобы убедиться, что она работает. Вы можете разумно заключить, что если случай 1 работает для одной пары значений x и y , где x > y , он должен работать для любой пары x и y , где x > y . Учитывая это, становится очевидным, что, чтобы иметь высокую степень уверенности, что программа работает правильно, нам нужно запустить ее только три раза (один раз для проверки каждого из трех случаев в функции compare() ). Существуют и другие аналогичные приемы, которые мы можем использовать, чтобы значительно сократить количество раз, когда нам нужно что-то тестировать, чтобы сделать тестирование управляемым.
Зачем нужно тестирование
О методологиях тестирования можно написать много – на самом деле, мы могли бы написать об этом целую главу. Но поскольку это тема, не специфичная для C++, мы будем придерживаться краткого и неформального введения, предоставляющего необходимую информацию для вас (как разработчика), тестирующего свой собственный код. В следующих нескольких подразделах мы поговорим о некоторых практических вещах, о которых вам следует подумать при тестировании кода.
Тестируйте свои программы небольшими частями
Рассмотрим производителя автомобилей, который создает нестандартный концепт-кар. Как вы думаете, что из перечисленного он делает?
- Собирает (или покупает) и тестирует каждый компонент автомобиля по отдельности перед его установкой. Как только будет доказано, что компонент работает, интегрирует его в автомобиль и тестирует его повторно, чтобы убедиться, что объединение работает. В конце тестирует всю машину, чтобы окончательно убедиться, что всё в порядке.
- Собирает автомобиль из всех компонентов за один присест, а затем, в конце тестирует всё это в первый раз.
Вероятно, кажется очевидным, что вариант а) – лучший выбор. И всё же многие начинающие программисты пишут код, подобно варианту b)!
В случае b), если какая-либо из частей автомобиля не будет работать должным образом, механику придется провести диагностику всей машины, чтобы определить, что случилось – проблема может быть где угодно. У симптома может быть много причин – например, автомобиль не заводится из-за неисправной свечи зажигания, аккумулятора, топливного насоса или чего-то еще?
Это приводит к потере времени, в попытках точно определить, где проблемы, и что с ними делать. И если проблема обнаружена, последствия могут быть катастрофическими – изменение в одной области может вызвать «волновые эффекты» (изменения) во многих других местах. Например, слишком маленький топливный насос может привести к изменению конструкции двигателя, что приведет к изменению конструкции рамы автомобиля. В худшем случае вы можете в конечном итоге переделать огромную часть автомобиля, просто чтобы учесть то, что изначально было небольшой проблемой!
В случае а) компания проводит испытания в процессе работы. Если какой-либо компонент неисправен прямо из коробки, они немедленно узнают об этом и могут исправить/заменить его. Ничто не интегрируется в автомобиль, пока не будет доказано, что оно работает само по себе, а затем, как только эта деталь будет интегрирована в автомобиль, она повторно проверяется. Таким образом, любые неожиданные проблемы обнаруживаются как можно раньше, и при этом они остаются небольшими проблемами, которые можно легко исправить.
К тому времени, когда дело дойдет до сборки всей машины, у производителя должна быть разумная уверенность в том, что машина будет работать – в конце концов, все части были испытаны изолированно и при первоначальной сборке. По-прежнему возможно, что на этом этапе будут обнаружены неожиданные проблемы, но этот риск сведен к минимуму всем предыдущим тестированием.
Приведенная выше аналогия верна и для программ, хотя по каким-то причинам начинающие программисты часто этого не осознают. Намного лучше писать небольшие функции (или классы), а затем немедленно их компилировать и тестировать. Таким образом, если вы допустили ошибку, вы будете знать, что она должна быть в небольшом объеме кода, который вы изменили с момента последней компиляции/тестирования. Это означает, гораздо меньше мест для поиска и гораздо меньше времени, затрачиваемого на отладку.
Изолированное тестирование небольшой части кода для проверки правильности «единицы» (юнита, модуля) кода называется модульным тестированием (юнит-тестированием). Каждый модульный тест (юнит-тест) предназначен для проверки правильности определенного поведения модуля.
Лучшая практика
Пишите свою программу небольшими, четко определенными модулями (функциями или классами), а по мере написания часто компилируйте и тестируйте свой код.
Если программа короткая и принимает пользовательские входные данные, может быть достаточно попробовать различные варианты этих пользовательских входных данных. Но по мере того, как программы становятся всё длиннее и длиннее, этого становится недостаточно, и появляется больше пользы от тестирования отдельных функций или классов перед их интеграцией в остальную программу.
Итак, как мы можем протестировать наш код в модулях?
Неформальное тестирование
Один из способов тестирования кода – это неформальное тестирование при написании программы. После написания единицы кода (функции, класса или какого-либо другого дискретного «пакета» кода) вы можете написать код для проверки только что добавленного модуля, а затем стереть тест, как только он будет пройден. Например, для следующей функции isLowerVowel() вы можете написать следующий код:
#include // Мы хотим протестировать следующую функцию bool isLowerVowel(char c) < switch (c) < case ‘a’: case ‘e’: case ‘i’: case ‘o’: case ‘u’: return true; default: return false; >> int main() < // Итак, вот наши временные тесты для проверки ее работы std::cout
Если возвращаемые результаты буду как 1 и 0, тогда всё готово. Вы знаете, что ваша функция работает в некоторых основных случаях, и, глядя на код, вы можете разумно сделать вывод, что она будет работать для случаев, которые вы не тестировали ( ‘e’ , ‘i’ , ‘o’ и ‘u’ ) . Таким образом, вы можете стереть этот временный тестовый код и продолжить написание программы.
Сохранение ваших тестов
Хотя написание временных тестов – это быстрый и простой способ протестировать какой-либо код, он не учитывает тот факт, что в какой-то момент вы, возможно, позже захотите протестировать тот же код снова. Возможно, вы изменили функцию, добавив новую возможность, и хотите убедиться, что не нарушили ничего из того, что уже работало. По этой причине имеет смысл сохранить ваши тесты, чтобы их можно было запускать в будущем снова. Например, вместо того, чтобы стирать временный тестовый код, вы можете переместить тесты в функцию testVowel() :
#include bool isLowerVowel(char c) < switch (c) < case ‘a’: case ‘e’: case ‘i’: case ‘o’: case ‘u’: return true; default: return false; >> // Сейчас ниоткуда не вызывается // Но находится здесь, если вы захотите повторить тест позже void testVowel() < std::cout int main()
По мере создания дополнительных тестов вы можете просто добавлять их в функцию testVowel() .
Автоматизация ваших тестовых функций
Одна из проблем показанной выше тестовой функции заключается в том, что при запуске вы должны вручную проверять результаты. Это требует, чтобы вы помнили, какой ответ ожидается (при условии, что вы его не задокументировали), и вручную сравнивали фактические результаты с ожидаемыми.
Для чего необходимо тестирование программы
Комментарии
Популярные По порядку
Не удалось загрузить комментарии.
ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ
️10 онлайн-платформ для заработка на тестировании
Можно начать без заказчиков и даже сделать профессию тестировщика подработкой. Онлайн-платформы для тестирования помогут заработать на фрилансе.
Погружаемся в основы и нюансы тестирования Python-кода
Пишете код на Python? Будет полезно знать о принципах тестирования Python-кода ваших приложений. Изучайте статью и применяйте навыки в работе.
6 книг по тестированию ПО
Каждый продукт требует проверки, и ПО не исключение. Представляем подборку книг про тестирование, которая поможет вам в этом нелегком деле.
Источник: proglib.io
Если бы вам пришлось ответить на вопрос «Что такое тестирование?», что бы вы сказали? Это понятие довольно трудно впихнуть в пару-тройку коротких предложений.
Плюс к тому многие недопонимают, что же такое тестирование, чем занимаются тестировщики – даже среди самих тестировщиков. Тестирование как навык и как профессия постоянно развивается. В этой статье мы рассматриваем, чем тестирование является, и чем нет.
Из чего состоит тестирование
Расследование
Расследование определяется как «наблюдение или изучение путем близкого наблюдения и систематического изучения» [1].
Процесс тестирования должен быть расследованием. Мы не всегда знаем, что получим на выходе, но наша задача – выяснить информацию, которая поможет людям принимать решения. Это не просто сравнение работы системы со спецификацией, где прописан ожидаемый результат. Мы должны мыслить критически, задавать сложные вопросы, рисковать, подмечать то, что на первый взгляд кажется несущественным, а при тщательном анализе оказывается важным и требующим дальнейшего изучения.
Исследование
Список требований всегда неполон – всегда найдутся неучтенные требования, которые опущены или предполагались по умолчанию. Вне зависимости от полноты ваших требований, они всегда будут неполны. Вы не будете заранее знать, что делает ваше приложение. И тут в игру вступает исследовательское тестирование.
Исследовательское тестирование определяется как одновременное обучение, тест-дизайн и прогон тестов [2]. Тестировщик исследует приложение, узнает новую информацию, учится, находит что-то новое для тестирования по ходу дела. Он может заниматься этим в одиночку или в паре с другим тестировщиком (а может, и разработчиком).
Тестирование не должно восприниматься, как прогон списка готовых тестов или тест-кейсов, дающих твердый «pass/fail’ результат. Если у вас есть юзер-стори или набор требований, конечно, важно иметь их в виду. Однако критерии приемки бывает полезно переформулировать как «критерии отказа». Когда критерии приемки не удовлетворены, продукт не принят, но если они в порядке, это не значит, что в ПО нет багов.
Проверки и верификация должны совмещаться с исследованием и расследованием, а также вопросами в духе «Что будет, если…», на которые вы можете не знать ответа, пока не попробуете, и ответы на которые не покрыты вашими готовыми кейсами.
Снижение рисков
Одна из причин, по которой мы тестируем – это поиск дефектов, рисков и другой информации о продукте, которая позволяет нам действовать, чтобы конечный пользователь не пострадал. Мы можем:
- Исправлять баги.
- Переоценить и изменить изначальные требования.
- Помочь пользователю с продуктом.
- Создать пользовательскую документацию.
- Донести информацию об имеющихся проблемах до заинтересованных лиц.
Устранить все возможные баги, с которыми может столкнуться пользователь, просто невозможно, каким бы сложным не было ваше ПО. Однако, тестируя, мы снижаем риск того, что пользователь с ними столкнется – или серьезность последствий такого столкновения.
Ценность
Тестирование – это ценная часть разработки ПО, но его часто недооценивают из-за его непредсказуемой и креативной природы.
Результат ежедневного труда разработчика – это код, аналитика – требования или документация, однако результаты труда тестировщика может быть довольно сложно измерить. Зачастую тестировщикам сложно рассказать о своих планах, своем прогрессе и результатах. Те, кто не разбирается в тестировании, в результате плохо понимают, что было сделано, как, и почему. В итоге понять ценность тестирования сложно. В мире множество компаний, разрабатывающих ПО вообще без тестировщиков.
Отсутствие счетного результата, создаваемого тестировщиками – одна из причин, по которой некоторые предпочитают использовать тест-кейсы как способ измерения – их можно легко сосчитать. Но ценность тестирования – это намного больше, чем тест-кейсы. Исследовательское тестирование, возможно, не дает в результате набора четких кейсов, однако тестировщик находит больше интересных багов, отступая от жестких сценариев.
Отчасти поэтому людям нравятся метрики, которые учитывают количество заведенных багов, написанных и пройденных кейсов, и других вещей, которые можно сосчитать. Некоторые проекты используют эти метрики, чтобы измерять качество продукта, а также качество работы разработчиков и тестировщиков. Эти метрики концентрируются на неправильных вещах и могут вас обманывать.
Тестирование ценно на всех стадиях жизненного цикла разработки, не только когда пишется код. Вот что еще можно протестировать:
- Идеи
- Требования
- Дизайн
- Предположения
- Документацию
- Инфраструктуру
- Процессы.
Задача тестировщика – задавать вопросы, исследовать, критически размышлять над этими вещами. В результате то, что могло бы стать багом в процессе разработки, можно изловить гораздо раньше.
Коммуникация
Коммуникация – это огромная часть работы тестировщика. Тестировщики предоставляют информацию о качестве программного продукта, поэтому очень важно передавать эту информацию точно, чтобы заинтересованные лица принимали верные решения.
Человек может начать работать тестировщиком, имея слабые технические навыки, но если он силен в коммуникации и может внятно донести свою мысль – это куда важнее.
Тестировщики должны использовать правильные слова и верно строить фразы, чтобы они не были противоречивыми – так снижается риск недопонимания. То, что вы хотели сказать – необязательно то, что вы в итоге сказали, и часто люди делают допущения и в результате предпринимают неверные действия, потому что коммуникация была плохой или недостаточной.
Нам нужно регулярно общаться с людьми, играющими различные роли, находящимися на разных позициях и обладающих разным объемом знаний о продукте.
- С разработчиками, задавая им вопросы и узнавая больше о продукте, который они создают. Разработчики помогают нам вникать в технические аспекты, и им мы объясняем, что за баги мы нашли и как их воспроизвести.
- С владельцами продукта, чтобы понимать требования, задавать вопросы по сценариям использования и делиться информацией насчет этих сценариев, чтобы они могли принимать решения насчет релизов продукта.
- С тестировщиками. Если вы работаете в команде тестировщиков, очень важно общаться с коллегами, обсуждать с ними проблемы и принимать решения. Возможно, вам придется тренировать новичка или джуниора, и очень важно внятно объяснять им их задачи и помогать, если им приходится нелегко.
- С пользователями и заказчиками, чтобы убедиться, что их ожидания и их проблемы правильно поняты. Если вы помогаете им решить проблему, вы должны быть способны объяснить, как пошагово избавиться от нее, так, чтобы собеседник вас отлично понял.
- С менеджерами, чтобы сообщить, что было проделано и что осталось сделать, чтобы проинформировать их о рисках и их последствиях, а также временных рамках. Если вы предлагаете улучшения, внятно рассказывайте о своих идеях и их влиянии на продукт.
Письменная коммуникация важна не меньше устной. Создать блестяще написанную, обширную документацию, которая никому не нужна, легче легкого. Мы должны убедиться, что используем правильный способ общения в каждом конкретном случае, будь то человек, процесс или проект.
Потенциальная бесконечность
По сути, мы всегда тестируем только выборку. Каждый нетривиальный продукт обладает непредставимым количеством параметров с большим количеством возможных значений. Откуда вы знаете, что тестируете важные значения? Мы не можем протестировать все.
Часть нашей работы – принятие решений о том, что тестировать, понимание последствий того, что будет протестировано только это, и способность обосновать свой выбор.
Из чего тестирование не состоит
Простота
О тестировании часто думают как о чем-то, чем может заниматься любой. Возможно, в какой-то степени это правдиво – любой может исследовать продукт, задавать вопросы о нем, прогнать пошагово тест-кейс или проверить, соответствует ли продукт списку требований. Но чтобы делать это хорошо и систематически, нужен настоящий навык.
Нам часто говорят «пишите кейсы так, чтобы их мог прогнать любой дурак», и из-за этого создается ложное впечатление, что тестировать очень просто. Мы тупо пишем тесты согласно критериям приемки, не так ли? Но тестировщики, тестирующие свободным поиском, знают, что это не так.
Даже проверки – не такое-то простое дело. Мы принимаем непростые решения, где нужны эти проверки, и какие из них следует автоматизировать. Эти решения требуют понимания фреймворков автоматизации, навыка программирования, знания, как работает API, и владения инструментами вроде Selenium. Резюмируя, мы должны разбираться в приличном наборе технологий. Помимо этого, нам нужно знать, что нужно автоматизировать, а к чему автотесты подпускать нельзя.
Автоматизируемость
«Ручные тестировщики нам больше не нужны – мы можем автоматизировать все!» Все мы видели те или иные вариации этой фразы в Твиттере, на форумах и в статьях. Тестирование – это исследовательская, детективная деятельность, и ее невозможно заменить автоматизированными проверками. Компьютер технически не способен исследовать продукт так, как это делает человек.