Как unit test код, который является очень сложным за публичным интерфейсом

Мне интересно, как я должен тестировать эту функцию через NUnit.

Public void HighlyComplexCalculationOnAListOfHairyObjects()
{
    // calls 19 private methods totalling ~1000 lines code + comments + whitespace
}

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

Ответ 1

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

  • Определите самый узкий интерфейс.

  • Определите класс реализации с помощью проверяемых (не private) методов и атрибутов. Это нормально, если класс имеет "лишний" материал.

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

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

Ответ 2

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

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

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

Ответ 3

Как уже упоминалось, InternalsVisibleTo("AssemblyName") - хорошее место для запуска устаревшего кода.

Internal методы все еще являются частными в том смысле, что сборки вне текущей сборки не могут видеть методы. MSDN для получения дополнительной информации.

Еще одна вещь - реорганизовать большой метод на более мелкие, более определенные классы. Проверьте этот вопрос, я спросил о подобной проблеме, тестирование больших методов.

Ответ 4

Лично я сделаю составные методы внутренними, применил InternalsVisibleTo и проверил разные биты.

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

Ответ 5

HighlyComplexCalculationOnAListOfHairyObjects() - это запах кода, указание на то, что класс, который его содержит, потенциально слишком много и должен быть реорганизован через Extract Class. Методы этого нового класса были бы общедоступными и, следовательно, проверялись бы как единицы.

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

Ответ 6

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

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

Ответ 8

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

Ответ 9

пытается создать набор тестовых данных полностью выполнив все функциональности, вычисление было бы почти невозможным

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

Рефакторинг метода на отдельные под-алгоритмы сделает ваш код более надежным (и может быть полезным другим способом), но если ваша проблема представляет собой нелепое количество взаимодействий между этими под-алгоритмами, метод extract (или извлечение в класс стратегии) ​​на самом деле не решит его: вам нужно будет создать надежный набор тестов по одному за раз.