Как сборщик мусора работает с юнит-тестами? - PullRequest
6 голосов
/ 22 июня 2010

Недавно я задал (и ответил) вопрос на StackOverflow о том, почему модульное тестирование будет работать при запуске само по себе, а затем сбоит время от времени при запуске с целым пакетом модульных тестов.См. Здесь: SQL Server и TransactionScope (с MSDTC): время от времени не удается получить соединение

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

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

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

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

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

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

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

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

Ответы [ 5 ]

3 голосов
/ 22 июня 2010

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

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

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

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

2 голосов
/ 22 июня 2010

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

См. Также Блог Cbrumme по этой теме .

2 голосов
/ 22 июня 2010

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

1 голос
/ 22 июня 2010

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

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

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

0 голосов
/ 02 июля 2010

Ух, несколько вопросов здесь!

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

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

В-третьих, вам действительно не нужно создавать TransactionScope в своих модульных тестах. Это не имеет смысла для меня. Что-то не так со стилем кодирования, который вы используете в своем тестовом коде. Модульные тесты - это не просто автоматические тесты, такие как интеграционные тесты или системные тесты. Модульные тесты - это небольшие и сфокусированные тесты, которые тестируют поведение МАЛЕНЬКОГО фрагмента производственного кода в ISOLATION, независимо от всего другого производственного кода.

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

using (SqlDataReader reader = ...)
{
   ...
}
...