このユニットテストについて混乱しています!
-
29-09-2019 - |
質問
だから基本的に、私は一意の増分IDを持つ抽象クラスを持っています - Primitive
. 。 a Primitive
(より正確には、の継承者 Primitive
)インスタンス化され、IDがインクリメントされます - IDがオーバーフローするポイントまで - その時点で、例外にメッセージを追加します。
わかりました、それはすべて正常に動作します...しかし、私はこの機能をテストしようとしていますが、これまでにモッキングを使用したことはありません。 IDがオーバーフローし、適切なタイミングでスローすることを主張するために十分なプリミティブを作成する必要があります。
- これを行うために20億のオブジェクトをインスタンス化することは不合理です!しかし、私は別の方法を見ません。
- モッキングを正しく使用しているかどうかはわかりませんか? (私は使用しています
Moq
.)
これが私のテストです(xUnit
):
[Fact(DisplayName = "Test Primitive count limit")]
public void TestPrimitiveCountLimit()
{
Assert.Throws(typeof(OverflowException), delegate()
{
for (; ; )
{
var mock = new Mock<Primitive>();
}
});
}
と:
public abstract class Primitive
{
internal int Id { get; private set; }
private static int? _previousId;
protected Primitive()
{
try
{
_previousId = Id = checked (++_previousId) ?? 0;
}
catch (OverflowException ex)
{
throw new OverflowException("Cannot instantiate more than (int.MaxValue) unique primitives.", ex);
}
}
}
私はそれを間違っていると思います - それで、これを適切にテストするにはどうすればよいですか?
解決
これにはock笑は必要ありません。 2つのクラスが一緒に動作するときにモッキングを使用し、1つのクラスをモック(偽)に置き換えて、もう1つのクラスをテストするだけで使用する必要があります。これはあなたの例ではそうではありません。
ただし、モックを使用する方法があり、2BLNインスタンスで問題を修正します。 ID生成をから分離した場合 Primitive
クラスとジェネレーターを使用すると、ジェネレーターをock笑できます。例:
私は変わった Primitive
提供されたジェネレーターを使用します。この場合、それは静的変数に設定されており、より良い方法がありますが、例として:
public abstract class Primitive
{
internal static IPrimitiveIDGenerator Generator;
protected Primitive()
{
Id = Generator.GetNext();
}
internal int Id { get; private set; }
}
public interface IPrimitiveIDGenerator
{
int GetNext();
}
public class PrimitiveIDGenerator : IPrimitiveIDGenerator
{
private int? _previousId;
public int GetNext()
{
try
{
_previousId = checked(++_previousId) ?? 0;
return _previousId.Value;
}
catch (OverflowException ex)
{
throw new OverflowException("Cannot instantiate more than (int.MaxValue) unique primitives.", ex);
}
}
}
次に、テストケースは次のようになります。
[Fact(DisplayName = "Test Primitive count limit")]
public void TestPrimitiveCountLimit()
{
Assert.Throws(typeof(OverflowException), delegate()
{
var generator = new PrimitiveIDGenerator();
for (; ; )
{
generator.GetNext();
}
});
}
これははるかに速く実行され、IDジェネレーターが機能するかどうかをテストするだけです。
さて、たとえば、新しいプリミティブを作成することで実際にIDを要求することをテストしたい場合は、以下を試すことができます。
public void Does_primitive_ask_for_an_ID()
{
var generator = new Mock<IPrimitiveIDGenerator>();
// Set the expectations on the mock so that it checks that
// GetNext is called. How depends on what mock framework you're using.
Primitive.Generator = generator;
new ChildOfPrimitive();
}
これで、さまざまな懸念を分離し、個別にテストできます。
他のヒント
モックのポイントは、外部リソースをシミュレートすることです。それはあなたが望むものではありません、あなたはテストしたい あなたの オブジェクト、このセナリオでは模擬は必要ありません。必要に応じて20億個のオブジェクトをインスタンス化するだけで、GCが古いインスタンスを捨てるので、それは傷つきません(ただし、完了するまでに時間がかかる場合があります)。
id '実際にアイデンティティカウンターのストラート値を受け入れる別のコンストラクターを追加して、実際に近くで開始できるように int.MaxValue
したがって、多くのオブジェクトを設置する必要はありません。
また、ソースを読んでから、オブジェクトがテストに失敗することがわかります。 ;-)
この質問に焼き付けられた2つの問題があります。
- インスタンス化できない抽象クラスをユニットテストする方法。
- 20億インスタンスを作成および破壊する必要がある機能を効率的に単位テストする方法。
オブジェクトの構造をわずかに再考する必要があるにもかかわらず、ソリューションは非常に簡単だと思います。
最初の問題については、解決策は継承する偽物を追加するのと同じくらい簡単です Primitive
, 、しかし、テストプロジェクトに機能を追加しません。その後、代わりに偽のクラスをインスタンス化することができますが、の機能をテストしています Primitive
.
public class Fake : Primitive { }
// and in your test...
Assert.Throws(typeof(OverflowException), delegate() { var f = new Fake(int.MaxValue); });
2番目の問題については、 int
以前のIDの場合、実際のコードで「必要ない」ようにコンストラクターを使用します。 (しかし、それ以外の場合は以前のIDを知る方法はありますか? int.MaxValue-1
テストのセットアップでは?)それは依存関係の注入と考えていますが、複雑なものを注入していません。あなたは単純なものを注入するだけです int
. 。それはこれらの線に沿って何かかもしれません:
public abstract class Primitive
{
internal int Id { get; private set; }
private static int? _previousId;
protected Primitive() : Primitive([some way you get your previous id now...])
protected Primitive(int previousId)
{
_previousId = previousId;
try
{
_previousId = Id = checked (++_previousId) ?? 0;
}
catch (OverflowException ex)
{
throw new OverflowException("Cannot instantiate more than (int.MaxValue) unique primitives.", ex);
}
}
すべてが他の答えで言われています。私はあなたに代替品を見せたいだけです、多分これはあなたにとって何らかの形で面白いです。
あなたが_を作った場合previousId
あなたのフィールド Primitive
クラス internal
(それぞれが含まれています InternalsVisibleTo
もちろん、属性)、あなたのテストはこれと同じくらい簡単になる可能性があります Typemock Isolator 道具:
[Fact(DisplayName = "Test Primitive count limit"), Isolated]
public void TestPrimitiveCountLimit()
{
Primitive._previousId = int.MaxValue;
Assert.Throws<OverflowException>(() =>
Isolate.Fake.Instance<Primitive>(Members.CallOriginal, ConstructorWillBe.Called));
}
確かに、TypeMockにはいくつかのライセンスコストが付いていますが、特にテストを簡単にテストしたり、テストすることさえ不可能なシステムで、大量のテストコードを書く必要がある場合、それは間違いなく人生をずっと楽にし、多くの時間を節約します。無料のモッキングフレームワーク。
トーマス