2010-06-21 3 views
6

Недавно я спросил (и ответил) на вопрос о StackOverflow о том, почему единичный тест будет работать при запуске сам по себе, а затем неудачно спорадически запускается со всей партией модульных тестов. См. Здесь: SQL Server and TransactionScope (with MSDTC): Sporadically can't get connectionКак сборщик мусора работает с модульными испытаниями?

Тестирование модулей при запуске по одному, а затем сбой при запуске - это классический признак того, что с кодом что-то серьезно не так.

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

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

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

Как я могу это объяснить?

Обновление: Тем, кто вас спрашивает о специфике кода, это довольно просто. Я объявляю новый объект TransactionScope в своем методе установки и удаляю его в моем методе Teardown. Тем не менее, проблемным тестом был тест, основанный на данных, с 100 тестовыми примерами; тестируемый код заполнил объект SqlDataReader из оператора select, используя класс SqlHelper, а затем не вызвал метод закрытия на SqlDataReader. Поскольку я использовал класс SqlHelper для получения SqlDataReader, я ожидал, что соединения будут обработаны для меня. Не так!

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

Обновление: Что я знаю о сборке мусора с помощью Unit Tests. Следуя моему собственному любопытству, я вытащил модульные тесты, которые терпели неудачу, потому что соединение было оставлено открытым объектом SqlDataReader. Я попытался добавить System.GC.Collect() в конце каждого теста. Это успешно освободило соединения, но накладывает штраф на 50%.

+0

Если у вас заканчиваются соединения, потому что вы не выпускаете достаточно их между тестами, я подозреваю, что есть проблемы с вашими методами срыва. При запуске их по одному ОС ОС позаботится о каком-то разрыве, но запуститься как набор, использование ресурсов сохраняется дольше. – MikeD

+1

Возможно, вам просто нужно еще несколько вызовов 'Dispose' или' Close'? – Gabe

ответ

1

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

+0

Действительно?Можете ли вы указать мне какую-то документацию, в которой показано, что GC обычно работает по собственной теме? –

+1

http://stackoverflow.com/questions/318462/how-to-identify-the-gc-finalizer-thread –

+0

GC может фактически остановить все потоки ненадолго, но он также запускает финализаторы в потоке, как объяснено в ссылке. –

3

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

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

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

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

+0

Jon, в случае, если вы его пропустили, Lucero упомянул, что, по-видимому, он выгружает весь AppDomain, который должен избавиться от любых утечек. –

2

Обычно каждый тестовый прогон выполняется в отдельном домене по нескольким причинам. Теперь, когда appdomain будет выгружен, он освободит связанные с ним ресурсы, чтобы открытые соединения были закрыты и, следовательно, предотвратили проявление «утечки».

См. Также Cbrumme's blog on this topic.

+0

А, ок. Если он выгружает AppDomain, тогда нет необходимости принудительно собирать мусор. –

+0

Точно. Поскольку единственный способ разгрузить сборки состоит в том, чтобы иметь их в отдельном домене приложения и выгружать appdomain, «очистка утечки ресурсов» является побочным эффектом. – Lucero

+0

Ну, это побочный эффект зависит от ваших намерений. Я должен был использовать AppDomains для очистки просочившихся ресурсов, при этом разгрузка сборок была побочным эффектом. :-) –

1

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

Я думаю, что есть что-то серьезно неправильное в том, как вы написали модульные тесты. Каждый тест должен выполняться независимо от других тестов. Один из способов сделать это - убедиться, что у вас есть методы настройки и удаления ([SetUp][TearDown]), которые создают и очищают среду, необходимую для запуска теста.

В вашем методе установки вы создаете свое соединение, в вашем методе слежения вы его уничтожаете. Теперь перед запуском каждого теста вызывается метод Setup, и после каждого теста будет вызываться метод teardown, и это обеспечит утечку каких-либо ресурсов.

0

Ничего себе, несколько вопросов здесь!

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

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

В-третьих, вам действительно не нужно создавать TransactionScope в своих модульных тестах. Для меня это не имеет смысла. Что-то не так с стилем кодирования, который вы используете в своем тестовом коде. Модульные тесты - это не просто автоматические тесты, такие как интеграционные тесты или системные тесты. Единичные тесты - это небольшие и целенаправленные тесты, которые проверяют поведение SMALL-кода производственного кода в ISOLATION, независимо от всего другого производственного кода.

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

+0

Как вы можете использовать 'SqlDataReader' с' using'. У него нет метода 'dispose()'. –

+0

Чувак, он просто открывает ваш браузер объектов и проверяет его, он наследует одноразовый DbDataReader - см. Объявление типа ниже. Кстати, большинство из этих типов связанных с базой данных соединений должны быть одноразовыми, поскольку они обертывают различные ресурсы ОС, которые распределяются при использовании. Закрытие считывателя фактически уничтожает его. Преимущество использования-операторов заключается в том, что они располагаются, даже если выбрано исключение, в сущности, сахар для try {...} finally {foo.Dispose();} Открытый класс SqlDataReader: DbDataReader, IDataReader, IDisposable, IDataRecord – Mahol25