9

我正在用 JUnit、PowerMock 和 Mockito 做一些单元测试。我有很多测试类注释@RunWith(PowerMockRunner.class)@PrepareForTest(SomeClassesNames)模拟最终类和超过 200 个测试用例。

最近,当我在 Eclipse 或 Maven2 中运行我的整个测试套件时,我遇到了 PermGen 空间溢出的问题。当我一个一个地运行我的测试时,他们每个人都成功了。

我对此进行了一些研究,但是没有任何建议对我有帮助(我增加了 PermGenSize 和 MaxPermSize)。最近我发现有一个类只包含静态方法,每个方法都返回用 PowerMockito 模拟的对象。我想知道这是否是一种好习惯,也许这是问题的根源,因为静态变量在单元测试之间共享?

一般来说,拥有一个包含许多返回静态模拟对象的静态方法的静态类是一种好习惯吗?

4

3 回答 3

28

我也从 Eclipse 中的 Junit 收到 PermGen 错误。但我没有使用任何模拟库,如 Mockito 或 EasyMock。但是,我的代码库很大,而且我的 Junit 测试使用的是 Spring-Test(并且是密集而复杂的测试用例)。为此,我需要真正增加所有 Junit 测试的 PermGen。

Eclipse 将 Installed JRE 设置应用于 Junit 运行,而不是 eclipse.ini 设置。所以要改变那些:

  • Window > Preferences > Java > Installed JRE's
  • 选择默认的 JRE,编辑...按钮
  • 添加到默认 VM 参数:-XX:MaxPermSize=196m

此设置将允许 Junit 测试在 Eclipse 中运行更密集的 TestCases,并避免 OutOfMemoryError: PermGen。这也应该是低风险的,因为大多数简单的 Junit 测试不会分配所有的内存。

于 2013-09-12T22:00:13.237 回答
9

正如@Brice 所说,PermGen 的问题将来自您对模拟对象的广泛使用。Powermock 和 Mockito 都创建了一个新类,它位于被模拟的类和您的测试代码之间。此类在运行时创建并加载到 PermGen 中,并且(实际上)永远不会恢复。因此,您对 PermGen 空间的问题。

对于你的问题:

1) 共享静态变量被认为是代码异味。在某些情况下这是必要的,但它会在测试之间引入依赖关系。测试 A 需要在测试 B 之前运行。

2)使用静态方法返回一个模拟对象并不是真正的代码味道,它是一种经常使用的模式。如果你真的不能增加你的 permgen 空间,你有很多选择:

使用模拟池,PowerMock#reset()当模拟放回池中时。这会减少你正在做的创作的数量。

其次,你说你的课程是最终的。如果这是可以更改的,那么您可以在测试中使用匿名类。这再次减少了使用的 permgen 空间量:

Foo myMockObject = new Foo() {
     public int getBar() { throw new Exception(); }
}

第三,您可以引入一个接口(在 Eclipse 中使用 Refactor->Extract Interface),然后用一个什么都不做的空类来扩展它。然后,在您的课堂上,您执行与上述类似的操作。我经常使用这种技术,因为我发现它更容易阅读:

public interface Foo {
  public int getBar();
}

public class MockFoo implements Foo {
  public int getBar() { return 0; }
}

然后在课堂上:

Foo myMockObject = new MockFoo() {
     public int getBar() { throw new Exception(); }
}

我不得不承认我不是特别喜欢模拟,我只在必要时使用它,我倾向于使用匿名类扩展类或创建一个真正的 MockXXX 类。有关此观点的更多信息,请参阅模拟模拟和测试结果。鲍勃叔叔

顺便说一句,在 maven surefire 中,您始终可以forkMode=always为每个测试类分叉 jvm。不过,这不会解决您的 Eclipse 问题。

于 2012-07-27T10:02:45.173 回答
5

首先:Mockito 使用 CGLIB 创建模拟,PowerMock 使用 Javassist 处理其他一些东西,比如删除最终标记,Powermock 还在新的 ClassLoader 中加载类。CGLIB 以吃永久代而闻名(只需谷歌CGLIB PermGen即可找到有关此事的相关结果)。

这不是一个直接的答案,因为它取决于您的项目的细节:

  1. 正如您指出的那样,有静态帮助程序类,我不知道是否也包含带有模拟的静态变量,我不知道您的代码的细节,所以这是纯粹的猜测,其他真正了解的读者可能会纠正我.

    可能是加载这个静态类的 ClassLoader(以及至少他的一些孩子)在测试中保持活动状态 -这可能是因为静态(它存在于Class 领域)或因为某处的某些引用 - 这意味着如果 ClassLoader 仍然存在没有被垃圾收集),他加载的类不会被丢弃, 包括生成的类仍然在 PermGen 中

  2. 这些类的大小也可能很大,如果要加载很多这些类,这可能与更高的 PermGen 值相关,特别是因为 Powermock 需要为每个测试在新的 Classloader 中重新加载类。

同样,我不知道您的项目的详细信息,所以我只是猜测,但是您的永久代问题可能是由于第 1 点或第 2 点,甚至两者兼而有之。

无论如何,一般来说我会说是的:拥有一个可能返回静态模拟对象的静态类在这里看起来确实是一种不好的做法,因为它通常在生产代码中。如果设计不当,可能会导致 ClassLoader 泄漏(这很糟糕!)。

在实践中,我已经看到运行了数百个测试(仅使用 Mockito)而没有更改内存参数,也没有看到 CGLIB 代理被卸载,而且我没有使用静态的东西 appart 来自 Mockito API 的东西。

如果您使用的是 Sun/Oracle JVM,您可以尝试以下选项来跟踪正在发生的事情:

-XX:+TraceClassLoading-XX:+TraceClassUnloading-verbose:class

希望有帮助。


在这个问题的范围之外:

个人而言,无论如何我都不喜欢使用 Powermock,我只在极端情况下使用它,例如测试不可修改的遗留代码。Powermock 太侵入性了,它必须为每个测试产生一个新的类加载器来执行它的行为(修改字节码),你必须对测试类进行大量注释才能模拟,......在我看来,对于通常的开发来说这些小小的不便超过了模拟决赛的能力所带来的好处。甚至 Powermock 的作者 Johan 也曾告诉我,他正在推荐 Mockito 并将 Powermock 保留用于某些特定目的。

不要误会我的意思:Powermock 是一项了不起的技术,当您必须处理无法更改的(糟糕的)设计遗留代码时,它真的很有帮助。但不适用于日常开发,尤其是在练习 TDD 时。

于 2012-07-27T09:14:32.143 回答