Модульное тестирование - Unit testing

В компьютерном программировании , модульное тестирование является тестирование программного обеспечения метод , с помощью которого отдельные единицы исходного кода -множеств одного или нескольких компьютерных программных модулей вместе с соответствующими данными управления, использования процедур и операционных процедур, тестируют , чтобы определить , являются ли они пригодны для использования .

Описание

Модульные тесты обычно представляют собой автоматизированные тесты, написанные и выполняемые разработчиками программного обеспечения, чтобы гарантировать, что часть приложения (известная как «модуль») соответствует своему дизайну и ведет себя должным образом. В процедурном программировании единицей может быть целый модуль, но чаще это отдельная функция или процедура. В объектно-ориентированном программировании единицей часто является весь интерфейс, например класс или отдельный метод. Написав тесты сначала для самых маленьких тестируемых модулей, а затем составного поведения между ними, можно создать комплексные тесты для сложных приложений.

Чтобы изолировать проблемы, которые могут возникнуть, каждый тестовый пример следует тестировать независимо. Заменители, такие как заглушки методов , имитирующие объекты , подделки и тестовые жгуты, могут использоваться для помощи в тестировании модуля изолированно.

Во время разработки разработчик программного обеспечения может закодировать критерии или заведомо хорошие результаты в тест, чтобы проверить правильность модуля. Во время выполнения тестового примера платформы регистрируют тесты, которые не соответствуют ни одному критерию, и сообщают о них в виде сводки. Для этого наиболее часто используется подход «проверка - функция - ожидаемое значение».

Написание и сопровождение модульных тестов можно ускорить с помощью параметризованных тестов . Это позволяет выполнять один тест несколько раз с разными наборами входных данных, что сокращает дублирование кода теста. В отличие от традиционных модульных тестов, которые обычно представляют собой закрытые методы и инвариантные условия тестирования, параметризованные тесты принимают любой набор параметров. Параметризованные тесты поддерживаются TestNG , JUnit и его аналогом .Net, XUnit . Подходящие параметры для модульных тестов могут быть предоставлены вручную или в некоторых случаях автоматически генерируются платформой тестирования. В последние годы была добавлена ​​поддержка для написания более мощных (модульных) тестов, использующих концепцию теорий, тестовых примеров, которые выполняют одни и те же шаги, но с использованием тестовых данных, сгенерированных во время выполнения, в отличие от обычных параметризованных тестов, которые используют те же шаги выполнения с входными наборами. которые предопределены.

Преимущества

Цель модульного тестирования - изолировать каждую часть программы и показать, что отдельные части верны. Модульный тест предоставляет строгий письменный контракт, которому должен удовлетворять фрагмент кода. В результате он дает несколько преимуществ.

Модульное тестирование обнаруживает проблемы на ранних этапах цикла разработки . Сюда входят как ошибки в реализации программиста, так и недостатки или отсутствующие части спецификации модуля. Процесс написания тщательного набора тестов вынуждает автора продумывать входные и выходные данные и условия ошибок и, таким образом, более четко определять желаемое поведение устройства. Стоимость обнаружения ошибки до начала кодирования или при первом написании кода значительно ниже, чем затраты на обнаружение, идентификацию и исправление ошибки позже. Ошибки в выпущенном коде также могут вызвать дорогостоящие проблемы для конечных пользователей программного обеспечения. Код может оказаться невозможным или сложным для модульного тестирования, если он плохо написан, поэтому модульное тестирование может заставить разработчиков лучше структурировать функции и объекты.

В разработке через тестирование (TDD), которая часто используется как в экстремальном программировании, так и в схватке , модульные тесты создаются до написания самого кода. Когда тесты проходят, этот код считается завершенным. Для этой функции часто запускаются одни и те же модульные тесты, поскольку большая база кода разрабатывается либо по мере изменения кода, либо с помощью автоматизированного процесса сборки. Если модульные тесты не проходят, это считается ошибкой либо в измененном коде, либо в самих тестах. Затем модульные тесты позволяют легко отследить место неисправности или отказа. Поскольку модульные тесты предупреждают группу разработчиков о проблеме до передачи кода тестировщикам или клиентам, потенциальные проблемы выявляются на ранних этапах процесса разработки.

