Подтвердить что ты не робот

Управление тестированием единиц деградированного сетевого стека, повреждение файлов и другие недостатки

Я прежде всего кодер С++, и до сих пор обошлось без написания тестов для всего моего кода. Я решил, что это плохая идея (tm), добавив новые функции, которые тонко сломали старые функции или, в зависимости от того, как вы хотите посмотреть на нее, внедрили некоторые новые "функции".

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

Приведенный выше пример немного надуман, и что-то, с чем я не сталкивался (в случае, если вы не догадались). Позвольте мне выбрать то, что у меня есть. Как вы протестируете приложение, выполнит свою работу перед лицом деградировавшего сетевого стека? То есть скажем, что у вас умеренная потеря пакетов по той или иной причине, и у вас есть функция DoSomethingOverTheNetwork(), которая должна изящно деградировать, когда стек не выполняет, как он предполагал; но не так ли? Разработчик проверяет его лично, намеренно настраивая шлюз, который отбрасывает пакеты для имитации плохой сети, когда он впервые ее пишет. Несколько месяцев спустя кто-то проверяет какой-то код, который изменяет что-то тонко, поэтому деградация не обнаруживается вовремя или приложение даже не распознает деградацию, это никогда не попадает, потому что вы не можете запустить реальный мир тесты, подобные этому, с помощью модульных тестов, можете ли вы?

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

Следовательно, хрупкость. Модульные тесты, похоже, проверяют код только в идеальных условиях (и это продвигается, с макетными объектами и т.д.), А не то, с чем они столкнутся в дикой природе. Не поймите меня неправильно, я думаю, что модульные тесты великолепны, но набор тестов, составленный только из них, кажется, является разумным способом ввода тонких ошибок в ваш код, чувствуя чрезмерную уверенность в его надежности.

Как я могу устранить описанные выше ситуации? Если модульные тесты не являются ответом, что такое?

Изменить: я вижу много ответов, которые говорят "просто издевайтесь над этим". Ну, вы не можете "просто издеваться", вот почему: Взяв мой пример деградирующего сетевого стека, предположим, что ваша функция имеет хорошо определенную NetworkInterface, которую мы будем издеваться. Приложение отправляет пакеты по TCP и UDP. Теперь, допустим, эй, пусть имитирует 10% -ную потерю интерфейса, используя макет-объект, и посмотрим, что произойдет. Ваши TCP-соединения увеличивают попытки повторных попыток, а также увеличивают их отдачу, все хорошие практики. Вы решили изменить X% своих UDP-пакетов, чтобы фактически создать TCP-соединение, интерфейс с потерями, мы хотим быть в состоянии гарантировать доставку некоторых пакетов, а другие не должны терять слишком много. Прекрасно работает. Между тем, в реальном мире.., когда вы увеличиваете количество TCP-соединений (или данных через TCP), при достаточно низком соединении, вы в конечном итоге увеличиваете потерю вашего UDP-пакета, так как ваши TCP-соединения будут в конечном итоге -издача их данных все больше и больше и/или сокращение их окна, в результате чего 10% -ная потеря пакетов на самом деле будет больше похожа на потерю пакетов UDP на 90%. Whoopsie.

Нет biggie, пусть это сломается в UDPInterface и TCPInterface. Подождите минуту.. они взаимозависимы, тестирование 10% потери UDP и 10% потери TCP ничем не отличается от вышеизложенного.

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

В какой-то момент вам нужно будет создать Mock OS, которая ведет себя точно так же, как ваша реальная ОС, кроме того, можно проверить. Это не похоже на хороший способ продвижения вперед.

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

Я надеюсь, что кто-то скажет мне, что я очень неправ, и укажите, почему!

Спасибо!

4b9b3361

Ответ 1

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

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

Обновление

В своем обновлении вы описываете сложные чувствительные к времени взаимодействия. Единичное тестирование просто не помогает вообще. Не нужно создавать сети: просто подумайте о том, чтобы попытаться написать простой класс потоковой безопасности, возможно, на платформе с некоторыми новыми примитивами concurrency. Протестируйте его на 8-уровневой системе... это работает? Вы просто не можете этого точно знать, тестируя его. Слишком много разных способов, что время может привести к перекрытию операций между ядрами. В зависимости от удачи, это может занять недели непрерывного исполнения, прежде чем произойдет какое-то действительно маловероятное совпадение. Единственный способ получить такие права - тщательный анализ (могут помочь инструменты статической проверки). Вероятно, в большинстве параллельных программ в нем встречаются редкие ошибки, включая все операционные системы.

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

Ответ 2

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

Ответ 3

какие тесты вы бы писали, чтобы постоянно проверять эти сайты в прямом эфире?

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

например, искатель

Сканер - это большой кусок кода, который вы можете написать. Он имеет несколько разных частей, одна часть может принести веб-страницу. Другая часть может анализировать html. Даже эти части могут быть слишком большими, чтобы написать unit test против.

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

Если тест использует сеть, это не UnitTest.

Модуль UnitTest (который должен указывать на ваш код) не может вызывать сеть. Вы не писали сеть. UnitTest должен включать в себя макет сети с имитируемой (но последовательной каждый раз) потерей пакетов.

