在Eclipse和Maven2中运行测试时,JUnit 4 PermGen大小溢出

时间:2012-07-27 07:23:18

标签: static junit mockito powermock permgen

我正在使用JUnit,PowerMock和Mockito进行一些单元测试。我有很多用@RunWith(PowerMockRunner.class)@PrepareForTest(SomeClassesNames)注释的测试类来模拟最终类和200多个测试用例。

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

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

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

3 个答案:

答案 0 :(得分:25)

我也在Eclipse中从Junit获得了PermGen错误。但我没有使用像Mockito和EasyMock这样的任何模仿库。但是,我的代码库很大,我的Junit测试使用的是Spring-Test(并且是强大而复杂的测试用例)。为此,我需要真正增加所有Junit测试的PermGen。

Eclipse将已安装的JRE设置应用于Junit运行 - 而不是eclipse.ini设置。所以改变那些:

  • 窗口>偏好> Java>安装了JRE的
  • 选择默认JRE,编辑...按钮
  • 添加到默认VM参数:-XX:MaxPermSize = 196m

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

答案 1 :(得分: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类。有关此观点的更多信息,请参阅Mocking Mocking and Testing Outcomes. by Uncle Bob

顺便说一句,在maven surefire中,你总是可以forkMode=always为每个测试类分叉jvm。但这并不能解决你的Eclipse问题。

答案 2 :(得分:5)

首先:Mockito正在使用CGLIB来创建模拟,而PowerMock正在使用Javassist来处理其他一些东西,例如删除最终标记,Powermock也会在新的ClassLoader中加载类。 CGLIB以吃永久性一代而闻名(只需谷歌CGLIB PermGen查找相关结果)。

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

  1. 正如你所指出的那样是静态助手类,我不知道是否还有mocks保存静态变量,我不知道你的代码的细节,所以这是纯粹的猜测,和其他读者一样实际上知道更好可能会纠正我。

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

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

  3. 我再一次不知道你项目的细节,所以我只是在猜测,但你的永久性问题可能是由于第1点或第2点引起的,甚至两者都是。

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

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

    如果您使用的是Sun / Oracle JVM,可以尝试使用这些选项来跟踪发生的情况:

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

    希望有所帮助。


    在这个问题的范围之外:

    Personnaly我不喜欢使用Powermock,我只在角落情况例如中使用它来测试不可修改的遗留代码。 Powermock太侵入了imho,它必须为每个测试生成一个新的类加载器来执行它的行为(修改字节码),你必须大量注释测试类才能够模拟,... 在我看来,对于通常的开发,所有这些小小的不便超过了模拟决赛的能力的好处。甚至作为Powermock的作者约翰曾经告诉过我,他推荐Mockito,并将Powermock保留用于特定用途。

    不要误解我的意思:Powermock是一项非常出色的技术,当您必须处理(设计不佳)您无法改变的遗留代码时,这真的很有帮助。但不是每天的发展,特别是如果praticing TDD。