Java模拟框架是如何工作的?

26

这不是关于哪个框架最好之类的问题。

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

有人能解释一下吗?您可以选择任何您喜欢的框架,只需要说明是什么框架即可。


2
你可能不想要“最佳框架”的答案,但这就是你将得到的... - skaffman
1
请参见https://dev59.com/gXE95IYBdhLWcg3wheNR,尽管那里的答案非常技术化。 - Robert Harvey
5个回答

17

一个模拟框架可以减少模拟测试中的冗余和样板代码。

当然,它知道要创建模拟对象是因为你告诉它,除非你在提问时指的是其他东西。

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

模拟框架基本上都是关于捕获参数并根据预先确定的期望进行验证,然后返回预配置或合理的默认值。它不会以任何特定的方式行事,这就是重点。调用代码正在验证其是否以正确的参数调用了方法,以及它如何响应特定的返回值或抛出异常。对真实对象的任何副作用或真正成就都不会发生。

这是来自一个项目的实际示例,使用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<String, Component> 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.
  }
}

如果忽略传入的模拟数据,测试将会失败。如果Map未能返回其中放置的值,则测试失败(这是标准JUnit)。在此处和另一个相反的测试中所测试的是,根据IPermissionsLookup接口对安全性的定义,Map会改变其返回内容的行为。这是基本的良好情况。在另一个测试中,模拟数据返回false,并期望Map返回其他内容。使用模拟数据确保Map依赖于IPermissionsLookup方法来确定安全姿态以及正在返回什么。

我得说,因为有了JMock,我才知道实例初始化器(双括号)的存在,而且如果你第一次看到它,确实感觉非常奇怪。 - Alexander Pogrebnyak
@ Alexander,确实,JMock测试是我唯一使用它的时间。虽然我不得不承认,在这种特定情况下,它确实是一个很好的习惯用法,但在一般情况下,我仍然认为抽象方法(例如Guice的AbstractModule)是正确的方法。 - Yishai

14

我将谈论我使用的框架 ( jmock ),但其他框架做类似的事情。

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

它不知道,你告诉框架你需要一个模拟对象,并给它提供对象类型(最好是接口)。 在幕后,模拟框架使用反射和有时候是字节码重写的组合来创建模拟对象。

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

jMock 在运行时创建它。

你怎么知道它的行为呢?

模拟对象非常笨。你指定期望的行为。

最重要的是 - 使用这样的框架的工作流是什么 (创建测试的逐步操作是什么)?

框架必须提供的一个非常重要的功能是检查在测试执行过程中是否观察到了期望的行为。

jmock通过引入特定的测试运行程序来实现此功能。该程序在测试结束后检查所有声明的模拟对象上的所有期望。如果有不匹配的地方,则会抛出异常。

通常,步骤如下:

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

5

为了更好地理解mocking,您可能希望制作自己的mock对象。这是一个相当简单的过程 - 您可以创建一个实现与您要mock的接口相同的类,并赋予它所需的行为,无论是记录对方法的调用还是在调用时以一种设置方式响应。从那里开始,mocking框架的设置方式就开始变得更加清晰了。


我想模拟库知道要模拟哪个接口,因为它使用反射来确定接口的外观。 - Robert Harvey
我以前创建过模拟对象...只是没有使用框架。 - Amir Rachum
为什么这个回答被踩了?它是唯一一个回答到了楼主的问题。 - Robert Harvey
这个回答没有解答关于现有模拟框架如何工作的问题。而且,如果问题是“我该如何编写自己的模拟框架?”,那么只有一半的问题得到了解答。一个完整的答案也会涉及如何对具体对象进行mock操作。 - Jacob Tomaw
@Robert Harvey 我给它点了踩,因为任何Java程序员都能够编写实现其接口的模拟类。更有趣的问题是如何在运行时使用模拟框架。因此,如果作者写了关于代理的内容,那么“从那里开始,模拟框架的设置方式就变得更加合理”这句话会更加恰当。 - Nikita Rybak

1

我认为EasyMock的文档是一个入门的好方法。它包含了很多易于理解的示例。


0

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

这些库大多数在运行时创建一个对象,您可以控制它。基本流程如下:

  1. 创建一个模拟对象
  2. 通过告诉模拟框架应该期望哪些调用来指定其行为。(这取决于框架。)
  3. 在测试中使用模拟对象,就像它是真实的一样
  4. 一些框架允许(或要求)您“验证”模拟对象是否被正确使用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接