Модульное тестирование позволяет программисту позднее реорганизовать код или обновить системные библиотеки и убедиться, что модуль по-прежнему работает правильно (например, при регрессионном тестировании ). Процедура состоит в том, чтобы написать тестовые примеры для всех функций и методов, чтобы каждый раз, когда изменение вызывает ошибку, ее можно было быстро идентифицировать. Модульные тесты обнаруживают изменения, которые могут нарушить контракт на разработку .

Модульное тестирование может уменьшить неопределенность самих модулей и может использоваться в подходе в стиле восходящего тестирования. Если сначала протестировать части программы, а затем проверить их сумму, интеграционное тестирование становится намного проще.

Модульное тестирование предоставляет своего рода живую документацию системы. Разработчики, которые хотят узнать, какие функции предоставляет модуль и как его использовать, могут взглянуть на модульные тесты, чтобы получить базовое представление об интерфейсе модуля ( API ).

Сценарии модульного тестирования включают характеристики, которые имеют решающее значение для успеха модуля. Эти характеристики могут указывать на уместное / несоответствующее использование отряда, а также на негативное поведение, которое должно быть уловлено этим отрядом. Сам по себе пример модульного тестирования документирует эти критические характеристики, хотя многие среды разработки программного обеспечения не полагаются исключительно на код для документирования разрабатываемого продукта.

Когда программное обеспечение разрабатывается с использованием подхода, основанного на тестировании, сочетание написания модульного теста для определения интерфейса и действий по рефакторингу, выполняемых после прохождения теста, может занять место формального проектирования. Каждый модульный тест можно рассматривать как элемент дизайна, определяющий классы, методы и наблюдаемое поведение.

Ограничения и недостатки

Тестирование не выявит каждую ошибку в программе, потому что оно не может оценить каждый путь выполнения в любой, кроме самых тривиальных программ. Эта проблема является надмножеством проблемы остановки , которая неразрешима . То же верно и для модульного тестирования. Кроме того, модульное тестирование по определению проверяет только функциональность самих модулей. Следовательно, он не будет обнаруживать ошибки интеграции или более широкие ошибки системного уровня (например, функции, выполняемые несколькими модулями, или нефункциональные области тестирования, такие как производительность ). Модульное тестирование следует проводить вместе с другими действиями по тестированию программного обеспечения , поскольку они могут показать только наличие или отсутствие определенных ошибок; они не могут доказать полное отсутствие ошибок. Чтобы гарантировать правильное поведение для каждого пути выполнения и всех возможных входных данных, а также гарантировать отсутствие ошибок, требуются другие методы, а именно применение формальных методов для доказательства того, что программный компонент не имеет неожиданного поведения.

Сложная иерархия модульных тестов не означает интеграционное тестирование. Интеграцию с периферийными устройствами следует включать в интеграционные тесты, но не в модульные тесты. Интеграционное тестирование обычно по-прежнему в значительной степени зависит от ручного тестирования людьми ; Высокоуровневое или глобальное тестирование может быть сложно автоматизировать, поэтому ручное тестирование часто оказывается быстрее и дешевле.

Тестирование программного обеспечения - это комбинаторная проблема. Например, для каждого логического оператора решения требуется как минимум два теста: один с результатом «истина», а другой - с результатом «ложь». В результате для каждой написанной строки кода программистам часто требуется от 3 до 5 строк тестового кода. Это, очевидно, требует времени, и его вложения могут не окупиться. Есть проблемы, которые вообще нелегко протестировать - например, недетерминированные или связанные с несколькими потоками . Кроме того, код для модульного теста, вероятно, будет содержать не меньше ошибок, чем код, который он тестирует. Фред Брукс в «Мифическом человеко-месяце» цитирует: «Никогда не выходите в море с двумя хронометрами; возьмите один или три». То есть, если два хронометра противоречат друг другу, как узнать, какой из них правильный?

Еще одна проблема, связанная с написанием модульных тестов, - это сложность создания реалистичных и полезных тестов. Необходимо создать соответствующие начальные условия, чтобы тестируемая часть приложения вела себя как часть всей системы. Если эти начальные условия не установлены правильно, тест не будет выполнять код в реалистичном контексте, что снижает ценность и точность результатов модульного тестирования.

