Модульное тестирование
Модульное тестирование, иногда блочное тестирование или юнит-тестирование (англ. unit testing) — процесс в программировании, позволяющий проверить на корректность отдельные модули исходного кода программы, наборы из одного или более программных модулей вместе с соответствующими управляющими данными, процедурами использования и обработки.
Идея состоит в том, чтобы писать тесты для каждой нетривиальной функции или метода. Это позволяет достаточно быстро проверить, не привело ли очередное изменение кода к регрессии, то есть к появлению ошибок в уже оттестированных местах программы, а также облегчает обнаружение и устранение таких ошибок. Например, обновить используемую в проекте библиотеку до актуальной версии можно в любой момент, прогнав тесты и выявив несовместимости.
Преимущества[править | править код]
Цель модульного тестирования — изолировать отдельные части программы и показать, что по отдельности эти части работоспособны.
Этот тип тестирования обычно выполняется программистами.
Поощрение изменений[править | править код]
Модульное тестирование позже позволяет программистам проводить рефакторинг, будучи уверенными, что модуль по-прежнему работает корректно (регрессионное тестирование). Это поощряет программистов к изменениям кода, поскольку достаточно легко проверить, что код работает и после изменений.
Упрощение интеграции[править | править код]
Модульное тестирование помогает устранить сомнения по поводу отдельных модулей и может быть использовано для подхода к тестированию «снизу вверх»: сначала тестируя отдельные части программы, а затем программу в целом.
Документирование кода[править | править код]
Модульные тесты можно рассматривать как «живой документ» для тестируемого класса. Клиенты, которые не знают, как использовать данный класс, могут использовать юнит-тест в качестве примера.
Отделение интерфейса от реализации[править | править код]
Поскольку некоторые классы могут использовать другие классы, тестирование отдельного класса часто распространяется на связанные с ним. Например, класс пользуется базой данных; в ходе написания теста программист обнаруживает, что тесту приходится взаимодействовать с базой. Это ошибка, поскольку тест не должен выходить за границу класса. В результате разработчик абстрагируется от соединения с базой данных и реализует этот интерфейс, используя свой собственный mock-объект. Это приводит к менее связанному коду, минимизируя зависимости в системе.
Когда модульное тестирование не работает[править | править код]
Сложный код[править | править код]
Тестирование программного обеспечения — комбинаторная задача. Например, каждое возможное значение булевой переменной потребует двух тестов: один на вариант TRUE, другой — на вариант FALSE. В результате на каждую строку исходного кода потребуется 3−5 строк тестового кода.
Алгоритмы вроде Marching cubes или красно-чёрного дерева имеют разветвлённое дерево решений, и чтобы проверить все варианты, нужны огромные наборы тестов: в одной из реализаций красно-чёрного дерева с GitHub на проверку вставки сделано двенадцать тестов[1]. В другой — автоматически строят 10! = 3,6 млн перестановок и испытывают их все[2].
Как и любая технология тестирования, модульное тестирование не позволяет отловить все ошибки программы. В самом деле, это следует из практической невозможности трассировки всех возможных путей выполнения программы, за исключением простейших случаев.
Результат известен лишь приблизительно[править | править код]
Например, в математическом моделировании. Бизнес-приложения зачастую работают с конечными и счётными множествами, научные — с континуальными.[3] Поэтому сложно подобрать тесты для каждой из ветвей программы, сложно сказать, верен ли результат, выдерживается ли точность, и т. д. А во многих случаях качество моделирования определяется «на глаз», и последний результат записывается как «опорный». Если найдено расхождение, новый результат проверяют вручную и выясняют, какой качественнее: старый или новый.
Код, взаимодействующий с системой[править | править код]
Код, взаимодействующий с портами, таймерами, пользователем и прочими «нестабильными» частями системы, крайне сложно проверить в изолированном окружении.
Но это не значит, что модульное тестирование здесь полностью непригодно: оно вынуждает программиста перейти от файлов и портов, например, на абстрактные потоки. Это делает код более общим (например, без проблем можно перейти с файлов на сетевые сокеты), более тестируемым (можно проверить ситуацию «пропала связь», написав поток, который, выдав N байт, смоделирует аварию; проверить под Windows часть функций преобразования путей Unix), ограничивает те части, которые не подлежат модульному тестированию.
Многопоточность[править | править код]
Это в принципе нестабильная часть системы[4], и если при какой-то редкой последовательности событий случается взаимная блокировка, очень сложно сделать даже код, повторяющий её с некоторой вероятностью, не говоря уже о надёжном повторении. К тому же модульные тесты обычно просты, а тесты для многопоточных систем, наоборот, должны быть достаточно велики.
Обычно модульные тесты многократно повторяют тестовый сценарий, рассчитывая, что ошибка рано или поздно выплывет[4].
Ошибки интеграции и производительности[править | править код]
При выполнении юнит-тестов происходит тестирование каждого из модулей по отдельности. Это означает, что ошибки интеграции, системного уровня, функций, исполняемых в нескольких модулях, не будут определены. Кроме того, данная технология бесполезна для проведения тестов на производительность. Таким образом, модульное тестирование более эффективно при использовании в сочетании с другими методиками тестирования.
При общей низкой культуре программирования[править | править код]
Для получения выгоды от модульного тестирования требуется строго следовать технологии тестирования на всём протяжении процесса разработки программного обеспечения. Нужно хранить не только записи обо всех проведённых тестах, но и обо всех изменениях исходного кода во всех модулях. С этой целью следует использовать систему контроля версий ПО. Таким образом, если более поздняя версия ПО не проходит тест, который был успешно пройден ранее, будет несложным сверить варианты исходного кода и устранить ошибку. Также необходимо убедиться в неизменном отслеживании и анализе неудачных тестов. Игнорирование этого требования приведёт к лавинообразному увеличению неудачных тестовых результатов.
Проблемы с объектами-заглушками[править | править код]
За исключением простейших случаев, тестируемый объект должен взаимодействовать с другими объектами. Этих «товарищей по взаимодействию» — объекты-заглушки — делают предельно простыми: либо крайне упрощёнными (память вместо базы данных), либо рассчитанными на конкретный тест и механически повторяющими сессию обмена. Проблемы могут возникать при смене протокола обмена, в таком случае объекты-заглушки должны отвечать новым требованиям протокола.[5]
Разработка встраиваемого ПО[править | править код]
Легко убедиться, что модуль работает на машине разработчика. Сложнее — что на целевой машине, зачастую сильно ограниченной[6].
Приложения модульного тестирования[править | править код]
Экстремальное программирование[править | править код]
Экстремальное программирование предполагает как один из постулатов использование инструментов автоматического модульного тестирования. Этот инструментарий может быть создан либо третьей стороной (например, Boost.Test), либо группой разработчиков данного приложения.
В экстремальном программировании используются модульные тесты для разработки через тестирование. Для этого разработчик до написания кода пишет тест, отражающий требования к модулю. Очевидно, тест до написания кода работать не должен. Дальнейший процесс сводится к написанию кратчайшего кода, удовлетворяющего данному тесту. После разработчик пишет следующий тест, код и так многократно.
Техника модульного тестирования[править | править код]
Сложность написания модульных тестов зависит от самой организации кода. Сильное зацепление или большая зона ответственности отдельных сущностей (классы для объектно-ориентированных языков) могут усложнить тестирование. Для объектов осуществляющих связь с внешним миром (сетевое взаимодействие, файловый ввод-вывод и т. д.) следует создавать заглушки. В терминологии выделяют более «продвинутые» заглушки — Mock-объекты, которые несут в себе логику. Также упростить тестирование может выделение как можно большей части логики в чистые функции. Они никак не взаимодействуют с внешним миром и их результат зависит только от входных параметров.
Код тестов принято выделять в отдельные каталоги. Желательно, чтобы добавление новых тестов в проекте не было сложной задачей и была возможность запускать все тесты. Некоторые системы контроля версий, например git, поддерживают хуки (англ. hook), с помощью которых можно настроить запуск всех тестов перед фиксированием изменений. При ошибке в хотя бы одном из тестов, изменения зафиксированы не будут. Также можно применять системы непрерывной интеграции.
Инструментарий[править | править код]
Список примеров в этой статье не основывается на авторитетных источниках, посвящённых непосредственно предмету статьи. |
Для большинства популярных языков программирования высокого уровня существуют инструменты и библиотеки модульного тестирования. Некоторые из них:
- Для ActionScript 2.0 — язык сценариев, используемый виртуальной машиной Adobe Flash Player версии 7 и 8
- Для ActionScript 3.0 — скриптовый язык, используемый виртуальной машиной Adobe Flash Player версии 9 и выше
- Для C
- CUnit cunit
- CTESK UniTESK.ru
- cfix cfix
- API Sanity Autotest — для динамических C/C++ библиотек в Unix-подобных ОС.
- Unity unity — для встраиваемых приложений
- MICRO UNIT MICRO_UNIT — небольшой набор макросов с примерами использования.
- Для Ruby
- Для Objective-C
- OCUnit [7]
- Для C++
- TypeMock Isolator++ за C++ и Isolator за .NET [8]
- CxxTest [9]
- CPPUnit [10]
- Boost Test [11]
- Google C++ Testing Framework [12]
- Symbian[13] — фреймворк для Symbian OS всех версий.
- API Sanity Autotest — для динамических C/C++ библиотек в Unix-подобных ОС.
- Qt Test framework — для программ, разработанных с помощью библиотеки Qt
- Для C#
- Для Delphi
- EUnit [20] — Erlang
- Для Java и Groovy
- JUnit JUnit.org
- TestNG testNG.org
- JavaTESK UniTESK.ru
- Spock (написан на Groovy)
- Для JavaScript
- Mocha (тестовый фреймворк) [21]
- Jest [22]
- Chai («assertion library», используется совместно с тестовым framework’ом) [23]
- Sinon.JS (библиотека для создания mock’ов, stub’ов, spy’ев, используется совместно с тестовым framework’ом) [24]
- Karma runner (от создателей Angular.JS, «test runner» — организует среду выполнения тестов) [25]
- QUnit (от создателей jQuery) [26]
- JsUnit (больше не поддерживается создателями) [27]
- Jasmine (рекомендован создателями jsUnit) [28]
- D.O.H [29]
- Для Perl
- Для PHP
- SimpleTest
- PHPUnit [35]
- Для Python
- vbUnit [39] — Visual Basic
- utPLSQL [40] — PL/SQL
- Для T-SQL
Поддержка на уровне языка[править | править код]
Некоторые языки имеют поддержку модульного тестирования на уровне синтаксиса. Это избавляет от необходимости выбирать, к какому фреймворку привязываться, и позволяет упростить перенос кода в другие проекты.
Пример таких языков:
Пример кода на языке D
class ABC
{
this() { val = 2; }
private int val;
public func() { val *= 2; }
}
unittest
{
ABC a;
a.func();
assert( a.val > 0 && a.val < 555 ); // можно обратиться к приватной переменной внутри модуля
}
Пример кода на языке Pyret
fun my-max(lst):
doc: ```Находит максимальное число в непустом списке.
Если список пуст, вызывает исключение.```
cases (List) lst:
| empty => raise("List is empty")
| link(fst, rst) =>
cases (List) rst:
| empty => fst
| link(snd, rst-of-rst) => num-max(fst, my-max(rst))
end
end
where:
my-max(empty) raises "List is empty"
my-max([list: 1]) is 1
my-max([list: -100]) is -100
my-max([list: 1, -100]) is 1
my-max([list: 1, 2, 3, 4, 3, 2, 1]) is 4
end
Примечания[править | править код]
- ↑ GitHub - xieqing/red-black-tree: A Red-black Tree Implementation In C . Дата обращения: 14 апреля 2022. Архивировано 14 апреля 2022 года.
- ↑ http://orion.lcg.ufrj.br/java/bigjava/ch17/worked_example_2/RedBlackTreeTester.java
- ↑ Почему юнит-тесты не работают в научных приложениях / Хабрахабр . Дата обращения: 9 июня 2014. Архивировано 14 июля 2014 года.
- ↑ 1 2 Источник . Дата обращения: 10 мая 2023. Архивировано 21 марта 2023 года.
- ↑ Проблема дублирования и устаревания знания в mock-объектах или Интеграционные тесты — это хорошо / Хабрахабр . Дата обращения: 19 января 2016. Архивировано 19 января 2016 года.
- ↑ Marek Kucharski Making Unit Testing Practical for Embedded Development Архивная копия от 25 мая 2022 на Wayback Machine
См. также[править | править код]
- Тестирование программного обеспечения
- Автоматическое тестирование
- Интеграционное тестирование
- Системное тестирование
Литература[править | править код]
- Ошероув, Р. Искусство автономного тестирования с примерами на C# = The Art Of Unit Testing Second Edition With Examples In C#. – ДМК Пресс, 2016. – ISBN 978-5-97060-415-1.
Ссылки[править | править код]
- Сайты и ресурсы
- Тестирование программного обеспечения: модульные тесты — коллекция статей на сайте OpenQuality.ru (рус.)
- The Art Of Unit Testing (англ.)
- Статьи
- Модульное тестирование: 2+2 = 4? (рус.)
- Модульное тестирование. Зачем, как и кто (рус.)
- The evolution of Unit Testing Syntax and Semantics (англ.)
- Unit Testing Guidelines from GeoSoft (англ.)
Для улучшения этой статьи по информационным технологиям желательно:
|