.NET Unit Testing - Лучшие практики для сокрытия тестовых швов в коде выпуска - PullRequest
5 голосов
/ 16 июля 2009

Я ищу несколько советов для модульного тестирования таким образом, чтобы в производственном коде не оставались «тестовые зацепки».

Допустим, у меня есть статический класс (модуль VB.NET) с именем MethodLogger, у которого есть метод «void WriteEntry (string Message)», который предназначен для записи вызывающего метода и сообщения в файл журнала на диске. Фактическая цель WriteEntry () может быть перенаправлена ​​на ложную реализацию IMethodLogger для запуска модульных тестов, а для всех других случаев она должна вызывать реальную реализацию IMethodLogger.

Вот краткое изложение того, что я до сих пор делал, и мне нравится подход - но в некоторых быстрых тестах это вызывает некоторые вопросы и сомнения:

[InternalAttributesVisibleTo("MethodLogger.Tests")]
internal static class MethodLogger
{
    public void WriteEntry(string message)
    {
        LogWriter.WriteEntry(message);
    }

    private IMethodLogger LogWriter
    {
        get;
        set;
    }

#if DEBUG
    internal void SetLogWriter(IMethodLogger logger)
    {
        LogWriter = logger;
    }
#endif
}

Вот мои конкретные вопросы:

  • Это тесно связывает юнит-тесты с работой против сборки DEBUG; когда я запускаю модульные тесты, хотя кажется, что он не перестраивает тестируемую сборку специально в DEBUG - возможно ли это сделать?

    • Хотел бы я когда-нибудь запустить модульные тесты для не отладочной сборки? Сверху головы я вижу меньшую ценность - но будет ли это?
  • Могу ли я использовать собственный флаг прекомпилятора, такой как "TEST" вместо "DEBUG"? Как бы я сказал Visual Studio всегда перестраивать цель с этим флагом для тестирования, чтобы убедиться, что мои крючки / швы доступны?

Ответы [ 11 ]

4 голосов
/ 23 июля 2009

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

http://blog.typemock.com/2009/01/isolator-new-vbnet-friendly-api.html

(также это единственный инструмент с дружественным к VB API)

3 голосов
/ 16 июля 2009

На самом деле вы находитесь на пути к внедрению зависимости - вы просто еще не осознали:)

Нет ничего плохого в том, чтобы оставить швы на месте - вам просто нужно смоделировать их, чтобы вместо того, чтобы быть специфичными для теста, они фактически улучшали вашего API. Это требует немного практики, но прежде всего изменение мышления.

Я писал об этом недавно в Тестируемость - это действительно открытый / закрытый принцип .

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

  1. Сделайте это классом экземпляра вместо статического класса. Как скажут вам все опытные практики TDD, статика - это зло .
  2. Пусть конструктор only берет экземпляр IMethodLogger. Это называется Constructor Injection и заставляет всех клиентов принимать явное решение о том, как вести журнал.
  3. Либо настройте График зависимостей вручную, либо используйте DI-контейнер для соединения MethodLogger с нужным IMethodLogger.
3 голосов
/ 16 июля 2009

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

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

Однако, чтобы ответить на ваши актуальные вопросы:

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

Мы обязательно запускаем тесты для сборок Release / Obfuscated, потому что мы часто выявляем проблемы таким образом, так что, да, я думаю, что есть смысл заставить их работать.

Обфускация делает внутренние устройства совершенно непригодными, что для меня достаточно, поэтому мы оставляем методы тестирования (и делаем их внутренними). ​​

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

2 голосов
/ 16 июля 2009
  1. , как говорили другие, двигаться в направлении внедрения зависимостей и инверсии контроля (IoC)
  2. изолировать тестируемые лунки с помощью изолирующих каркасов (Moq и т. Д.)
  3. перенос тестов в отдельную сборку - нет необходимости #if DEBUG ... # endif
2 голосов
/ 16 июля 2009

Согласно книге Майкла Фезерса "Эффективная работа с устаревшим кодом", было бы лучше, если бы вы назвали сеттер каким-то конкретным для тестирования.

Пример: SetTestingLogger (IMethodLogger logger)

Я бы также удалил операторы препроцессора. В книге также говорится, и я цитирую

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

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

Раздел «Введение статического сеттера» в книгу объясняет это более подробно. Я рекомендовал бы это как книгу, которую должен иметь каждый разработчик.

2 голосов
/ 16 июля 2009

Имея #IF DEBUG и т. Д. ... это плохая форма.

Рабочий код должен быть независим от кода тестирования / отладки.

Я бы посоветовал изучить внедрение зависимостей, чтобы помочь в модульном тестировании.

1 голос
/ 16 июля 2009

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

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

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

1 голос
/ 16 июля 2009

Мне кажется, это главный кандидат на контейнер IoC. Я бы сделал мой регистратор не статичным и настроил бы различные конфигурации для тестирования, разработки и производства.

1 голос
/ 16 июля 2009

Это внутренний метод. Просто оставьте все как есть без условной компиляции, и только вызовите из ваших модульных тестов.

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

Снова просматривая ваш код; вам даже не нужен метод SetLogWriter; просто используйте приватный аксессор для вашего статического класса, и вы можете установить его самостоятельно в своих модульных тестах.

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

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

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