如何编写递归方法的 Mockist 测试
如果我有一个在特定条件下调用自身的方法,是否可以编写一个测试来验证该行为?我很想看一个例子,我不关心模拟框架或语言。我在 C# 中使用 RhinoMocks,所以我很好奇它是否是该框架缺少的功能,或者我是否误解了一些基本的东西,或者它是否只是一个不可能的东西。
If I have a method that calls itself under a certain condition, is it possible to write a test to verify the behavior? I'd love to see an example, I don't care about the mock framework or language. I'm using RhinoMocks in C# so I'm curious if it is a missing feature of the framework, or if I'm misunderstanding something fundamental, or if it is just an impossibility.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
是的。但是,如果您需要测试递归,则最好将递归入口点和递归步骤分开以进行测试。
无论如何,如果你不能这样做,这里是如何测试它的示例。你真的不需要任何嘲笑:
Yes. However, if you need to test recursion you better separate the entry point into the recursion and the recursion step for testing purposes.
Anyway, here is the example how to test it if you cannot do that. You don't really need any mocking:
假设您想要执行类似从完整路径获取文件名的操作,例如:
并且您有:
并且您想要编写:
这里的递归是一个实现细节,不应进行测试。您确实希望能够在两个实现之间切换并验证它们是否产生相同的结果:对于上面的三个示例,两者都生成 lol.cs。
话虽这么说,因为您是按名称递归,而不是说 thisMethod.again() 等,所以在 Ruby 中您可以将原始方法别名为新名称,使用旧名称重新定义方法,调用新名称并检查是否你最终会进入新定义的方法。
Assuming you want to do something like get the filename from a complete path, for example:
and you have:
and you want to write:
Recursion here is an implementation detail and should not be tested for. You really want to be able to switch between the two implementations and verify that they produce the same result: both produce lol.cs for the three examples above.
That being said, because you are recursing by name, rather than saying thisMethod.again() etc., in Ruby you can alias the original method to a new name, redefine the method with the old name, invoke the new name and check whether you end up in the newly defined method.
您误解了模拟对象的目的。模拟(Mockist 意义上的)用于测试与被测系统的依赖关系的行为交互。
例如,您可能有这样的内容:
Coyote 依赖于 IMailOrder。在生产代码中,Coyote 的实例将传递给实现 IMailOrder 的 Acme 实例。 (这可以通过手动依赖注入或通过 DI 框架来完成。)
您想要测试方法 CatchDinner 并验证它是否调用 OrderExplosives。为此,您:
当您设置模拟对象的期望时,可能取决于您的模拟(隔离)框架。
如果您正在测试的类或方法没有外部依赖项,则您不需要(或不想)为该组测试使用模拟对象。该方法是否递归并不重要。
您通常想要测试边界条件,因此您可能会测试不应递归的调用、具有单个递归调用的调用以及深度递归调用。 (不过,miaubiz 对于递归作为实现细节有一个很好的观点。)
编辑:上一段中的“调用”是指带有参数或对象状态的调用,该调用将触发给定的递归深度。我还建议阅读单元测试的艺术。
编辑2:使用Moq的示例测试代码:
You're misunderstanding the purpose of mock objects. Mocks (in the Mockist sense) are used to test behavioral interactions with dependencies of the system under test.
So, for instance, you might have something like this:
Coyote depends on IMailOrder. In production code, an instance of Coyote would be passed an instance of Acme, which implements IMailOrder. (This might be done through manual Dependency Injection or via a DI framework.)
You want to test method CatchDinner and verify that it calls OrderExplosives. To do so, you:
When you setup the expectations on the mock object may depend on your mocking (isolation) framework.
If the class or method you're testing has no external dependencies, you don't need (or want) to use mock objects for that set of tests. It doesn't matter if the method is recursive or not.
You generally want to test boundary conditions, so you might test a call that should not be recursive, a call with a single recursive call, and a deeply-recursive call. (miaubiz has a good point about recursion being an implementation detail, though.)
EDIT: By "call" in the last paragraph I meant a call with parameters or object state that would trigger a given recursion depth. I'd also recommend reading The Art of Unit Testing.
EDIT 2: Example test code using Moq:
在我所知道的任何模拟框架中,没有任何东西可以监视堆栈深度/(递归)函数调用的数量。但是,单元测试正确的模拟前提条件提供正确的输出应该与模拟非递归函数相同。
无限递归会导致堆栈溢出,您必须单独调试,但单元测试和模拟从未摆脱这种需求。
There isn't anything to monitor stack depth/number of (recursive) function calls in any mocking framework I'm aware of. However, unit testing that the proper mocked pre-conditions provide the correct outputs should be the same as mocking a non-recursive function.
Infinite recursion that leads to a stack overflow you'll have to debug separately, but unit tests and mocks have never gotten rid of that need in the first place.
这是我的“农民”方法(在Python中,经过测试,请参阅注释以了解其原理)
请注意,实现细节“暴露”在这里是没有问题的,因为您正在测试的是发生的底层架构由“顶级”代码使用。因此,测试它是合法且行为良好的(我也希望,这就是您的想法)。
代码(主要思想是从单个但“不可测试”的递归函数变为一对等效的递归依赖(因此可测试)函数):
测试:
输出:
Here's my 'peasant' approach (in Python, tested, see the comments for the rationale)
Note that implementation detail "exposure" is out of question here, since what you are testing is the underlying architecture which happens to be utilized by the "top-level" code. So, testing it is legitimate and well-behaved (I also hope, it's what you have in mind).
The code (the main idea is to go from a single but "untestable" recursive function to an equivalent pair of recursively dependent (and thus testable) functions):
The test:
The output: