Модульное тестирование обработки ухудшенного сетевого стека, повреждения файлов и других недостатков - 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 ]

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

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

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

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

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

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