Java模拟框架如何工作?

这不是一个关于哪个是最佳框架等的问题。

我从未使用过模拟框架,我对这个想法感到有点困惑。 它是如何知道如何创建模拟对象的? 它是在运行时完成还是生成文件? 你怎么知道它的行为? 最重要的是 – 使用这样一个框架的工作流程是什么(创建测试的步骤是什么)?

谁有人解释一下? 您可以选择您喜欢的框架,例如,说出它是什么。

我将谈论我使用的框架(jmock),但其他人做了非常相似的事情。

它是如何知道如何创建模拟对象的?

它没有,你告诉框架你需要一个模拟对象,你给它一个你想要模拟的对象的类型(理想情况下是一个接口)。 在幕后,模拟框架使用reflection和有时字节码重写的组合来创建模拟对象。

它是在运行时完成还是生成文件?

jMock在运行时创建它。

你怎么知道它的行为?

模拟对象非常愚蠢。 您可以指定所期望的行为。

最重要的是 – 使用这样一个框架的工作流程是什么(创建测试的步骤是什么)。

框架必须提供的一个非常重要的事情是能够检查在测试执行期间是否已观察到预期行为。

jmock通过引入特定的测试运行器来完成它,该测试运行器在测试完成后检查所有声明的模拟对象的所有期望。 如果某些内容不匹配,则抛出exception。

通常模式是:

  1. 凭借空气获取模拟工厂(在jmock中称为Mockery)(使用特定的无参数构造函数)。
  2. 创建所需的模拟对象。
  3. 设置对模拟对象的期望
  4. 调用您正在测试的方法,传递模拟而不是真实对象
  5. 如果您的方法返回一些值,请使用常规的assertXXX方法检查预期值。

模拟框架将冗余和样板文件从模拟测试中取出。

它知道创建Mock对象,因为你告诉它,当然(除非你的意思是其他问题)。

创建模拟对象的“标准”方法是使用/滥用java.lang.reflect.Proxy类来创建接口的运行时实现。 这是在运行时完成的。 代理有一个限制,因为它不能代理具体的类。 要完成具体类的模拟,需要动态字节码创建,创建子类,覆盖公共方法的实际实现,基本上将使用Proxy完成(记录方法参数并返回预定值)。 这有一个限制,因为它不能inheritance最终类。 为此,你有像JDave这样的解决方案(我相信,我没有证实这一点)与类加载器混淆 ,在加载它之前删除类的最终名称,所以该类实际上在运行时并不是最终的就JVM而言。

Mocking框架基本上都是关于捕获参数并根据预先确定的期望validation它们,然后返回预先配置或合理的默认值。 它没有任何特定的行为,这是重点。 正在validation调用代码它使用正确的参数调用方法,以及它可能如何响应特定的返回值或抛出的exception。 不会发生对真实对象的调用的任何副作用或实际成就。

这是一个项目的实例,使用JMock和JUnit4。 我添加了评论来解释发生了什么。

  @RunWith(JMock.class) //The JMock Runner automatically checks that the expectations of the mock were actually run at the end of the test so that you don't have to do it with a line of code in every test. public class SecuredPresentationMapTest { private Mockery context = new JUnit4Mockery(); //A Mockery holds the state about all of the Mocks. The JUnit4Mockery ensures that a failed mock throws the same Error as any other JUnit failure. @Test public void testBasicMap() { final IPermissionsLookup lookup = context.mock(IPermissionsLookup.class); //Creating a mock for the interface IPermissionsLookup. context.checking(new Expectations(){{ //JMock uses some innovative but weird double brace initialization as its standard idom. oneOf(lookup).canRead(SecuredEntity.ACCOUNTING_CONTRACT); //expect exactly one call to the IPermissionsLookup.canRead method with the the enum of ACCOUNTING_CONTRACT as the value. Failure to call the method at all causes the test to fail. will(returnValue(true)); //when the previous method is called correctly, return true; }}); Map map = new SecuredPresentationMap(lookup, SecuredEntity.ACCOUNTING_CONTRACT); //This creates the real object under test, but passes a mock lookup rather than the real implementation. JLabel value = new JLabel(); map.put("", value); assertThat(((JLabel) map.get("")), is(value)); //This ensures that the Map returns the value placed, which it should based on the fact that the mock returned true to the security check. } } 

如果传入的模拟被忽略,则测试将失败。 如果映射未能返回其中的值,则测试失败(即标准JUnit)。

这里和另一个相反的测试正在测试的是,根据IPermissionsLookup接口所说的安全性,Map会改变其返回内容的行为。 这是基本的好例子。 另一个测试,模拟返回false,并且预期来自Map的其他内容。 使用模拟确保映射依赖于IPermissionsLookup方法来确定安全状态以及返回的内容。

为了更好地理解模拟,您可能想制作自己的模拟对象。 这是一个非常简单的过程 – 您创建一个类,它实现与您正在模拟的相同的接口,并为其提供所需的行为,记录对方法的调用,或者在调用时以set方式响应。 从那里开始,模拟框架的设置方式开始变得更有意义。

我认为EasyMock的文档是一个让你入门的方法。 它包含许多易于掌握的示例。

EasyMock( http://easymock.org/ )是我最常用的模拟库。 (还有其他:jMock,Mockito等)

其中大多数在运行时创建一个对象,您可以控制。 基本流程是:

  1. 创建一个模拟
  2. 通过告诉模拟框架期望的调用来指定它应该如何操作。 (这取决于框架。)
  3. 在测试中使用模拟,就像它是真实的一样
  4. 某些框架允许(或要求)您“validation”模拟是否正确使用。