Не удается создать модульный тест для модуля - это символизирует плохой дизайн? - PullRequest
3 голосов
/ 04 июня 2009

У меня есть приложение, которое возвращает данные в зависимости от указанного времени, я могу указать дни, месяцы или годы. Проблема заключается в том, что если бы я должен был запустить приложение сегодня и попросить его вернуть данные за 1 месяц назад, а через 3 месяца я бы попросил приложение вернуть данные с этой даты за предыдущий 1 месяц (т.е. 1 месяц с даты ) результаты, очевидно, будут другими. Из-за динамического характера этого мне трудно создавать модульные тесты, потому что мне приходится менять дату в зависимости от того, когда я запускаю тесты. Это символизирует плохой дизайн или это исключительный случай?

Ответы [ 11 ]

5 голосов
/ 04 июня 2009

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

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

2 голосов
/ 04 июня 2009

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

2 голосов
/ 04 июня 2009

Это проблемный случай - но не обязательно плохой дизайн.

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

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

1 голос
/ 04 июня 2009

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

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

Первое, что нужно выяснить, это то, куда идут ваши швы. Это не так сложно. По сути, каждый раз, когда вы строите новый объект, это хорошее место для шва. Обязательным условием для этого правила является то, что у вас есть довольно приличный объектно-ориентированный дизайн для начала. (Ребята из Google Testing Blog утверждают, что вы не можете выполнить модульное тестирование императивный код , потому что вы не можете внедрение зависимостей .) Другое хорошее место для шов - это всякий раз, когда вы общаетесь с внешним источником данных, таким как операционная система, файловая система, база данных, Интернет и т. д. Это то, что вы делаете.

Вам нужно системное время. Вот где ваш шов должен идти. Я рекомендую вам хорошую книгу по этому вопросу для полного рассмотрения всех ваших вариантов здесь, но вот один пример того, что вы могли бы сделать. Есть как минимум 2 или 3 других способа «вставить вашу зависимость» в текущее системное время. Я буду использовать Python для псевдокода, но он работает на любом языке OO:

class MyClass(object):
    def _get_current_time(self):
        '''This is a test seam'''
        return datetime.datetime.now()

    def age(self):
        return self._get_current_time() - self._birthday

Затем в вашем тестовом коде сделайте следующее:

class FakeMyClass(MyClass):
    def __init__(self, test_time, *args, **kwargs):
        self._test_time = test_time
        MyClass.__init__(self, *args, **kwargs)

    def _get_current_time(self)
        return self._test_time

Теперь, если вы тестируете с FakeMyClass, вы можете ввести любое системное время, какое захотите:

myclass = FakeMyClass(t)
self.assertEqual(myclass.age(), expected_age)

Опять же, это довольно большая тема, поэтому я рекомендую приобрести хорошую книгу.

1 голос
/ 04 июня 2009

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

0 голосов
/ 04 июня 2009

Что мешает вашему тесту предоставить данные для вашего кода для проверки?

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

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

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

0 голосов
/ 04 июня 2009

Я думаю, если это действительно модульное тестирование, это плохой дизайн. У вашего юнита / класса есть зависимость от времени, если нет способа смоделировать время, тогда «юнит» не является юнитом. Для модульного тестирования вы должны иметь возможность влиять на все внешние зависимости, которые влияют на поток управления. На практике это означает, что существует класс, отвечающий за выбор необходимой информации, который принимает некоторый делегат, отвечающий за время. Это может быть объект календаря или что-то подобное.

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

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

0 голосов
/ 04 июня 2009

Если тестовые данные изменяются, это означает, что это своего рода «живая» база данных. Зачем вам проводить тест на такой базе данных? Если результаты тестов не совпадают между двумя прогонами, это идет вразрез с целью теста, модульный тест должен быть повторяемым и надежным, если вы переписываете его каждые пару месяцев, это не то же самое ...

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

0 голосов
/ 04 июня 2009

Перечитав ваш вопрос, я не уверен, что вы пытаетесь проверить.

  1. Вы пытаетесь проверить, что база данных возвращает правильные данные для запроса, который вы выполняете?
  2. Вы пытаетесь проверить, что ваш код генерирует соответствующий запрос?
  3. Вы пытаетесь проверить, правильно ли ваш код обрабатывает возвращаемые данные?

Если # 1, то, возможно, вы могли бы использовать базу данных в памяти, которую вы могли бы инициализировать в своем модульном тесте с фиксированными данными. Я использовал H2 раньше для этой цели. Поиск встроенных баз данных.

Если # 2 или # 3, вы должны иметь возможность фиксировать данные для теста и получать известные результаты. Если встроенная генерация SQL или обработка результатов встроены, вы можете использовать макет для макетирования методов приема.

Например, у меня обычно есть класс QueryRunner, который принимает SQL, который я хочу запустить, и класс, который он будет вызывать для обработки набора результатов. Таким образом я могу смоделировать QueryRunner и посмотреть, вызывается ли он с ожидаемым SQL-кодом и вызывается ли он соответствующее количество раз. Гораздо проще, чем пытаться издеваться над классами JDBC.

0 голосов
/ 04 июня 2009

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

1) Откатить / переслать часы системы.

2) Обновите дату на тестовых данных, чтобы она всегда была в пределах вашего диапазона (т. Е. Обновите дату установки базы данных = now ()).

3) Запустите запрос в чистой системе, затем примените изменения и снова выполните запрос. Это, наверное, самый простой способ.

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