Как я могу защищать юнит-тестирование на частном коде?

15

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

Хотя я думаю, что юнит-тест можно и нужно применять ко всему коду, как я могу убедить своих коллег?

Wizard79
источник
3
Если у вас есть закрытые методы, которые вы испытываете необходимость в тестировании, это часто является признаком того, что ваш код нарушает SRP, и есть еще один класс, требующий извлечения и тестирования самостоятельно.
паддислакер
@Paddyslacker: я чувствую, что весь код должен быть протестирован. Я не понимаю, почему блок кода, который следует принципу единой ответственности, не должен подвергаться модульному тестированию ...
Wizard79
4
@lorenzo, ты упустил мою точку зрения; может быть, я не очень хорошо это сделал. Если вы извлечете эти закрытые методы в другой класс, теперь они должны быть доступны из вашего исходного класса. Поскольку методы теперь общедоступны, их нужно будет протестировать. Я не имел в виду, что их не следует тестировать, я имел в виду, что если вы чувствуете необходимость непосредственно тестировать методы, скорее всего, они не должны быть частными.
паддислакер
@Paddyslacker: я чувствую необходимость напрямую тестировать и частные методы. Почему вы думаете, что они не должны быть частными?
Wizard79 11.10.10
6
Тестируя частные методы, вы нарушаете абстракцию. Вы должны тестировать состояние и / или поведение, а не реализацию в модульных тестах. Ваши примеры / сценарии должны быть в состоянии проверить, к чему приводит результат частного кода - если вам это трудно, тогда, как сказал Paddyslacker, это может означать, что вы нарушаете SRP. Это также может означать, что вы не использовали свои примеры, чтобы действительно представлять, что делает ваш код.
FinnNk 11.10.10

Ответы:

9

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

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

  • Тестовое покрытие . Измерьте фактический процент охвата тестами этих интеграционных тестов. Это проверка реальности для тех, кто никогда не проводил тестовое покрытие. Поскольку сложно использовать все логические пути, когда входные данные находятся на расстоянии нескольких слоев, тестовое покрытие составляет максимум от 20% до 50%. Чтобы получить больше охвата, ваши коллеги должны написать реальные, изолированные юнит-тесты.
  • Конфигурация . Разверните то же тестируемое программное обеспечение, и, возможно, вы сможете продемонстрировать своим коллегам, насколько сложно проводить их тесты в другой среде. Пути к различным файлам, строки подключения к БД, URL-адреса удаленных служб и т. Д. - все это складывается.
  • Время выполнения . Если тесты не являются настоящими модульными тестами и не могут выполняться в памяти, их запуск займет много времени.
azheglov
источник
12

Причины использования модульных тестов для внутреннего / частного кода точно такие же, как для внешне поддерживаемых API:

  • Они предотвращают повторение ошибок (модульные тесты являются частью вашего набора регрессионных тестов).
  • Они документируют (в исполняемом формате!), Что код работает.
  • Они предоставляют исполняемое определение того, что означает «код работает».
  • Они предоставляют автоматизированные средства демонстрации того, что код действительно соответствует спецификациям (как определено пунктом выше).
  • Они показывают, как происходит сбой модуля / класса / модуля / функции / метода при неожиданном вводе.
  • Они предоставляют примеры того, как использовать устройство, что является отличной документацией для новых членов команды.
Фрэнк Шиарар
источник
8

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

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

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

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

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

  1. Мой результат отсортирован?

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

  1. Мой результат отсортирован?
  2. При заданном сценарии (скажем, начальный список почти отсортирован для начала) является ли вызов класса, который сортирует строки с использованием алгоритма X?
  3. При заданном сценарии (начальный список в случайном порядке) выполняется ли вызов класса, который сортирует строки с использованием алгоритма Y?

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

Тесты (такого типа) должны изменяться только при изменении поведения, начинать писать тесты для частного кода, и это выходит за рамки.

FinnNk
источник
1
Возможно, есть недоразумение о значении «частный». В нашей системе 99% кода является «частным», тогда у нас есть небольшой API для автоматизации / удаленного управления одним из компонентов системы. Я имею в виду модульное тестирование кода всех остальных модулей.
Wizard79
4

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

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

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

Стейн
источник
+1 "с другой стороны ..." Но если ничего другого, добавьте тесты, в которых сбой повредит больше всего.
Тони Эннис
2

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

В первый раз, когда вы обнаружите ошибку, вы должны признать правду, что вы не совершенны (как быстро мы программисты забываем!), И вы говорите: «Хм».


Другим аспектом модульного тестирования является то, что код должен быть написан для тестирования. Понимание того, что Some Code легко тестируется, а Some Code не делает хорошего программиста «Хммм».


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


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

Другой способ - подождать, пока одна и та же ошибка не произойдет дважды . «Ухх, хорошо, Босс, мы добавили код для проверки на ноль после проблемы на прошлой неделе, но на этот раз пользователь ввел пустую вещь!»


Подавать пример. Напишите модульные тесты для ВАШЕГО кода, а затем покажите своему боссу значение. Затем посмотрите, не позвонит ли босс в пиццу на обед и не сделает презентацию.


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

Тони Эннис
источник
2

Существует два вида закрытого кода: закрытый код, который вызывается открытым кодом (или закрытый код, вызываемый закрытым кодом, который вызывается открытым кодом (или ...)) и закрытый код, который в конечном итоге не вызывается открытым код.

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

Обратите внимание, что при выполнении TDD для непроверенного частного кода невозможно существование.

Йорг Миттаг
источник
В нашей системе 99% кода относится к третьему виду : частный, не вызывается общедоступным кодом и необходим для системы (только минимальная часть нашей системы имеет внешний открытый API).
Wizard79
1
«Обратите внимание, что когда вы делаете TDD, для непроверенного частного кода невозможно существование». <- удалить тестовый пример, не зная, что этот тест - единственный тест, охватывающий определенную ветку. Хорошо, это более «в настоящее время не проверенный» код, но достаточно легко увидеть, что последующий тривиальный рефакторинг изменит этот код… только ваш набор тестов больше не покрывает его.
Фрэнк Шиарар
2

Модульное тестирование - это все о тестировании модулей вашего кода. Вам решать, что такое юнит. Ваши коллеги определяют единицы как элементы API.

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

  • определить другой контрольный пример для покрытия этой части,
  • анализировать код для обоснования того, почему он не может быть рассмотрен в контексте модульного тестирования, но должен охватываться в других ситуациях,
  • удалить мертвый код, который не был покрыт и не оправдан.
mouviciel
источник
В нашей системе API - это лишь минимальная часть, которая позволяет автоматизировать / дистанционно управлять сторонним приложением. Тестирование только учетных записей API для покрытия кода 1% ...
Wizard79