Как тестировать код финализатора (c#)

от автора

Одной из не очевидных задач, является тестирование кода, реализованного в финализаторе дотнетовского класса.
Данная заметка рассматривает один из способов решения этой задачи.


Например, есть классс TemporaryFile (временный файл), который создает уникальный временный файл в конструкторе и должен удалять его в Dipose() или в финализаторе.

    public class MyTemporaryFile : IDisposable     {         public string FileName { private set; get; }         public MyTemporaryFile()         {             FileName = Path.GetTempFileName();         }          public void Dispose()         {             Dispose(true);         }         ~MyTemporaryFile()         {             Dispose(false);         }          void Dispose(bool disposing)         {             if (disposing)             {                 GC.SuppressFinalize(this);             }             DeleteFile();         }         void DeleteFile()         {             if (FileName != null)             {                 File.Delete(FileName);                 FileName = null;             }         }     }  

Реализация паттерна Dispose довольно стандартная и обсуждалась на Хабре. Наверняка есть в данной реализации некоторые тонкие места, поэтому в «настоящей» программе имейте это ввиду.

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

Понятно, что «очень наивная» реализация теста работать не будет.

        [Test]         public void TestMyTemporaryFile_without_Dispose()         {             var temporaryFile = new MyTemporaryFile();             string createdTemporaryFileName = temporaryFile.FileName;              Assert.IsTrue(File.Exists(createdTemporaryFileName));              temporaryFile = null;                          Assert.IsFalse(File.Exists(createdTemporaryFileName));         } 

Дело в том, что присвоение null переменной temporaryFile не вызывает финализатор.

Встречался совет вызывать GC.WaitForPendingFinalizers();, но почему то в данном тесте мне это не помогло.

offtopic: Когда то давно на какой то лекции по c# рассказывали про AppDomain. Я тогда не очень понимал зачем мне это надо. Ну вы знаете, как большинство лекторов рассказывают для "некого среднего слушателя" "некие общие вещи". Я ни разу не смог понять паттерн Dispose со слов лектора. Самое смешное, что после того, как я стал его чуть чуть понимать, я с трудом стал догадываться, что лектор таки имеет ввиду.

Так вот, оказывается, что с помощью AppDomain можно легко приготовить тест для кода финализатора:

        [Test]         public void TestTemporaryFile_without_Dispose()         {             const string DOMAIN_NAME = "testDomain";             const string FILENAME_KEY = "fileName";              string testRoot = Directory.GetCurrentDirectory();              AppDomainSetup info = new AppDomainSetup                 {                     ApplicationBase = testRoot                 };             AppDomain testDomain = AppDomain.CreateDomain(DOMAIN_NAME, null, info);             testDomain.DoCallBack(delegate             {                 MyTemporaryFile temporaryFile = new MyTemporaryFile();                 Assert.IsTrue(File.Exists(temporaryFile.FileName));                 AppDomain.CurrentDomain.SetData(FILENAME_KEY, temporaryFile.FileName);             });             string createdTemporaryFileName = (string)testDomain.GetData(FILENAME_KEY);             Assert.IsTrue(File.Exists(createdTemporaryFileName));                AppDomain.Unload(testDomain);       // выгружается код и очищается вся память (вызывается финализатор), файл удаляется              Assert.IsFalse(File.Exists(createdTemporaryFileName));          } 

Как известно, AppDomain.Unload(testDomain); выгружает код и очищает память (в том числе и вызываются финализаторы).
Это и помогает «насильно вызвать» финализатор и, соответсвенно, протестировать его код.

Примечания:
1. Один из лекторов советовал в финализаторе выкидывать исключительную ситуацию (exception) со словами "вызови Dispose, идиот". Где то он может быть и прав, но если есть unmanaged ресурс, надо предусмотреть и финализатор тоже.
2. Реализация класса MyTemporaryFile очень схематична и не рекомендуется для продакшен использования.
3. Скорей всего реализация данного теста, тоже имеет всякие тонкие моменты, но многолетняя практика ни разу не зафиксировала ложное срабатывание этого теста.
4. С удовольствием почитаю, как можно решить задачу тестирования финализатора другими способами или какие есть недостатки у данного подхода.

Спасибо,
Игорь.

ссылка на оригинал статьи http://habrahabr.ru/post/182702/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *