对这个单元测试感到困惑!
基本上,我有一个抽象类,它有一个唯一的增量 ID - Primitive
。当实例化 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);
}
}
}
我认为我做错了 - 那么我如何正确测试它?
So basically, I have an abstract class which has a unique, incremental ID - Primitive
. When a Primitive
(or more precisely, an inheritor of Primitive
) is instantiated, the ID is incremented - up to the point where the ID overflows - at which point, I add a message to the exception and rethrow.
OK, that all works fine... but I'm trying to test this functionality and I've never used mocking before. I just need to make enough Primitives for the ID to overflow and assert that it throws at the right time.
- It is unreasonable to instantiate 2 billion objects to do this! However I don't see another way.
- I don't know if I'm using mocking correctly? (I'm using
Moq
.)
Here's my test (xUnit
):
[Fact(DisplayName = "Test Primitive count limit")]
public void TestPrimitiveCountLimit()
{
Assert.Throws(typeof(OverflowException), delegate()
{
for (; ; )
{
var mock = new Mock<Primitive>();
}
});
}
and:
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);
}
}
}
I assume I'm doing it wrong - so how do I test this properly?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
您不需要为此进行模拟。当两个类一起工作并且您想要用模拟(假)类替换一个类时,您可以使用模拟,这样您只需测试另一个类。您的示例中并非如此。
但是,有一种方法可以使用模拟,并且可以解决 20 亿个实例的问题。如果将 ID 生成与
Primitive
类分开并使用生成器,则您可以可以模拟生成器。示例:我已更改
Primitive
以使用提供的生成器。在本例中,它被设置为静态变量,并且有更好的方法,但作为示例:然后,您的测试用例变为:
这将运行得更快,现在您只需测试 ID 生成器是否工作。
现在,当您想要测试创建一个新原语实际上是否需要 ID 时,您可以尝试以下操作:
现在您已经分离了不同的关注点,可以单独测试它们。
You don't need mocking for this. You use mocking when two classes work together and you want to replace one class with a mock (fake) one so you only have to test the other one. This is not the case in your example.
There is however a way you could use mocks, and that fixes your issue with the 2bln instances. If you separate the ID generation from the
Primitive
class and use a generator, you can mock the generator. An example:I've changed
Primitive
to use a provided generator. In this case it's set to a static variable, and there are better ways, but as an example:Then, your test case becomes:
This will run a lot faster and now you're only testing whether the ID generator works.
Now, when you e.g. want to test that creating a new primitive actually asks for the ID, you could try the following:
Now you have separated the different concerns and can test them separately.
模拟的目的是模拟外部资源。这不是你想要的,你想要测试你的对象,在这个场景中不需要模拟。如果您愿意,只需实例化 20 亿个对象即可,这并没有什么坏处,因为 GC 会丢弃旧实例(但可能需要一段时间才能完成)。
Id' 实际上添加了另一个构造函数,它接受身份计数器的起始值,这样您实际上可以从接近
int.MaxValue
开始,因此不需要实例化那么多对象。另外,只需阅读源代码,我就可以知道您的对象将无法通过测试。 ;-)
The point of the mock is to simulate an external resource. It's not what you want, you want to test your object, no mock needed in this szenario. Just instantiate the 2 billion objects if you like to, it doesn't hurt since the GC will throw away the old instances (but may take a while to complete).
Id' actually add another constructor which accepts a strarting value for the identity counter, so that you can actually start close to
int.MaxValue
and therefore don't need to instatiate as many objects.Also, just from readin the source I can tell that your object will fail the test. ;-)
这个问题有两个问题:
我认为解决方案非常简单,尽管您必须稍微重新考虑对象的结构。
对于第一个问题,解决方案很简单,只需向您的测试项目添加一个继承
Primitive
的假冒产品,但不添加任何功能。然后,您可以实例化您的伪类,并且您仍然可以测试Primitive
的功能。对于第二个问题,我将添加一个构造函数,该构造函数采用
int
作为先前的 ID,并使用构造函数链接来在实际代码中“不需要它”。 (但是你如何才能知道之前的 id?你不能在测试设置中将其设置为int.MaxValue-1
吗?)将其视为依赖注入,但是你不注入任何复杂的东西;您只是注入一个简单的int
。可能是这样的:You have two problems baked into this question:
I think the solutions are pretty simple, even though you'll have to re-think the structure of your object slightly.
For the first problem, the solution is as simple as adding a fake that inherits
Primitive
, but adds no functionality, to your test project. You can then instantiate your fake class instead, and you'll still be testing the functionality ofPrimitive
.For the second problem, I'd add a constructor that takes an
int
for the previous ID, and use constructor chaining to "not need it" in your actual code. (But how to you get to know of the previous id otherwise? Can't you set that toint.MaxValue-1
in the setup of your test?) Think of it as dependecy injection, but you're not injecting anything complex; you're just injecting a simpleint
. It could be something along these lines:其他答案都已经说了。我只是想向您展示另一种选择,也许这对您来说有点有趣。
如果您将
Primitive
类的 _previousId
字段设置为internal
(当然,并包含相应的InternalsVisibleTo
属性) ),那么使用 Typemock Isolator 工具,您的测试就可以像这样简单:当然,Typemock 附带了一些许可证成本,但如果您必须编写大量测试代码,它肯定会让生活变得更加轻松并节省大量时间 - 特别是在不容易测试甚至无法使用免费模拟框架进行测试的系统上。
托马斯
All has been said in the other answers. I just want to show you an alternative, maybe this is somehow interesting for you.
If you made the _
previousId
field of yourPrimitive
classinternal
(and included the respectiveInternalsVisibleTo
attribute, of course), then your test could be as simple as this with the Typemock Isolator tool:Sure, Typemock comes with some license costs, but it definitely makes life much easier and saves you a lot of time, if you have to write large amounts of test code - especially on systems which are not easily tested or are even impossible to test with a free mocking framework.
Thomas