📜 ⬆️ ⬇️

Is it possible to combine unit tests and memory profiling?

Memory profiling can hardly be called "utilities for daily use." Most often, developers are thinking about profiling their product before the release itself. Such an approach may well work, but only as long as any memory problem that was detected at the last moment (for example, a memory leak or large memory traffic) does not destroy all your plans. One solution could be profiling on a regular basis, but hardly anyone would want to spend such valuable time on it. However, the solution seems to be there.

If unit testing is an integral part of your development process, then you regularly run numerous tests that test the functionality of the application. Now imagine that you can write some special "tests for memory use." For example, a test that detects a leak by checking the memory for the presence of objects of a certain type, or a test that monitors memory traffic and “drops” if the traffic (alloted volume) exceeds a specified threshold. This is exactly what the dotMemory Unit framework allows you to do. The dotMemory Unit is distributed as a NuGet package and allows you to run the following scripts:

In other words, the dotMemory Unit extends the capabilities of your unit-testing framework with the functionality of a memory profiler.

How it works?



Example 1: Checking memory for certain objects


Let's start with something simple. One of the most useful scenarios is leak detection by checking memory for objects of a certain type.
 [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 }); } 

  1. Lambda is passed to the Check method of the static dotMemory class. This method will only be called if you run this test using the Run Unit Tests menu under the dot Unit Unit .
  2. The memory object passed to the lambda contains data about all objects in memory at the current program execution point.
  3. The GetObjects method returns a set of objects corresponding to the condition passed in the next lambda. For example, this line of code selects only objects of type Foo from memory. The Assert expression assumes that there must be 0 objects of type Foo in memory.

    Please note that the dotMemory Unit does not oblige you to use any specific syntax for Assert . Just use the syntax of the framework for which your test was written. For example, the line from the example above (written for NUnit) can be rewritten for MSTest:
      Assert.AreEqual(0, memory.GetObjects(where => where.Type.Is<Foo>()).ObjectsCount); 


dotMemory Unit allows you to select objects for almost any condition, get data on the number of objects and use them in Assert expressions. For example, you can make sure that the Large object heap contains no objects:
  Assert.That(memory.GetObjects(where => where.Generation.Is(Generation.Loh)).ObjectsCount, Is.EqualTo(0)); 

')

Example 2: Check Memory Traffic


The test for checking memory traffic (allocated data volume) looks even easier. All that is required of you is to “tag” the test with the AssertTraffic attribute. In the following example, we assume that the memory allocated by TestMethod1 does not exceed 1000 bytes.
 [AssertTraffic(AllocatedMemoryAmount = 1000)] [Test] public void TestMethod1() { ... // -  } 


Example 3: Complicated scripts for checking memory traffic


If you need more detailed information about traffic (for example, data about allocations of objects of a particular type), you can use an approach similar to that shown in Example 1. The lambdas passed to the dotMemory.Check method dotMemory.Check you to filter data according to various conditions.
 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)); }); 

  1. In order to mark the time interval on which you want to analyze traffic, use the “checkpoint” created by the same dotMemory.Check method (as you may have guessed, this method simply takes a snapshot of the memory at the time of the call).
  2. The checkpoint defining the starting point of the interval is passed to the GetTrafficFrom method.
    For example, this line assumes that the total size of objects implementing the interface IFoo and created between memoryCheckPoint1 and memoryCheckPoint2 does not exceed 1000 bytes.
  3. You can get data on any of the previously created checkpoints. So, this line requests traffic data between the current dotMemory.Check call and memoryCheckPoint2 .


Example 4: Comparing Snapshots


Just as in the adult profile dotMemory, you can use checkpoints not only to analyze traffic, but also to compare them with each other. In the example below, we assume that none of the objects belonging to the MyApp namespace survive the garbage collection in the interval between memoryCheckPoint1 and the second call to 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)); }); 


Conclusion


The dotMemory Unit is very flexible and allows you to completely control the memory usage of your application. Use the "memory tests" as you use the normal tests:

Source: https://habr.com/ru/post/257751/


All Articles