Чтобы получить ожидаемые выгоды от модульного тестирования, требуется строгая дисциплина на протяжении всего процесса разработки программного обеспечения. Важно вести тщательный учет не только проведенных тестов, но и всех изменений, которые были внесены в исходный код этого или любого другого модуля в программном обеспечении. Важно использовать систему контроля версий. Если более поздняя версия модуля не проходит определенный тест, который он ранее прошел, программное обеспечение контроля версий может предоставить список изменений исходного кода (если таковые имеются), которые были применены к модулю с того времени.

Также важно внедрить устойчивый процесс для обеспечения того, чтобы сбои тестовых примеров регулярно проверялись и немедленно устранялись. Если такой процесс не реализован и не укоренился в рабочем процессе группы, приложение будет развиваться вне синхронизации с набором модульных тестов, увеличивая количество ложных срабатываний и снижая эффективность набора тестов.

Модульное тестирование встроенного системного программного обеспечения представляет собой уникальную проблему: поскольку программное обеспечение разрабатывается на платформе, отличной от той, на которой оно в конечном итоге будет работать, вы не можете легко запустить тестовую программу в реальной среде развертывания, как это возможно с настольными программами.

Модульные тесты обычно оказываются наиболее простыми, когда метод имеет входные параметры и некоторые выходные данные. Не так просто создавать модульные тесты, когда основной функцией метода является взаимодействие с чем-то внешним по отношению к приложению. Например, метод, который будет работать с базой данных, может потребовать создания макета взаимодействий с базой данных, который, вероятно, не будет таким всеобъемлющим, как реальные взаимодействия с базой данных.

Пример

Вот набор тестовых примеров на Java, которые определяют ряд элементов реализации. Во-первых, должен быть интерфейс с именем Adder и класс реализации с конструктором без аргументов с именем AdderImpl. Далее утверждается, что интерфейс Adder должен иметь метод с именем add с двумя целочисленными параметрами, который возвращает другое целое число. Он также определяет поведение этого метода для небольшого диапазона значений в нескольких тестовых методах.

import static org.junit.Assert.assertEquals;

import org.junit.Test;

public class TestAdder {

    @Test
    public void testSumPositiveNumbersOneAndOne() {
        Adder adder = new AdderImpl();
        assertEquals(2, adder.add(1, 1));
    }

    // can it add the positive numbers 1 and 2?
    @Test
    public void testSumPositiveNumbersOneAndTwo() {
        Adder adder = new AdderImpl();
        assertEquals(3, adder.add(1, 2));
    }

    // can it add the positive numbers 2 and 2?
    @Test
    public void testSumPositiveNumbersTwoAndTwo() {
        Adder adder = new AdderImpl();
        assertEquals(4, adder.add(2, 2));
    }

    // is zero neutral?
    @Test
    public void testSumZeroNeutral() {
        Adder adder = new AdderImpl();
        assertEquals(0, adder.add(0, 0));
    }

    // can it add the negative numbers -1 and -2?
    @Test
    public void testSumNegativeNumbers() {
        Adder adder = new AdderImpl();
        assertEquals(-3, adder.add(-1, -2));
    }

    // can it add a positive and a negative?
    @Test
    public void testSumPositiveAndNegative() {
        Adder adder = new AdderImpl();
        assertEquals(0, adder.add(-1, 1));
    }

    // how about larger numbers?
    @Test
    public void testSumLargeNumbers() {
        Adder adder = new AdderImpl();
        assertEquals(2222, adder.add(1234, 988));
    }
}

В этом случае модульные тесты, написанные первыми, действуют как проектный документ, определяющий форму и поведение желаемого решения, но не детали реализации, которые остаются на усмотрение программиста. Следуя практике «делай самое простое, что могло бы сработать», ниже показано самое простое решение, которое позволит пройти тест.

interface Adder {
    int add(int a, int b);
}
class AdderImpl implements Adder {
    public int add(int a, int b) {
        return a + b;
    }
}

Как исполняемые спецификации

Использование модульных тестов в качестве проектной спецификации имеет одно существенное преимущество перед другими методами проектирования: проектный документ (сами модульные тесты) можно использовать для проверки реализации. Тесты никогда не пройдут, если разработчик не реализует решение в соответствии с проектом.