Ед. тесты, похоже, проверяют код только в идеальных условиях.

UnitTests проверяет ваш код в определенных условиях. Если вы способны определять идеальные условия, ваше утверждение верно. Если вы способны определить несовершенные условия, ваше утверждение неверно.

Ответ 4

Звучит так, как будто вы ответили на свой вопрос.

Mocks/stubs - это ключ к тестированию трудно тестируемых областей. Во всех ваших примерах ручной подход, например создание веб-сайта с изворотливыми данными или сбоя сети, может быть выполнен вручную. Однако было бы очень сложно и утомительно сделать это, а не то, что кто-то порекомендовал бы. Фактически, некоторые из них означают, что вы на самом деле не тестируете устройство.

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

Изменить - Что касается обновленного вопроса.

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

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

Ответ 5

Тестирование интеграции и тестирование модулей

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

Чтобы издеваться или нет, почему бы и не сделать

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

  • набор тестов работает быстрее
  • гарантирует одинаковый ответ каждый раз (без тайм-аутов, непредвиденной деградированной сети и т.д.).
  • мелкозернистый контроль над поведением

Иногда вам не следует писать тест

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

Вы просто сохраняете образцы страниц, проверяете против тех, и надеемся, что сайты никогда не меняются?

Тестирование не является панацеей

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

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

Тестирование!= Мониторинг

Тесты - это тесты и часть разработки (и QA), а не для производства. МОНИТОРИНГ - это то, что вы используете в производстве, чтобы убедиться, что ваше приложение работает правильно. Вы можете писать мониторы, которые должны предупредить вас, когда что-то сломано. Это целая другая тема.

Как вы будете тестировать приложение его работа перед лицом ухудшения сетевой стек?

Бэкон

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

Далее, как насчет повреждения файла?

Сколько проверок

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

Ответ 6

Я думаю, вы не можете и не должны делать unit test для всех возможных ошибок, с которыми вы можете столкнуться (что, если метеорит попадает на сервер db?) - вы должны приложить усилия для проверки ошибок с разумной вероятностью и/или полагаться или на другие услуги. Например; если ваше приложение требует правильного поступления сетевых пакетов; вы должны использовать транспортный уровень TCP: он гарантирует корректность полученных пакетов прозрачно, поэтому вам нужно только сосредоточиться, например. что произойдет, если сетевое соединение будет отключено. Контрольные суммы предназначены для обнаружения или исправления разумного количества ошибок. Если вы ожидаете 10 ошибок на файл, вы будете использовать другую контрольную сумму, чем если бы вы ожидали 100 ошибок. Если выбранная контрольная сумма указывает, что файл правильный, то у вас нет причин считать его сломанным (вероятность того, что он сломан, пренебрежимо мала). Поскольку у вас нет бесконечных ресурсов (например, время), вы должны идти на компромиссы, когда пишете свои тесты; и выбирая эти компромиссы, это непростой вопрос.

Ответ 7

Хотя не полный ответ на массивную дилемму, с которой вы сталкиваетесь, вы можете уменьшить количество тестов, используя технику под названием Equivalence Partitioning.

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

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

Ответ 8

Иногда я создам два (или более) набора тестов. Один пакет использует mocks/stubs и только проверяет код, который я пишу. Другие тесты проверяют базу данных, веб-сайты, сетевые устройства, другие серверы и все остальное вне моего контроля.

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

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

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

Ответ 9

Правильное использование Unit Testing начинается с нуля. То есть вы пишете свои модульные тесты, прежде чем писать свой производственный код. Затем модульные тесты вынуждены учитывать условия ошибки, предварительные условия, пост-условия и т.д. После того, как вы напишете производственный код (и модульные тесты могут быть скомпилированы и успешно запущены), если кто-то внесет изменения в код, который изменяет любое из своих условий (даже тонко), unit test выйдет из строя, и вы узнаете об этом очень быстро (либо через ошибку компилятора, либо через сбой unit test).

РЕДАКТИРОВАТЬ: Что касается обновленного вопроса

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

Ответ 10

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

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

Или тестирование анализатора PDF или компилятора С++ затруднительно из-за огромного количества возможных входов. Это когда владение 10-летними образцами клиентов и история дефектов дороже, чем исходный код. Почти каждый может сесть и закодировать его, но изначально у вас не будет возможности проверить правильность вашей программы.

Ответ 11

Красота штучных объектов заключается в том, что у вас может быть больше одного. Предположим, что вы программируете против четко определенного интерфейса для сетевого стека. Тогда вы можете иметь макет объекта WellBehavingNetworkStack для проверки нормального случая и другого макета объекта OddlyBehavingNetworkStack, который имитирует некоторые сетевые сбои, которые вы ожидаете.

Используя модульные тесты, я обычно также проверяю проверку аргументов (например, обеспечение того, что мой код генерирует NullPointerExceptions), и это легко в Java, но сложно в С++, поскольку на последнем языке вы можете легко ударить поведение undefined и то все ставки отключены. Поэтому вы не можете быть абсолютно уверены, что ваши модульные тесты работают, даже если они кажутся. Но все же вы можете проверить нечетные ситуации, которые не вызывают поведение undefined, что должно быть довольно много в хорошо написанном коде.

Ответ 12

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