Категории программных тестов

Автор оригинала: Miško Hevery
  • Перевод
Перевод был сделан как ответ на некоторые комментарии к переводу Настройка IDE для автоматического запуска тестов. Прочитав статью и посмотрев наглядные примеры, вы сможете прочувствовать разницу между разнообразными видами тестов, что, в свою очередь, поможет вам грамотно составлять тесты и не перемешивать их в одной куче. Каждый тест хорош в нужном месте и в нужное время!
— mazurov


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



Модульные/Маленькие


Начнем с модульных тестов. Лучшее определение, которое я нашел — это тесты, которые выполняются очень быстро (менее 1 миллисекунды) и в случае, когда они не проходят, нам не нужен дебагер для выявления места, где произошла ошибка. Само определение делает ограничение на то, какими должны быть тесты. Например, ваши тесты не должны выполнять никаких операций ввода/вывода (I/O) — это одно из условий выполнения теста менее 1 миллисекунды. Ограничение в 1 миллисекунду очень важно, поскольку вы желаете запускать ВСЕ (тысячи) ваши модульные тесты каждый раз, когда вы изменяете что-либо, желательно при каждом сохранении файла. Я могу терпеть только 2 секунды и в течение этих двух секунд я хочу, чтобы все мои тесты выполнились и ничего не сломалось. Это очень здорово, когда можно просто нажать ctrl+z несколько раз для отмены недавних изменений и исправить «красный» тест. Немедленная обратная связь вызывает привычку как у наркомана. Отсутствие потребности в дебагере подразумевает, что область теста локализована (отсюда и название теста — модульный (unit), тест одного класса, например).

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

KeyedMultiStackTest.java — это пример хорошего модульного теста из Обозревателя тестируемости. Почувствуйте, как каждый тест рассказывает нам историю. Это не testMethodA, testMethodB,... — это скорее похоже на сценарий. Заметьте, как вначале идут обычные тесты, к которым вы возможно привыкли, но ближе к концу файла тесты выглядят немного незнакомо. Это объясняется тем, что это тесты на пограничные случаи, которые я обнаружил уже позже. Забавная вещь с тестируемым классом в KeyMultiStack.java в том, что я переписывал его три раза. Я не мог заставить его работать на всех тестах. Один из тестов всегда ломался до тех пор, пока я не понял, что алгоритм содержит существенный недостаток. К тому времени у меня была почти работающая программа и это был ключевой класс для процесса анализа байт-кода. Как вы будете себя чувствовать, когда надо похоронить что-то столь фундаментальное в системе и переписать это с нуля? У меня ушло два дня на переработку до того момента, пока успешно не прошли все тесты. После этого все приложение осталось работоспособным. Это то, что называется — «Ага! момент» — момент, когда вы осознаете насколько хороши модульные тесты.

Должен ли каждый класс иметь соответствующий модульный тест? В общем-то, нет. Многие классы тестируются не напрямую, а через тестирование чего-то еще. Обычно простые объекты-значения (value objects) не имеет модульных тестов. Но не путайте неимение тестов и неполное покрытие тестами. Все классы/методы должны быть протестированы (test coverage). Если вы тест-инфицированны (TDD), то это у вас в крови.

Средние/Функциональные


Итак, с помощью модульных тестов вы удостоверились, что каждый класс по отдельности выполняет то, что нужно. Но как вам узнать, что они также хорошо будут работать вместе? Для этого нам нужно объединить связанные классы так, как это будет в рабочей версии программы и проверить основные пути их взаимодействия. Задача состоит не в проверке работают ли if'ы или циклы, а в контроле контрактов между интерфейсами классов. Хорошим примером функционального теста является MetricComputerTest.java. Заметьте, что входными данными для каждого теста является внутренний класс в тестируемом файле и выходом является ClassConst.java. Для получения выхода несколько классов взаимодействуют между собой: парсинг байт-кода, анализ путей выполнения и вычисление стоимости выполнения до того момента пока окончательная стоимость не будет принята.

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

Вычисление метрик является лишь небольшой частью того, что делает Обозреватель тестируемости. Существуют еще функции, которые следует протестировать. Вы можете думать о функциональных тестах, как о тестах связанных классов, которые формируют единую сущность для всего приложения. Вот некоторые из подобных сущностей в Обозревателе: парсинг байт-кода java, парсинг кода java, парсинг кода с++, высисление метрик, 3 разных вида отчетов и движок для советов. Все они соответствуют уникальному набору классов, которые взаимодействуют между собой и нуждаются в совместном тестировании. Чаще всего подобные наборы не зависят друг от друга.

Большие/Приемочные/Сценарные


