[Перевод] Можно ли совместить юнит тесты и профиляцию памяти?13.05.2015 12:47
Профиляторы памяти с трудом можно назвать «утилитами для ежедневного использования». Чаще всего разработчики задумываются о профиляции своего продукта перед самым релизом. Подобный подход вполне может работать, но лишь до тех пор, пока какая-нибудь проблема с памятью, обнаруженная в последний момент (например, утечка памяти или большой трафик памяти) не разрушит все ваши планы. Одним из решений могла бы быть профиляция на регулярной основе, но вряд ли кто-то захочет тратить на это столь драгоценное время. Тем не менее, решение кажется есть.Если юнит-тестирование — неотъемлемая часть вашего процесса разработки, значит вы регулярно запускаете многочисленные тесты проверяющие функциональность приложения. А теперь представьте, что вы можете написать некие специальные «тесты на использование памяти». Например, тест обнаруживающий утечку при помощи проверки памяти на наличие объектов определенного типа, или тест который отслеживает трафик памяти и «падает», если трафик (аллоцированный объем) превысит заданный порог. Это в точности то, что позволяет делать dotMemory Unit фреймворк. dotMemory Unit распространяется в виде NuGet пакета и позволяет выполнять следующие сценарии: Проверка памяти на наличие объектов определенного типа.
Проверка трафика памяти.
Сравнение снимков (далее 'снэпшотов') памяти.
Сохранение снэпшотов на диск с целью последующего анализа в dotMemory (профиляторе памяти от JetBrains).
Иными словами, dotMemory Unit расширяет возможности вашего юнит-тестинг фреймворка функциональностью профилятора памяти.Как это работает? Пример 1: Проверка памяти на наличие определенных объектов
Давайте начнем с чего-нибудь простого. Один из наиболее полезных сценариев — это определение утечки путем проверки памяти на наличие объектов определенного типа.
[Test]
public void TestMethod1()
{
… // делаем что-нибудь
// предполагаем, что в памяти осталось 0 объектов типа Foo
dotMemory.Check (memory => //1, 2
{
Assert.That (memory.GetObjects (where => where.Type.Is()).ObjectsCount, Is.EqualTo (0)); //3
});
}
Лямбда передается в метод Check статического класса dotMemory. Этот метод будет вызван только в том случае, если вы запустите этот тест при помощи меню Run Unit Tests under dotMemory Unit.
Объект memory передаваемый в лямбду содержит данные обо всех объектах в памяти в текущей точке выполнения программы.
Метод GetObjects возвращает набор объектов соответствующих условию передаваемому в очередной лямбде. Наприме, данная строка кода выбирает из памяти только объекты типа Foo. Выражение Assert предполагает, что в памяти должно быть 0 объектов типа Foo.Обратите внимание, что dotMemory Unit не обязывает вас использовать какой-то определенный синтаксис для Assert. Просто используйте синтаксис того фреймворка, для которого написан ваш тест. Например, строчка из прмера выше (написана для NUnit) может быть переписана для MSTest:
Assert.AreEqual (0, memory.GetObjects (where => where.Type.Is()).ObjectsCount);
dotMemory Unit позволяет выбирать объекты практически по любому условию, получать данные по количеству объектов и использовать их в Assert выражениях. Например, можно убедиться что Large object heap не содержит объектов:
Assert.That (memory.GetObjects (where => where.Generation.Is (Generation.Loh)).ObjectsCount, Is.EqualTo (0));
Пример 2: Проверка трафика памяти
Тест для проверки трафика памяти (аллоцированного объема данных) выглядит еще проще. Все что от вас требуется, это «пометить» тест при помощи аттрибута AssertTraffic. В следующем примере, мы предполагаем, что объем памяти аллоцированной тестом TestMethod1 не превышает 1000 байт.
[AssertTraffic (AllocatedMemoryAmount = 1000)]
[Test]
public void TestMethod1()
{
… // какой-то код
}
Пример 3: Сложные сценарии проверки трафика памяти
Если вам нужна более подробная информация о трафике (например, данные об аллокациях объектов определенного типа), вы можете использовать подход схожий с тем, что показан в примере 1. Лямбды передаваемые в метод dotMemory.Check позволяют фильтровать данные по всевозможным условиям.
var memoryCheckPoint1 = dotMemory.Check (); // 1
foo.Bar ();
var memoryCheckPoint2 = dotMemory.Check (memory =>
{
// 2
Assert.That (memory.GetTrafficFrom (memoryCheckPoint1).Where (obj => obj.Interface.Is()).AllocatedMemory.SizeInBytes,
Is.LessThan (1000));
});
bar.Foo ();
dotMemory.Check (memory =>
{
// 3
Assert.That (memory.GetTrafficFrom (memoryCheckPoint2).Where (obj => obj.Type.Is()).AllocatedMemory.ObjectsCount,
Is.LessThan (10));
});
Для того, чтобы отметить временной промежуток на котором вы хотите анализировать трафик, используйте «чекпойнты», создаваемые все тем же методом dotMemory.Check (как вы возможно догадались, этот метод просто снимает снэпшот памяти в момент вызова).
Чекпоинт, определяющий начальную точку интервала, передается в метод GetTrafficFrom.Например, данная строка предполагает что общий размер объектов имплементирующих интерфейс IFoo и созданных на промежутке между memoryCheckPoint1 и memoryCheckPoint2 не превышает 1000 байт.
Вы можете получать данные относительно любого из ранее созданных чекпоинтов. Так, данная строка запрашивает данные о трафике между текущим вызовом dotMemory.Check и memoryCheckPoint2.
Пример 4: Сравнение снэпшотов
Также как и во «взрослом» профиляторе dotMemory, вы можете использовать чекпоинты не только для анализа трафика, но и для сравнения их друг с другом. В примере ниже, мы предполагаем, что ни один из объктов принадлежащих пространству имен MyApp не пережил сборку мусора в интервале между memoryCheckPoint1 и вторым вызовом dotMemory.Check.
var memoryCheckPoint1 = dotMemory.Check ();
foo.Bar ();
dotMemory.Check (memory =>
{
Assert.That (memory.GetDifference (memoryCheckPoint1)
.GetSurvivedObjects ().GetObjects (where => where.Namespace.Like («MyApp»)).ObjectsCount, Is.EqualTo (0));
});
Заключение
dotMemory Unit очень гибок и позволяет вам полнсотью контролировать использование памяти вашим приложением. Используйте «тесты для памяти» также как вы используете обычные тесты: После того, как вы самостоятельно обнаружите утечку памяти, напишите тест, которые покрывает эту часть кода.
Пишите интеграционные тесты с использованием dotMemory Unit, чтобы убедиться, что новые «фичи» не создают проблем с памятью.
© Habrahabr.ru