Если юнит-тестирование — неотъемлемая часть вашего процесса разработки, значит вы регулярно запускаете многочисленные тесты проверяющие функциональность приложения. А теперь представьте, что вы можете написать некие специальные «тесты на использование памяти». Например, тест, обнаруживающий утечку при помощи проверки памяти на наличие объектов определенного типа, или тест, который отслеживает трафик памяти и «падает», если трафик (аллоцированный объем) превысит заданный порог. Это в точности то, что позволяет делать dotMemory Unit фреймворк. dotMemory Unit распространяется в виде NuGet пакета и позволяет выполнять следующие сценарии:
- Проверка памяти на наличие объектов определенного типа.
- Проверка трафика памяти.
- Сравнение снимков (далее 'снэпшотов') памяти.
- Сохранение снэпшотов на диск с целью последующего анализа в dotMemory (профиляторе памяти от JetBrains).
Иными словами, dotMemory Unit расширяет возможности вашего юнит-тестинг фреймворка функциональностью профилятора памяти.
Как это работает?
- dotMemory Unit распространяется как NuGet пакет устанавливаемый в ваш тест проект:
PM> Install-Package JetBrains.DotMemoryUnit
- dotMemory Unit требуется юнит-тест 'раннер', входящий в состав ReSharper. Поэтому для запуска dotMemory Unit тестов, на вашей машине должен быть установлен ReSharper 9.1 или dotCover 3.1.
- После установки dotMemory Unit пакета, в меню ReSharper появится дополнительный пункт Run Unit Tests under dotMemory Unit. В этом режиме, тест раннер будет выполнять вызовы dotMemory Unit наряду с остальным кодом. Если вы запустите этот тест как обычно (без поддержки dotMemory Unit), все вызовы dotMemory Unit фреймворка будут проигнорированы.
- dotMemory Unit совместим со всеми юнит-тестинг фреймворками поддерживаемыми ReSharper, в том числе MSTest и NUnit.
- Отдельный 'лаунчер' для интеграции с CI системами по типу JetBrains TeamCity запланирован в одном из последующих релизов.
- dotMemory Unit абсолютно бесплатен.
Пример 1: Проверка памяти на наличие определенных объектов
Давайте начнем с чего-нибудь простого. Один из наиболее полезных сценариев — это определение утечки путем проверки памяти на наличие объектов определенного типа.
[Test]
public void TestMethod1()
{
... // делаем что-нибудь
// предполагаем, что в памяти осталось 0 объектов типа Foo
dotMemory.Check(memory => //1, 2
{
Assert.That(memory.GetObjects(where => where.Type.Is<Foo>()).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<Foo>()).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<IFoo>()).AllocatedMemory.SizeInBytes,
Is.LessThan(1000));
});
bar.Foo();
dotMemory.Check(memory =>
{
// 3
Assert.That(memory.GetTrafficFrom(memoryCheckPoint2).Where(obj => obj.Type.Is<Bar>()).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, чтобы убедиться, что новые «фичи» не создают проблем с памятью.
Комментарии (13)
xtraroman
13.05.2015 11:12+5Интересная статья, но dotMemory Unit не рекомендуют совмещать с unit тестами, потому что обычно unit тесты гоняют под конфигурацией Debug а dotMemory Unit больше любит Release конфигурацию. А еще статья расширила мой лексикон такими словами как: профиляция, профилятор :).
techdoc Автор
13.05.2015 11:41эмм, я так понимаю это не проблема dotMemory Unit. Просто в Debug билдах, те объекты на которые ссылается отладчик не будут собраны во время GC (даже если они уже не нужны самой программе). Собсно поэтому с точки зрения потребления памяти смотреть надо всегда на Release билды.
yurash
13.05.2015 14:04+3Извините, но в статье-переводе я на «профиляцию» не согласен. Давайте все вместе:
память профилировали, профилировали да невыпрофилировали, надо память перепрофилировать, перевыпрофилироватьandreycha
13.05.2015 15:45Это у них в JetBrains такой сленг. Посмотрите их доклады с .NEXT, там то же самое :).
Serg2DFX
13.05.2015 18:36-1Как это интегрируется с CI?
Если я правильно понимаю, то на агенты построений придётся поставить ReSharper или dotCover.
Поэтому для запуска dotMemory Unit тестов, на вашей машине должен быть устанолен ReSharper 9.1 или dotCover 3.1.
Перестанут ли работать тесты после завершения триала?
Razbezhkin
Спасибо. очень полезная, должно быть, штука.
А как вообще через код C# можно определить, какие объекты находятся в памяти?
Dywar
Если почитать два последних поста от Jet, то в них можно найти видео.
В одном из которых автор доклада бегает по куче и считает объекты используя C# unsafe.
Можно использовать windbg с расширением SOS.dll, SOSEX.dll.
Запускаем программу, цепляемся в нужном месте, и смотрим SOH (0,1,2) и LOH.
Razbezhkin
Спасибо. Не могли бы привести пример, если не затруднит.
Dywar
1) Человек из JetBrains.
www.youtube.com/watch?v=KbuJdkPLAjw
2) Windbg.
Если попытаться загрузить sos при старте приложения ничего не выйдет, надо аттачиться когда процесс уже прошел первичную инициализацию.