2013-03-31 5 views
3

У меня есть статический класс Configuration, отвечающий за настройки данных для всей моей системы. Он загружает определенные значения из реестра в свой конструктор, и все его методы основаны на этих значениях. Если он не может получить значения из реестра (что возможно, если приложение еще не активировано), оно выдает исключение, которое переводится в TypeInitializationException, что является хорошим для меня.Проблема с дизайном: статический класс инициализирует только один раз, прерывает модульное тестирование

Я написал модульные тесты с использованием NUnit, чтобы убедиться, что конструктор конфигурации правильно обрабатывает все случаи - нормальные значения, пустые значения, значение Null. Каждый тест инициализирует реестр, используя соответствующие значения, а затем вызывает некоторый метод внутри Конфигурации. И вот в чем проблема: NUnit решил сначала запустить тест Null. Он очищает реестр, инициализирует конфигурацию, выдает исключение - все хорошо. Но тогда, потому что это статический класс, чей конструктор просто провалился - он не перестраивает класс снова для других тестов, и все они терпят неудачу.

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

Мой вопрос: должен ли я использовать рефлексию для повторного построения класса для каждого теста, или мне нужно перепроектировать этот класс, чтобы проверить реестр в свойстве, а не на конструкторе?

+0

Вы можете переместить код загрузки в метод класса Configuration вместо конструктора. Затем вы можете выполнить тестирование функции с помощью разных сценариев. – rgunawan

+0

@rgunawan Согласитесь, это похоже на действительно простую модификацию. Просто переместите все тело конструктора в метод и позвольте конструктору просто вызвать этот метод. Тогда один и тот же метод можно было бы вызывать из модульных тестов. Кажется простым. –

ответ

9

Мой совет состоит в том, чтобы немного перестроить ваш класс Configuration. Поскольку ваш вопрос носит теоретический характер, с небольшим практическим аспектом (сбой модульного теста), я предоставлю некоторую ссылку для резервного копирования моих идей.

Прежде всего, введите Configuration нестатический класс. У Miško Hevery, инженера в google, есть очень хорошая речь о OO Design for Testability, где он специально затрагивает глобальное состояние как плохой выбор дизайна, особенно если вы хотите его протестировать.

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

Теперь преимущества:

  • вас есть хорошие unit tests, потому что вы не полагаться на реестр
  • не имеют глобального состояния (Тестируемость)
  • тесты не зависят друг с другом (каждый из них имеет отдельный экземпляр Configuration)
  • ваши тесты не зависят от среды, в которой они выполняются (у вас могут не быть прав доступа к реестру, но вы можете проверить свой Configuration класса)

Если вы чувствуете, что вы не совсем хорошо Dependency Injection, я рекомендовал бы изумительное произведение искусства и технику, при условии, смертной душу гением Марка Seemann, называется Dependency Injection in .NET. Одна из лучших книг, которые я прочитал о дизайне классов, который ориентирован на разработчиков .NET.

Для того, чтобы мой ответ более конкретен:

Должен ли я использовать отражение заново построить класс для каждого теста?

Нет, вы никогда не должны использовать рефлексию в своих тестах (только если это не другой случай). Рефлексия сделает вас тестами:

  • хрупкой
  • трудно понять
  • трудно поддерживать

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

Следует ли перепроектировать этот класс, чтобы проверить реестр в свойстве , а не на конструкторе?

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

Теперь у меня недостаточно информации, чтобы сообщить вам, следует ли читать реестр через RegistryProvider в конструкторе Configuration или лениво читать реестр по требованию, это сложный вопрос. Но я определенно могу сказать - старайтесь избегать глобального состояния как можно больше, старайтесь минимизировать зависимость от деталей реализации в тестах (это относится к принципам OO в целом) и пытайтесь тестировать объекты отдельно, т. Е. Без доступа к внешним системы. Тогда вы можете имитировать исключительные случаи, например, вы придерживаетесь класса, как ожидалось, когда реестр недоступен? Это очень сложно воссоздать такой сценарий, если вы напрямую обращаетесь к реестру через статические члены класса Configuration.

+0

Спасибо за подробный ответ, а за ссылку - я только начал смотреть лекцию, и это здорово. – user884248

+0

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

+0

@ user884248 ok, теперь вы можете абстрагироваться от RegistryProvider в интерфейс IConfigurationProvider и передать интерфейс в конструктор , Теперь вы можете связать эти зависимости с корнем композиции (см. Книгу или статьи Марка), и никто не узнает, что 'Configuration' использует экземпляр класса RegistryProvider –

0

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

0

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

+2

... потому что синглтоны не страдают от недостатка инициализации только один раз? – jalf

+0

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

+0

Нет, минимальное количество изменений кода - ничего не делать. Синглтон - ужасный шаблон дизайна, а не то, что вы должны искать. Когда даже авторы книги, которые ее популяризируют, отступают и говорят «мы, вероятно, не должны были ее включать», это своего рода намек. Конечно, еще один намек заключается в том, что в качестве шаблона проектирования он не дает никаких преимуществ, сочетая в себе все недостатки и недостатки нескольких других подходов. Короче говоря, [это отстой] (http://jalf.dk/singleton/) – jalf

1

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

Я боюсь, что вы» вам придется выбирать между проверяемым классом и статичным.

Компромисс, который вы можете сделать, состоит в том, чтобы переместить «логические» биты (например, проверку и т. Д.) В другой нестатический класс, который будет вызываться основным статическим классом.
Этот нестатический класс может быть легко протестирован.