単体テストとメモリプロファイリングを組み合わせることはできますか?

メモリプロファイラは、「日常的に使用するユーティリティ」とはほとんど言えません。 ほとんどの場合、開発者はリリース前に製品のプロファイリングを検討します。 そのようなアプローチはうまくいくかもしれませんが、最後の瞬間に何らかのメモリの問題(メモリリークや大量のメモリトラフィックなど)が発見されるまで、すべての計画が破壊されます。 1つの解決策は定期的なプロファイリングかもしれませんが、そのような貴重な時間を費やすことを望む人はほとんどいません。 ただし、解決策があるようです。

単体テストが開発プロセスの不可欠な部分である場合、アプリケーションの機能を検証するために多数のテストを定期的に実行します。 ここで、特別な「メモリテスト」を作成できると想像してください。 たとえば、特定の種類のオブジェクトの存在をメモリで確認してリークを検出するテスト、またはメモリトラフィックを監視し、トラフィック(割り当てられたボリューム)が指定されたしきい値を超えると「ドロップ」するテスト。 これは、 dotMemory Unitフレームワークでできることです。 dotMemory UnitはNuGetパッケージとして配布されており、次のスクリプトを実行できます。

つまり、dotMemory Unitは、メモリプロファイラーの機能を使用してユニットテストフレームワークの機能を拡張します。

どのように機能しますか?



例1:特定のオブジェクトのメモリを確認する


簡単なものから始めましょう。 最も有用なシナリオの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 }); } 

  1. ラムダは、 dotMemory静的クラスのCheckメソッドに渡されます。 このメソッドは、dotMemory UnitメニューのRun Unit Testsを使用してこのテストを実行する場合にのみ呼び出されます
  2. ラムダに渡されるmemoryオブジェクトには、プログラム実行の現在の時点でメモリ内のすべてのオブジェクトのデータが含まれています。
  3. GetObjectsメソッドは、次のラムダで渡された条件に一致するオブジェクトのセットを返します。 たとえば、次のコード行は、メモリからFoo型のオブジェクトのみを選択します。 Assert式は、メモリ内にFoo型のオブジェクトが0個あることを前提としています。

    dotMemoryユニットはAssert特定の構文を使用する義務を負わないことに注意してください。 テストの対象となるフレームワークの構文を使用するだけです。 たとえば、上記の例の行(NUnit用に記述)は、MSTest用に書き直すことができます。
      Assert.AreEqual(0, memory.GetObjects(where => where.Type.Is<Foo>()).ObjectsCount); 


dotMemory Unitを使用すると、ほとんどすべての条件でオブジェクトを選択し、オブジェクトの数だけデータを受信し、それらをAssert式で使用できます。 たとえば、ラージオブジェクトヒープにオブジェクトが含まれていないことを確認できます。
  Assert.That(memory.GetObjects(where => where.Generation.Is(Generation.Loh)).ObjectsCount, Is.EqualTo(0)); 


例2:メモリトラフィックの確認


メモリトラフィック(割り当てられたデータボリューム)を確認するためのテストは、さらに簡単に見えます。 必要なのは、 AssertTraffic属性を使用してテストを「マーク」することAssertTrafficです。 次の例では、 TestMethod1によって割り当てられたメモリが1000バイトを超えないと仮定します。
 [AssertTraffic(AllocatedMemoryAmount = 1000)] [Test] public void TestMethod1() { ... // -  } 


例3:メモリトラフィックをチェックするための複雑なスクリプト


トラフィックに関するより詳細な情報(たとえば、特定のタイプのオブジェクトの割り当てに関するデータ)が必要な場合は、例1に示すようなアプローチを使用dotMemory.CheckますdotMemory.Checkメソッドに渡されるdotMemory.Checkを使用すると、さまざまな条件に従ってデータをフィルター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)); }); 

  1. トラフィックを分析する期間をマークするには、同じdotMemory.Checkメソッドで作成された「チェックポイント」を使用します(ごdotMemory.Check 、このメソッドは呼び出し時にメモリスナップショットを単に削除します)。
  2. 間隔の開始点を定義するチェックポイントは、 GetTrafficFromメソッドに渡されます。
    たとえば、この行は、 IFooインターフェイスを実装し、 memoryCheckPoint1memoryCheckPoint2間に作成されたオブジェクトの合計サイズが1000バイトを超えないことをmemoryCheckPoint2しています。
  3. 以前に作成されたチェックポイントのいずれかでデータを受信できます。 したがって、この行は、 dotMemory.CheckmemoryCheckPoint2現在の呼び出し間のトラフィックデータを要求します。


例4:スナップショットの比較


「アダルト」dotMemoryプロファイラーと同様に、チェックポイントを使用して、トラフィックを分析するだけでなく、それらを相互に比較することもできます。 以下の例では、 memoryCheckPoint1memoryCheckPoint1の2番目の呼び出しの間の間隔で、 MyApp名前空間内のオブジェクトがガベージコレクションを生き残っていないと仮定しMyApp
  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は非常に柔軟性があり、アプリケーションのメモリ使用量を完全に制御できます。 通常のテストを使用するときは、「メモリのテスト」を使用します。

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


All Articles