Модульное тестирование , также известное как компонентное или модульное тестирование, представляет собой форму тестирования программного обеспечения , при которой изолированный исходный код тестируется для проверки ожидаемого поведения. [1]
Модульное тестирование описывает тесты, которые выполняются на уровне модуля в отличие от тестирования на уровне интеграции или системы .
Модульное тестирование как принцип тестирования отдельных меньших частей больших программных систем восходит к ранним дням разработки программного обеспечения. В июне 1956 года HD Benington представил на симпозиуме ВМС США по передовым методам программирования для цифровых компьютеров проект SAGE и его подход на основе спецификации, где за фазой кодирования следовало «тестирование параметров» для проверки подпрограмм компонентов на соответствие их спецификации, за которым следовало «тестирование сборки» для собранных вместе частей. [2] [3]
В 1964 году аналогичный подход был описан для программного обеспечения проекта Mercury , где отдельные модули, разработанные разными программами, проходили «модульные тесты» перед тем, как быть интегрированными вместе. [4] В 1969 году методологии тестирования стали более структурированными, с модульными тестами, компонентными тестами и интеграционными тестами с целью проверки отдельных частей, написанных отдельно, и их постепенной сборки в более крупные блоки. [5] Некоторые публичные стандарты, принятые в конце 60-х годов, такие как MIL-STD-483 [6] и MIL-STD-490, внесли дальнейший вклад в широкое принятие модульного тестирования в крупных проектах.
В те времена модульное тестирование было интерактивным [3] или автоматизированным, [7] с использованием либо закодированных тестов, либо инструментов тестирования захвата и воспроизведения . В 1989 году Кент Бек описал тестовую среду для Smalltalk (позже названную SUnit ) в "Simple Smalltalk Testing: With Patterns". В 1997 году Кент Бек и Эрих Гамма разработали и выпустили JUnit , инфраструктуру модульного тестирования, которая стала популярной среди разработчиков Java . [8] Google приняла автоматизированное тестирование примерно в 2005–2006 годах. [9]
Единица определяется как единичное поведение, демонстрируемое тестируемой системой (SUT), обычно соответствующее требованию. Хотя это может подразумевать, что это функция или модуль (в процедурном программировании ) или метод или класс (в объектно-ориентированном программировании ), это не означает, что функции/методы, модули или классы всегда соответствуют единицам. С точки зрения системных требований важен только периметр системы, поэтому только точки входа во внешне видимое поведение системы определяют единицы. [10]
Модульные тесты можно выполнять вручную или с помощью автоматизированного выполнения тестов. Автоматизированные тесты включают такие преимущества, как: частое выполнение тестов, выполнение тестов без затрат на персонал, а также последовательное и повторяемое тестирование.
Тестирование часто выполняется программистом, который пишет и изменяет тестируемый код. Модульное тестирование можно рассматривать как часть процесса написания кода.
В процессе разработки программист может закодировать в тесте критерии или результаты, которые, как известно, являются хорошими, чтобы проверить правильность модуля.
Во время выполнения теста фреймворки регистрируют тесты, не соответствующие каким-либо критериям, и сообщают о них в сводке.
Для этого наиболее часто используется подход «тест – функция – ожидаемое значение».
Параметризованный тест — это тест, который принимает набор значений, которые можно использовать для запуска теста с несколькими различными входными значениями. Тестовая инфраструктура, которая поддерживает параметризованные тесты, поддерживает способ кодирования наборов параметров и запуска теста с каждым набором.
Использование параметризованных тестов может уменьшить дублирование тестового кода.
Параметризованные тесты поддерживаются TestNG , JUnit , [13] XUnit и NUnit , а также различными тестовыми фреймворками JavaScript. [ необходима ссылка ]
Параметры для модульных тестов могут быть закодированы вручную или в некоторых случаях автоматически сгенерированы тестовой средой. В последние годы была добавлена поддержка для написания более мощных (модульных) тестов, использующих концепцию теорий, тестовых случаев, которые выполняют те же шаги, но используют тестовые данные, сгенерированные во время выполнения, в отличие от обычных параметризованных тестов, которые используют те же шаги выполнения с предопределенными наборами входных данных. [ необходима цитата ]
Иногда в гибкой разработке программного обеспечения модульное тестирование выполняется для каждой пользовательской истории и выполняется во второй половине спринта после завершения сбора требований и разработки. Обычно разработчики или другие члены команды разработчиков, такие как консультанты , пишут пошаговые «тестовые сценарии», которые разработчики выполняют в инструменте. Тестовые сценарии обычно пишутся для подтверждения эффективной и технической работы определенных разработанных функций в инструменте, в отличие от полноценных бизнес-процессов, которые будут взаимодействовать с конечным пользователем , что обычно делается во время приемочного тестирования пользователем . Если тестовый сценарий может быть полностью выполнен от начала до конца без инцидентов, модульный тест считается «пройденным», в противном случае ошибки отмечаются, и пользовательская история возвращается в разработку в состоянии «в процессе». Пользовательские истории, которые успешно проходят модульные тесты, переходят на последние этапы спринта — обзор кода, экспертную оценку и, наконец, сеанс «отчета», демонстрирующий разработанный инструмент заинтересованным сторонам.
В разработке через тестирование (TDD) модульные тесты пишутся во время написания производственного кода. Начиная с рабочего кода, разработчик добавляет тестовый код для требуемого поведения, затем добавляет ровно столько кода, чтобы тест прошел, затем рефакторит код (включая тестовый код) по мере необходимости, а затем повторяет, добавляя еще один тест.
Тестирование модулей призвано гарантировать, что модули соответствуют своему проекту и ведут себя так, как задумано. [14]
Написав тесты сначала для наименьших тестируемых единиц, а затем для сложных поведений между ними, можно создать комплексные тесты для сложных приложений. [14]
Одна из целей модульного тестирования — изолировать каждую часть программы и показать, что отдельные части корректны. [1] Модульный тест представляет собой строгий письменный контракт , которому должен соответствовать фрагмент кода.
Модульное тестирование выявляет проблемы на ранних этапах цикла разработки . Это включает в себя как ошибки в реализации программиста, так и недостатки или отсутствующие части спецификации для модуля. Процесс написания тщательного набора тестов заставляет автора продумывать входы, выходы и условия ошибок, и, таким образом, более четко определять желаемое поведение модуля. [ необходима цитата ]
Стоимость поиска ошибки до начала кодирования или при первом написании кода значительно ниже стоимости обнаружения, идентификации и исправления ошибки позже. Ошибки в выпущенном коде также могут вызвать дорогостоящие проблемы для конечных пользователей программного обеспечения. [15] [16] [17] Код может быть невозможным или сложным для модульного тестирования, если он плохо написан, поэтому модульное тестирование может заставить разработчиков структурировать функции и объекты лучшим образом.
Модульное тестирование позволяет выпускать более частые версии программного обеспечения. Тестируя отдельные компоненты изолированно, разработчики могут быстро выявлять и устранять проблемы, что приводит к более быстрым циклам итераций и релизов. [18]
Модульное тестирование позволяет программисту рефакторить код или обновить системные библиотеки на более позднем этапе и убедиться, что модуль по-прежнему работает правильно (например, при регрессионном тестировании ). Процедура заключается в написании тестовых случаев для всех функций и методов , чтобы всякий раз, когда изменение вызывает ошибку, ее можно было быстро идентифицировать.
Модульные тесты выявляют изменения, которые могут нарушить контракт на проектирование .
Модульное тестирование может снизить неопределенность в самих модулях и может использоваться в подходе снизу вверх по стилю тестирования. Тестирование частей программы сначала, а затем суммы ее частей, значительно упрощает интеграционное тестирование . [ необходима цитата ]
Некоторые программисты утверждают, что модульные тесты предоставляют форму документации кода. Разработчики, желающие узнать, какие функциональные возможности предоставляет модуль и как его использовать, могут просмотреть модульные тесты, чтобы получить представление о нем. [ необходима цитата ]
Тестовые случаи могут воплощать характеристики, которые имеют решающее значение для успеха блока. Эти характеристики могут указывать на надлежащее/ненадлежащее использование блока, а также на негативное поведение, которое должно быть отловлено блоком. Тестовый случай документирует эти критические характеристики, хотя многие среды разработки программного обеспечения не полагаются исключительно на код для документирования продукта в разработке. [ необходима цитата ]
В некоторых процессах акт написания тестов и тестируемого кода, а также связанный с ним рефакторинг, могут занять место формального проектирования. Каждый модульный тест можно рассматривать как элемент проектирования, определяющий классы, методы и наблюдаемое поведение. [ необходима цитата ]
Тестирование не выявит все ошибки в программе, поскольку оно не может оценить каждый путь выполнения в любой, кроме самых тривиальных программах. Эта проблема является надмножеством проблемы остановки , которая неразрешима . То же самое верно и для модульного тестирования. Кроме того, модульное тестирование по определению проверяет только функциональность самих модулей. Поэтому оно не выявит ошибки интеграции или более широкие ошибки системного уровня (например, функции, выполняемые в нескольких модулях, или нефункциональные области тестирования, такие как производительность ). Модульное тестирование должно проводиться совместно с другими мероприятиями по тестированию программного обеспечения , поскольку они могут показать только наличие или отсутствие определенных ошибок; они не могут доказать полное отсутствие ошибок. Чтобы гарантировать правильное поведение для каждого пути выполнения и каждого возможного ввода, а также обеспечить отсутствие ошибок, требуются другие методы, а именно применение формальных методов для доказательства того, что программный компонент не имеет неожиданного поведения. [ необходима цитата ]
Сложная иерархия модульных тестов не равна интеграционному тестированию. Интеграция с периферийными устройствами должна быть включена в интеграционные тесты, но не в модульные тесты. [ требуется ссылка ] Интеграционное тестирование обычно по-прежнему в значительной степени зависит от людей , тестирующих вручную ; высокоуровневое или глобальное тестирование может быть трудно автоматизировать, так что ручное тестирование часто кажется более быстрым и дешевым. [ требуется ссылка ]
Тестирование программного обеспечения — это комбинаторная задача. Например, каждое утверждение о принятии решения с булевым значением требует как минимум двух тестов: один с результатом «истина» и один с результатом «ложь». В результате для каждой написанной строки кода программистам часто требуется от 3 до 5 строк тестового кода. [ требуется ссылка ] Очевидно, что это требует времени, и его вложения могут не стоить усилий. Существуют проблемы, которые вообще невозможно легко протестировать — например, те, которые являются недетерминированными или включают несколько потоков . Кроме того, код для модульного теста с такой же вероятностью будет содержать ошибки, как и код, который он тестирует. Фред Брукс в своей книге «Мифический человеко-месяц» цитирует: «Никогда не выходите в море с двумя хронометрами; возьмите один или три». [19] То есть, если два хронометра противоречат друг другу, как узнать, какой из них правильный?
Еще одной проблемой, связанной с написанием модульных тестов, является сложность настройки реалистичных и полезных тестов. Необходимо создать соответствующие начальные условия, чтобы часть тестируемого приложения вела себя как часть полной системы. Если эти начальные условия не установлены правильно, тест не будет тренировать код в реалистичном контексте, что снижает ценность и точность результатов модульного теста. [ необходима цитата ]
Чтобы получить ожидаемые преимущества от модульного тестирования, необходима строгая дисциплина на протяжении всего процесса разработки программного обеспечения.
Важно вести тщательные записи не только о проведенных тестах, но и обо всех изменениях, внесенных в исходный код этого или любого другого модуля в программном обеспечении. Использование системы контроля версий имеет важное значение. Если более поздняя версия модуля не проходит определенный тест, который она ранее прошла, программное обеспечение контроля версий может предоставить список изменений исходного кода (если таковые имеются), которые были применены к модулю с того времени. [ необходима цитата ]
Также важно внедрить устойчивый процесс, гарантирующий регулярный просмотр и немедленное устранение сбоев тестовых случаев. [20] Если такой процесс не будет внедрен и не будет внедрен в рабочий процесс команды, приложение будет развиваться несинхронно с набором модульных тестов, что приведет к увеличению количества ложных срабатываний и снижению эффективности набора тестов.
Модульное тестирование встроенного системного программного обеспечения представляет собой уникальную проблему: поскольку программное обеспечение разрабатывается на платформе, отличной от той, на которой оно в конечном итоге будет работать, вы не можете легко запустить тестовую программу в реальной среде развертывания, как это возможно с настольными программами. [21]
Модульные тесты, как правило, проще всего проводить, когда у метода есть входные параметры и некоторые выходные данные. Не так просто создавать модульные тесты, когда основная функция метода — взаимодействовать с чем-то внешним по отношению к приложению. Например, метод, который будет работать с базой данных, может потребовать создания макета взаимодействия с базой данных, который, вероятно, не будет таким же полным, как реальное взаимодействие с базой данных. [22] [ требуется лучший источник ]
Ниже приведен пример набора тестов JUnit. Он фокусируется на Adder
классе.
класс Adder { public int add ( int a , int b ) { return a + b ; } }
Тестовый набор использует операторы assert для проверки ожидаемого результата различных входных значений метода sum
.
import static org.junit.Assert.assertEquals ; import org.junit.Test ; public class AdderUnitTest { @Test public void sumReturnsZeroForZeroInput () { Adder adder = new Adder (); assertEquals ( 0 , adder.add ( 0 , 0 ) ); } @Test public void sumReturnsSumOfTwoPositiveNumbers ( ) { Сумматор сумматор = новый Сумматор (); assertEquals ( 3 , сумматор.add ( 1 , 2 ) ); } @Test public void sumReturnsSumOfTwoNegativeNumbers ( ) { Сумматор сумматор = новый Сумматор ( ) ; assertEquals ( -3 , сумматор.add ( -1 , -2 ) ) ; } @Test public void sumReturnsSumOfLargeNumbers ( ) { Adder adder = new Adder (); assertEquals ( 2222 , adder.add ( 1234 , 988 ) ); } }
Использование юнит-тестов в качестве спецификации дизайна имеет одно существенное преимущество перед другими методами дизайна: проектный документ (сами юнит-тесты) можно использовать для проверки реализации. Тесты никогда не пройдут, если разработчик не реализует решение в соответствии с дизайном.
Модульное тестирование не обладает некоторой доступностью диаграммной спецификации, такой как диаграмма UML , но они могут быть сгенерированы из модульного теста с использованием автоматизированных инструментов. Большинство современных языков имеют бесплатные инструменты (обычно доступные как расширения для IDE ). Бесплатные инструменты, такие как основанные на фреймворке xUnit , передают на аутсорсинг другой системе графическую визуализацию представления для человеческого потребления.
Модульное тестирование является краеугольным камнем экстремального программирования , которое опирается на автоматизированную структуру модульного тестирования . Эта автоматизированная структура модульного тестирования может быть как сторонней, например, xUnit , так и созданной внутри группы разработки.
Экстремальное программирование использует создание модульных тестов для разработки через тестирование . Разработчик пишет модульный тест, который выявляет либо требование к программному обеспечению, либо дефект. Этот тест не будет пройден, потому что либо требование еще не реализовано, либо намеренно выявляет дефект в существующем коде. Затем разработчик пишет простейший код, чтобы тест и другие тесты прошли.
Большая часть кода в системе проходит модульное тестирование, но не обязательно все пути через код. Экстремальное программирование предписывает стратегию «тестировать все, что может сломаться», а не традиционный метод «тестировать каждый путь выполнения». Это заставляет разработчиков разрабатывать меньше тестов, чем классические методы, но это не проблема, а скорее переформулировка факта, поскольку классические методы редко когда-либо соблюдались достаточно методично для того, чтобы все пути выполнения были тщательно протестированы. [ необходима цитата ] Экстремальное программирование просто признает, что тестирование редко бывает исчерпывающим (потому что оно часто слишком дорого и требует много времени, чтобы быть экономически выгодным), и дает рекомендации о том, как эффективно сосредоточить ограниченные ресурсы.
Важно то, что тестовый код считается первоклассным артефактом проекта, поскольку он поддерживается на том же уровне качества, что и код реализации, при этом все дублирование удалено. Разработчики выпускают код модульного тестирования в репозиторий кода вместе с кодом, который он тестирует. Тщательное модульное тестирование экстремального программирования обеспечивает вышеупомянутые преимущества, такие как более простая и надежная разработка и рефакторинг кода , упрощенная интеграция кода, точная документация и более модульные конструкции. Эти модульные тесты также постоянно запускаются как форма регрессионного тестирования .
Модульное тестирование также имеет решающее значение для концепции Emergent Design . Поскольку Emergent Design в значительной степени зависит от рефакторинга, модульные тесты являются его неотъемлемым компонентом. [ необходима цитата ]
Автоматизированная инфраструктура тестирования предоставляет функции для автоматизации выполнения тестов и может ускорить написание и запуск тестов. Фреймворки были разработаны для широкого спектра языков программирования .
Как правило, фреймворки являются сторонними и не распространяются вместе с компилятором или интегрированной средой разработки (IDE).
Тесты можно писать без использования фреймворка для проверки тестируемого кода с использованием утверждений , обработки исключений и других механизмов потока управления для проверки поведения и сообщения об ошибках. Некоторые отмечают, что тестирование без фреймворка ценно, поскольку существует барьер для входа для принятия фреймворка; что наличие некоторых тестов лучше, чем никаких, но как только фреймворк будет готов, добавлять тесты может быть проще. [23]
В некоторых фреймворках отсутствуют расширенные функции тестирования, и их необходимо кодировать вручную.
Некоторые языки программирования напрямую поддерживают модульное тестирование. Их грамматика позволяет напрямую объявлять модульные тесты без импорта библиотеки (сторонней или стандартной). Кроме того, булевы условия модульных тестов могут быть выражены в том же синтаксисе, что и булевы выражения, используемые в коде немодульного тестирования, например, what is used for if
и while
операторы.
Языки со встроенной поддержкой модульного тестирования включают:
Языки со стандартной поддержкой фреймворка модульного тестирования включают:
Некоторые языки не имеют встроенной поддержки модульного тестирования, но имеют установленные библиотеки или фреймворки модульного тестирования. Эти языки включают:
Затем подрядчик должен закодировать и протестировать программные модули и ввести исходный и объектный код, а также соответствующие списки каждого успешно протестированного модуля в Конфигурацию разработки
{{cite book}}
: CS1 maint: date and year (link)