Модульное тестирование обработки ухудшенного сетевого стека, повреждения файлов и других недостатков - PullRequest
45 голосов
/ 29 декабря 2010

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

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

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

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

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

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

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

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

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

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

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

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

Спасибо!

Ответы [ 12 ]

14 голосов
/ 29 декабря 2010

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

12 голосов
/ 29 декабря 2010

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

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

, например, сканер

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

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

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

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

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

10 голосов
/ 29 декабря 2010

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

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

Обновление

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

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

5 голосов
/ 30 декабря 2010

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Бэкон

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

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

Сколько стоит тестирование

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

5 голосов
/ 29 декабря 2010

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

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

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

Редактировать - Что касается обновленного вопроса.

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

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

2 голосов
/ 29 декабря 2010

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

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

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

2 голосов
/ 29 декабря 2010

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

1 голос
/ 29 декабря 2010

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

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

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

1 голос
/ 29 декабря 2010

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

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

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

1 голос
/ 29 декабря 2010

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

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

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

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

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