Что мы еще должны протестировать, кроме if'ов, циклов и взаимодействия интерфейсов. Существует еще один класс возможных ошибок. Вы можете неверно объединить компоненты системы. Например, можно передать null вместо отчета, неправильно указать путь к jar-файлу для парсинга и т.д. Это не логические баги, а баги связывания. К счастью, баги связывания легко воспроизводимы и обычно сопровождаются появлением exception'а. Примером приемочного теста является TestabilityRunnerTest.java. Посмотрите как тесты проверяют работу всего приложения и имеют не так уж много assert-проверок. Нам их много и не нужно — мы до этого уже убедились, что все функции работают и нам надо только проверить те немногие места связки компонентов.

---------------------------
translated.by/you/software-testing-categorization/into-ru/trans
Оригинал (английский): Software Testing Categorization (http://googletesting.blogspot.com/2009/07/software-testing-categorization.html)
Перевод: © mazurov (Alexander MAZUROV).


UPD1: Новый перевод из серии про тестирование: Моя объединенная теория багов
Поделиться публикацией

Комментарии 36

    0
    Когда-то давно придумал свою систему тестирования, когда еще на паскале в школе учился. Оказывается называются эти тесты модульные. Я их и к тестированию то не относил, т.к. кользуюсь ими в момент написания. Вот когда полностью реализовал задуманый функционал, вот тогда и тестирую на более высоком уровне. Это уже тестами и считал. Надо будет пересмотреть свою концепцию тестирования с привлечением новых знаний. Спасибо за общий обзор. Почитаю подробнее эту тему.
      +3
      По этой теме от меня еще будут переводы на хабре.
      –4
      Логотип группы DDT

      –1
      «TDD» не только напомнило лого ДДТ


      Но также является и палиндромом :-)
        +5
        Из wikipedia: «Палиндро́м (от греч. πάλιν — «назад, снова» и греч. δρóμος — «бег») — число (например, 404), буквосочетание, слово (например, топот, финск. saippuakauppias = продавец мыла — самое длинное употребительное слово-палиндром в мире) или текст, одинаково (или почти одинаково) читающиеся в обоих направлениях.»

        Так что TDD палиндромом не является.
          –4
          Ну вы даёте, шутку юмора не понимаете.

          Прочитайте TDD «задом-наперёд», получится DDT ;-)
            0
            опять мимо, читаются по-разному
              0
              Всё с вами ясно, видимо не любители классики рока :-)
                +3
                Да ты ресурсом ошибся…
                  0
                  да ладно вам кипятиться…
                  ведь правда похожи? ;)
          +3
          Не палиндром. Анаграмма.
          0
          Level100, но хороший обзор. Ждем продолжения :)
            0
            Впервые попробовал Unit тесты в последнем, уже практически готовом проекте, для тестирования запросов с клиента к серверу и проверку результата. Чтобы сразу знать, что у них там отвалилось и не работает, а не в ручную через эмулятор все проверять. Запускал их в начальный момент работы программы. Для других частей смысла тесты делать не нашел.
            Хотя вот тест на проверку существования файла, это надо запомнить.
              0
              кстати, во всех жюнитовых ассерт-методах есть возможность задания первой переменной комментария к тому, что проверяет данный ассерт. вы это нигде не используете, а зря.
                0
                Зависит от того как написаны тесты — порой, для того, чтобы понять назначение теста, достаточно просто взглянуть на него — это называется самодокументированным кодом и комментарии бывают лишними.
                Хотя не скрою, есть ситуации когда подобные комментарии могут быть полезны.
                0
                > Лучшее определение, которое я нашел — это тесты, которые выполняются очень быстро (менее 1 миллисекунды)
                очень фиговое определение, надо сказать, ни о чем.
                  0
                  Согласен, что определение дано не совсем обычно, но время выполнения это основной признак модульных тестов — большинство тестов более высокого уровня выполняются дольше. Наложив ограничение на время выполнения, вы придете к пониманию того, какие вещи можно протестировать за это время и придете к выводу, что это модульные тесты в классическом их понимании (они и описаны в статье).
                  0
                  All Code IS Guilty Until Proven Innocent.

                  Одно из лучших определений концепции TDD в двух словах.
                    0
                    Отдельно хотелось бы акцентировать вот это:

                    «Почувствуйте, как каждый тест рассказывает нам историю. Это не testMethodA, testMethodB,… — это скорее похоже на сценарий.»

                    Действительно, по мере работы с TDD приходишь к тому, что тестируются не методы и классы, а кусочек поведения системы. Методы и классы — всего лишь средство реализации этого поведения.

                    И это действительно очень удобный подход. Ведь, действительно, что нам нужно от системы? Правильно, чтобы она работала так, как нам необходимо! Соответственно, если выполняются наши правила — считаем, что система работает как надо, ни больше, ни меньше. Неспроста все большую популярность набирает логическое продолжение TDD — BDD (Behaviour-Driven Development), ставя во главу угла не объект, используемый в тесте, а часть логики, которую он реализует.
                      0
                      и к чему этот обзор? примеров сильно не хватает
                        0
                        если писать сначала тесты, а потом код, то многих проблем можно избежать с самого начала. В том числе правилного разделения кода, который тестируется разными видами тестов (unit/integration/system)
                          0
                          Я тут только начал осваивать тестирование, у меня вопрос:
                          вот если использовать SimpleTest в PHP, то как тестировать protected-методы, или это не нужно?
                            +1
                            Обычно тестируют основные интерфейсы классов, а не реализацию, сокрытую в приватных или защищенных методах.
                              0
                              Ну я догадывался. А отсутствие friend-классов как в C++ меня полностью в этом убедило.
                              Но есть одно но — порой тесту для сравнения нужно знать делали внутренней реализации тестируемого класса, той реализации, что сокрыта в protected & private методах. И это не есть хорошо. Это можно обойти как-нибудь?
                                +1
                                это не можно, а нужно обойти, нормально продумав архитектуру класса.
                                  0
                                  Ну, вот, скажем, есть у нас набор строк, они склеиваются protected-методом. Как вы сможете в тесте сравнить выход для заданного набора строк, не зная, как конкретно они склеиваются?
                                    0
                                    я ничего не понял.
                                      0
                                      Пример:
                                      есть массив строк array( 'str1', 'str2', 'str4' );
                                      метод glue() возвращает одну строку, склеенную через _:
                                      'str1_str2_str4';

                                      Вам надо для заданного набора строк проверить работоспособность метода:
                                      т.е. для array('Вася', 'Петя', 'Онотоле') получить 'Вася_Петя_Онотоле'
                                      т.е. в тесте можно написать assertEquals($Class->glue(array('Вася', 'Петя', 'Онотоле')), 'Вася_Петя_Онотоле')

                                      Так вот я описал простой метод склейки, но ведь он может быть куда более сложным.

                                      1. Если изменится метод склейки, нужно будет менять тестовые данные
                                      2. Нужно знать, как именно склеиваются строки, в данном случае, через '_', что тоже не очень хорошо

                                      Вот как в этом случае продумать архитектуру класса.
                                        +1
                                        Что-то неправильно я пример написал…
                                        Ну да ладно. Сам понял, что тестовые данные для сравнения всегда будут зависеть от внутренней реализации, и никуда от этого не деться.
                            0
                            Это одномерный способ классификации, (по «уровню крупности»), который включает только «программные тесты», как явствует из заголовка.

                            Мне больше нравится классификация в форме «магического квадрата», которая была сначала предложена Брайаном Мариком, а потом идею доработал Ари Таннинен: stopandfix.blogspot.com/2009/04/all-about-testing.html

                            Модульные тесты — в левом нижнем углу.
                              0
                              Господа, а поделитесь опытом, кто какой тул использует для continuous integration? желательно с доводами почему -)
                                0
                                господа, разработчики! я работаю в QA уже более 5 лет со многими крупными компаниями мирового уровня. я не видел ни одного проекта, в котором бы разработчики делали не то что functional testing, они даже о unit testing не задумывались в 85% случаев. первоначально приложения ваще было невозможно запустить.
                                так что вы, рассказывая о unit/functional/integration фазах тестирования, немного лукавите. большинство из вас этого не делает (глобально говорю).
                                при этом я понимаю, что это просто перевод статьи… peace люди…
                                а ваще QA должны заниматься специализированные люди, а не разработчики. ведь асфальтоукладчики не бегают перед катком и не разбрасывают асфальт. их дело — ехать ровно на катке. все по специальностям. кто во чем хорош. пусть все делают профессионалы: одни кодят, другие доказывают что первые сделали свою работу не до конца хорошо )))
                                  0
                                  Разве автор статьи где-то хоть раз обмолвился о разделении труда? Нет, автор даже не пытался давать указания на то, кто какие тесты должен делать.
                                  Разве автор заявил, что он пишет про «фазы тестирования»? Нет, он это назвал «категориями тестов».
                                  Может быть, конечно, у Вас просто наболело за 5 лет работы в проектах, где переданное на тестирование приложение с первой попытки даже не запускается, но причём тут данная статья?
                                  0
                                  Новая статья из серии про тестирование: habrahabr.ru/blogs/testing/64874/ — «Моя объединенная теория багов»

                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                  Самое читаемое