Это плохая практика для обеспечения порядка выполнения для модульных тестов?


84

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

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

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

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

редактировать: вопрос от Как структурировать тесты, где один тест является настройкой другого теста? так как «предыдущий» тест не является настройкой, но тестирует код, который выполняет настройку.



123
Если вы тестируете соединение с удаленным сервером, то это по определению не юнит-тесты.
Теластин

9
Первый ответ смутил меня здесь, потому что в своем названии вы сказали: «Это плохая практика?» и в резюме вашего вопроса вы написали "это нормально?" Любой, кто ответит «да» или «нет», запутает одного из них!
Лиаф

8
Похоже, вы создаете набор интеграционных тестов. Даже для этого одного теста не следует полагаться на другие тесты.
Низколетящий пеликан

17
Если порядок имеет значение, то вы, вероятно, делаете это неправильно.
Марк Роджерс

Ответы:


236

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

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

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


14
Не говоря уже о том, что какой-то тест должен специально проверять, что ожидаемое поведение происходит, когда удаленный сервер недоступен.
Александр

2
Или, возможно, вы на самом деле намереваетесь написать интеграционные тесты, и, таким образом, извлечение данных не даст того, чего вы пытаетесь достичь с помощью этих тестов.
Гай Шалнат,

10
Проблема, скорее всего, в том, что у Junit есть «юнит» в названии.
Торбьерн Равн Андерсен

7
@ ThorbjørnRavnAndersen Точно. Люди, естественно, пишут интеграционные тесты, а не модульные тесты, потому что интеграционные тесты гораздо полезнее и гораздо менее трудны для написания, чем «настоящие» модульные тесты. Но поскольку популярные платформы тестирования были названы в честь концепции модульных тестов, этот термин был объединен и стал означать «любое автоматизированное тестирование» на современном языке.
Мейсон Уилер

1
@MasonWheeler Или даже не технические менеджеры выбрали для приемочного тестирования.
TKK

32

Да, это плохая практика.

Как правило, модульное тестирование предназначено для тестирования одного блока кода (например, одной функции, основанной на известном состоянии).

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

Чтобы выполнить модульное тестирование подобных вещей, вам нужно найти способ внедрить фиктивные данные, например, реализовать интерфейс службы данных, который отражает веб-запрос, но возвращает известные данные из локального файла фиктивных данных.


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

8
@autophage: Определенно согласен с этим. На самом деле, я настолько согласен с этим, что регулярно попадаю в одну и ту же ловушку, несмотря на то, что согласен с тем, что это ловушка 😂
Легкость

16

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

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

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

Лучший совет, который я могу дать, - написать тесты таким образом, чтобы было легко определить, когда сбой в зависимости приводит к провалу теста.


7

Запах, на который вы ссылаетесь, является применением неправильного набора ограничений и правил для ваших тестов.

Модульные тесты часто путают с «автоматическим тестированием» или «автоматическим тестированием программистом».

Модульные тесты должны быть небольшими, независимыми и быстрыми.

Некоторые люди неправильно читают это как «автоматизированные тесты, написанные программистом, должны быть небольшими, независимыми и быстрыми» . Но это просто означает, что если ваши тесты не маленькие, независимые и быстрые, они не являются юнит-тестами, и поэтому некоторые правила для юнит-тестов не должны, не могут или не должны применяться к вашим тестам. Тривиальный пример: вы должны запускать свои модульные тесты после каждой сборки, чего не следует делать для не быстрых автоматических тестов.

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


6

Как отмечалось выше, то, что вы запускаете, похоже, является интеграционным тестом, однако вы заявляете, что:

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

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

например

Если у вас есть:

class Queries {

    int GetTheNumber() {
        var dataModule = new Submodule1();
        var data = dataModule.GetData();
        return ... run some query on data
    }
}

Вместо этого предпочитаю:

interface DataModule {
    Data GetData();
}


class Queries {

    IDataModule _dataModule;

    ctor(IDataModule dataModule) {
       _dataModule = dataModule;
    }

    int GetTheNumber() {
        var data = _dataModule.GetData();
        return ... run some query on data
    }
}

Это удаляет зависимость от запросов к вашему источнику данных и позволяет вам настраивать легко повторяемые модульные тесты для определенных сценариев.


6

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


2
Я был бы более склонен исследовать, почему определенный модульный тест выполняется медленнее, чем навязывание порядка. Модульные тесты должны быть быстрыми.
Робби Ди

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

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

@MikeHoller Тогда это не юнит-тесты. Там действительно не должно быть путаницы относительно того, что такое юнит-тесты .
Робби Ди

@RobbieDee Я просто использовал терминологию, которую использовал ОП. Я понимаю, что это не настоящие юнит-тесты. Если вы хотите бороться за терминологию, обсудите это с OP. (следовательно, почему я пояснил «истинные юнит-тесты» в моем предыдущем комментарии »)
Майк Холлер
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.