Ручное написание модульных тестов Proof By Example?

9

Мы знаем, что пишем JUnit тестов демонстрирует один конкретный путь через ваш код.

Один из моих партнеров прокомментировал:

Написание модульных тестов вручную - это Proof By Example .

Он пришел из истории Haskell, который имеет такие инструменты, как Quickcheck и способность рассуждать о поведении программы с типами .

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

У меня вопрос: пишут ли модульные тесты вручную Proof By Example?

Hawkeye
источник
3
Нет, не пишу / не использую тесты. Утверждение о том, что ваши модульные тесты являются доказательством того, что в программе нет ничего плохого, является Proof by Example (неуместное обобщение). Тесты не о математически доказывающих правильность кода - тесты, по своей природе, экспериментальные проверки. Это система безопасности, которая помогает вам обрести уверенность, рассказывая вам кое-что о коде. Но вы - тот, кто должен выбрать хорошую стратегию для исследования кода, и вы - тот, кто должен интерпретировать, что означают эти данные.
Филипп Милованович

Ответы:

10

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

Но хорошие юнит-тесты никогда не делают этого. Вместо этого они имеют дело с диапазонами и крайними случаями.

Например, если вы хотите написать модульные тесты для функции абсолютного значения, которая принимает целое число в качестве входных данных, вам не нужно проверять каждое возможное значение входных данных, чтобы доказать, что код работает. Чтобы получить исчерпывающий тест, вам потребуется всего пять значений: -1, 0, 1, а также значения max и min для входного целого числа.

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

Роберт Харви
источник
11
Тестер кода входит в бар и заказывает пиво. 5 сортов пива -1 пиво, пиво MAX_VALUE, курица. ноль.
Нил
2
«5 значений» - это чепуха. Рассмотрим тривиальную функцию вроде int foo(int x) { return 1234/(x - 100); }. Также обратите внимание, что (в зависимости от того, что вы тестируете) вам может потребоваться убедиться, что неверный («вне диапазона») ввод верных результатов (например, что `` find_thing (thing) `" правильно возвращает какой-то статус «не найден» если вещь не была найдена).
Брендан
3
@ Брендан: нет ничего существенного в том, что это пять значений; в моем примере это просто пять значений. Ваш пример имеет разное количество тестов, потому что вы тестируете другую функцию. Я не говорю, что для каждой функции требуется ровно пять тестов; Вы поняли это, прочитав мой ответ.
Роберт Харви
1
Библиотеки генеративного тестирования, как правило, лучше справляются с тестированием, чем вы. Если, например, вы использовали поплавки вместо целых чисел, ваша библиотека будет также проверить -Inf, Inf, NaN, 1e-100, -1e-100, -0, 2e200... Я бы предпочел не делать те все вручную.
Hovercouch
@Hovercouch: Если вы знаете о хорошем, я хотел бы услышать об этом. Лучшим, кого я видел, был Пекс; это было невероятно нестабильно, хотя. Помните, мы говорим об относительно простых функциях здесь. Все становится сложнее, когда вы имеете дело с такими вещами, как бизнес-логика в реальной жизни.
Роберт Харви
8

Любое тестирование программного обеспечения похоже на «Proof By Example», а не только на модульное тестирование с использованием такого инструмента, как JUnit. И это не новая мудрость, есть цитата из Дейкстры 1960 года, которая говорит, по сути, то же самое:

«Тестирование показывает наличие, а не отсутствие ошибок»

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

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

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

Док Браун
источник
1
Любой практический набор тестов, использующий случайную проверку, также будет иметь модульные тесты. (Технически, модульные тесты - это просто вырожденный случай случайного тестирования.) Ваша формулировка предполагает, что рандомизированных тестов трудно достичь, или что комбинировать рандомизированное тестирование и модульные тесты сложно. Обычно это не так. На мой взгляд, одним из самых больших преимуществ рандомизированного тестирования является то, что оно настоятельно рекомендует писать тесты как свойства кода, которые должны всегда храниться. Я бы предпочел, чтобы эти свойства были явно указаны (и проверены!), Чем выводить из них некоторые точечные тесты.
Дерек Элкинс покинул SE
@DerekElkins: «сложно» - это ИМХО неправильный термин. Случайные тесты требуют довольно больших усилий, и это усилие, которое уменьшает доступное время для тестов ручной работы (и если у вас есть люди, которые просто следуют лозунгу, подобному тому, который упоминается в вопросе, они, вероятно, вообще не будут делать ручной работы). Простое создание большого количества случайных тестовых данных для фрагмента кода - это только половина работы, необходимо также получить ожидаемые результаты для каждого из этих входных данных теста. Для некоторых сценариев это можно сделать автоматически. Для других нет.
Док Браун
Хотя определенно бывают моменты, когда нужно подумать о выборе хорошего дистрибутива, обычно это не серьезное зависание. Ваш комментарий предполагает, что вы думаете об этом неправильно. Свойства, которые вы пишете для рандомизированной проверки, - это те же свойства, которые вы пишете для проверки модели или для формальных доказательств. Действительно, они могут быть использованы и использовались для всех этих вещей одновременно. Там нет никаких "ожидаемых результатов", которые вы должны получить также. Вместо этого вы просто указываете свойство, которое всегда должно храниться. Некоторые примеры: 1) положить что-то в стек и ...
Дерек Элкинс покинул SE
... тогда совать всё равно, что ничего не делать; 2) любой клиент с балансом более 10000 долларов должен получить высокую процентную ставку баланса и только тогда; 3) позиция спрайта всегда находится внутри ограничительной рамки экрана. Некоторые свойства могут хорошо соответствовать точечным тестам, например, «когда баланс равен $ 0, выведите предупреждение о нулевом балансе». Свойства являются частичными спецификациями с идеалом получения полной спецификации. Трудно придумать эти свойства означает, что вы не знаете, что такое спецификация, и часто это означает, что вам будет сложно придумать хорошие модульные тесты.
Дерек Элкинс покинул SE
0

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

Генеративное тестирование, такое как QuickCheck, действительно хорошо подходит для широкого круга входных данных. Это также намного лучше для решения крайних случаев, чем ручные тесты: библиотеки генеративного тестирования будут иметь больше опыта, чем вы. С другой стороны, они говорят только об инвариантах, а не о конкретных выходных данных. Таким образом, чтобы убедиться, что ваша программа дает правильные результаты, вам все еще нужны ручные тесты, чтобы убедиться в этом foo(bar) = baz.

Hovercouch
источник