пятница, 3 марта 2017 г.

"Unit тесты" или "Функциональные тесты"

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

Например, тест из первой группы мог бы проверять результат работы метода, который должен посчитать новое значение в зависимости от переданного значения масштабирования экрана ("Make texts larger" в Windows для HighDpi мониторов): передали в него исходную цифру и несколько масштабов - проверили цифру под каждый масштаб.

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

Что же мне выбрать для эффективной работы?

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

К этому вопросу классификация "Unit"/"Функциональный" вообще не имеет отношения. Мне просто надо запустить некоторый блок тестов, который с высокой вероятностью (одна ложная ошибка из 500 запусков задачи) адекватно покажет соответствуют мои изменения требованиям этих тестов или нет.

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

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

На долгосрочной перспективе работы в несколько лет мне оказалось удобнее иметь второй вариант тестов: часто через пару лет после первой отдачи решения клиентам я получал от них дополнительные сценарии к списку моих начальных сценариев и для нового, расширенного набора сценариев требовалось совершенно новое решение. А с первым подходом в этот момент я мог начать только "с чистого листа": весь старый список требований был выражен в тестах на обработчики евентов. Но вот зачем конкретный обработчик должен был быть именно таким я уже не знал и никак не мог выяснить, как его работа проявлялась для конечного пользователя. И я начинал составлять тесты "по памяти". Конечно же это опять вело к неполному набору, дополнительным сценариям от клиентов и замкнутому кругу.

Со вторым вариантом я смог ставить очень разнообразные эксперименты и сразу получать ответ на вопрос "удовлетворяет ли мое новое решение моему новому набору требований "?