Модульному тестированию не хватает некоторых возможностей схематической спецификации, такой как диаграмма UML , но они могут быть сгенерированы из модульного теста с использованием автоматизированных инструментов. Большинство современных языков имеют бесплатные инструменты (обычно доступные как расширения для IDE ). Бесплатные инструменты, такие как инструменты, основанные на платформе xUnit , передают на аутсорсинг другой системе графическую визуализацию представления для потребления человеком.

Приложения

Экстремальное программирование

Модульное тестирование является краеугольным камнем экстремального программирования , в основе которого лежит автоматизированная среда модульного тестирования . Эта среда автоматизированного модульного тестирования может быть сторонней, например xUnit , или создана в рамках группы разработчиков.

Экстремальное программирование использует создание модульных тестов для разработки через тестирование . Разработчик пишет модульный тест, который выявляет требования к программному обеспечению или дефект. Этот тест завершится неудачно, потому что либо требование еще не реализовано, либо намеренно обнаруживает дефект в существующем коде. Затем разработчик пишет простейший код, позволяющий пройти тест вместе с другими тестами.

Большая часть кода в системе проходит модульное тестирование, но не обязательно все пути прохождения кода. Экстремальное программирование требует стратегии «тестировать все, что может сломаться», а не традиционного метода «тестировать каждый путь выполнения». Это заставляет разработчиков разрабатывать меньше тестов, чем классические методы, но на самом деле это не проблема, а скорее повторение фактов, поскольку классические методы редко когда-либо применялись достаточно методично, чтобы все пути выполнения были тщательно протестированы. Экстремальное программирование просто признает, что тестирование редко бывает исчерпывающим (потому что оно часто слишком дорого и требует много времени, чтобы быть экономически жизнеспособным), и дает рекомендации о том, как эффективно сосредоточить ограниченные ресурсы.

Важно отметить, что тестовый код считается первоклассным артефактом проекта, поскольку он поддерживается в том же качестве, что и код реализации, без всякого дублирования. Разработчики выпускают код модульного тестирования в репозиторий кода вместе с кодом, который он тестирует. Тщательное модульное тестирование экстремального программирования обеспечивает упомянутые выше преимущества, такие как более простая и надежная разработка и рефакторинг кода , упрощенная интеграция кода, точная документация и более модульные конструкции. Эти модульные тесты также постоянно выполняются как форма регрессионного теста .

Модульное тестирование также имеет решающее значение для концепции Emergent Design . Поскольку возникающий дизайн сильно зависит от рефакторинга, модульные тесты являются неотъемлемым компонентом.

Фреймворки для модульного тестирования

Фреймворки для модульного тестирования - это чаще всего сторонние продукты, которые не распространяются как часть компилятора. Они помогают упростить процесс модульного тестирования, поскольку были разработаны для самых разных языков .

Как правило, модульное тестирование можно выполнять без поддержки конкретной структуры, написав клиентский код, который проверяет тестируемые модули и использует утверждения , обработку исключений или другие механизмы потока управления для сигнализации о сбое. Модульное тестирование без фреймворка ценно тем, что есть входной барьер для принятия модульного тестирования; иметь скудные модульные тесты едва ли лучше, чем их вообще не иметь, в то время как как только фреймворк создан, добавление модульных тестов становится относительно простым. В некоторых фреймворках многие расширенные функции модульного тестирования отсутствуют или должны быть написаны вручную.

Поддержка модульного тестирования на уровне языка

Некоторые языки программирования напрямую поддерживают модульное тестирование. Их грамматика позволяет напрямую объявлять модульные тесты без импорта библиотеки (сторонней или стандартной). Кроме того, логические условия модульных тестов могут быть выражены в тот же синтаксис , как булевы выражения , используемые в необратим кода тест, например, то , что используется для ifи whileутверждения.

Языки со встроенной поддержкой модульного тестирования включают:

Некоторые языки без встроенной поддержки модульного тестирования имеют очень хорошие библиотеки / фреймворки для модульного тестирования. Эти языки включают:

Смотрите также

Рекомендации

Внешние ссылки