Переполнение размера JUnit 4 PermGen при выполнении тестов в Eclipse и Maven2

Я делаю несколько модульных тестов с JUnit, PowerMock и Mockito. У меня есть много тестовых классов, аннотированных @RunWith(PowerMockRunner.class) и @PrepareForTest(SomeClassesNames) для имитации финальных классов, и более 200 тестовых случаев.

Недавно я столкнулся с проблемой переполнения пространства PermGen, когда запускаю весь набор тестов в Eclipse или Maven2. Когда я запускаю тест один за другим, каждый из них завершается успешно.

Я провел некоторое исследование по этому поводу, однако ни один из советов мне не помог (я увеличил PermGenSize и MaxPermSize). Недавно я узнал, что есть один класс, который содержит только статические методы, и каждый метод возвращает объект, смоделированный с помощью PowerMockito. Мне интересно, является ли это хорошей практикой, и, возможно, это источник проблемы, потому что статические переменные используются совместно между модульными тестами?

Вообще говоря, рекомендуется ли иметь статический класс с множеством статических методов, которые возвращают статические имитированные объекты?


person BlueLettuce16    schedule 27.07.2012    source источник
comment
Есть ли причина, по которой ваше приложение было разработано с таким количеством статических методов? Статические методы, как правило, затрудняют тестирование. Возможно ли, чтобы вы использовали их как нестатические методы некоторого класса; затем внедрить объект этого класса в любой код, который вам нужен?   -  person Dawood ibn Kareem    schedule 27.07.2012
comment
@DavidWallace Из того, что сказал ОП, я бы предположил, что статические методы используются в тестах, они возвращают только имитированные объекты. Они не будут использоваться в нетестовом коде.   -  person Matthew Farwell    schedule 27.07.2012
comment
Хорошо, я прочитал это по-другому. @ Адам, не могли бы вы уточнить? Являются ли статические методы частью вашего приложения или просто тестовые леса?   -  person Dawood ibn Kareem    schedule 27.07.2012
comment
Чтобы уточнить, статические методы являются частью тестовых лесов.   -  person BlueLettuce16    schedule 27.07.2012
comment
Почему? Разве вы не можете просто сделать их нестатическими методами в любом тестовом классе, в котором они живут?   -  person Dawood ibn Kareem    schedule 27.07.2012
comment
Я поменял, но не помогло :(   -  person BlueLettuce16    schedule 27.07.2012


Ответы (3)


Как говорит @Brice, проблемы с PermGen будут возникать из-за вашего широкого использования фиктивных объектов. Powermock и Mockito создают новый класс, который находится между имитируемым классом и вашим тестовым кодом. Этот класс создается во время выполнения и загружается в PermGen и (практически) никогда не восстанавливается. Отсюда и ваши проблемы с пространством PermGen.

На ваш вопрос:

1) Совместное использование статических переменных считается запахом кода. В некоторых случаях это необходимо, но вводит зависимости между тестами. Тест А должен быть запущен перед тестом Б.

2) Использование статических методов для возврата издевательского объекта на самом деле не является запахом кода, это часто используемый паттерн. Если вы действительно не можете увеличить пространство permgen, у вас есть несколько вариантов:

Используйте пул макетов с PowerMock#reset(), когда макет возвращается в пул. Это сократит количество творений, которые вы делаете.

Во-вторых, вы сказали, что ваши занятия финальные. Если это можно изменить, вы можете просто использовать анонимный класс в тесте. Это снова сокращает объем используемого пространства permgen:

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

В-третьих, вы можете ввести интерфейс (используйте Refactor->Extract Interface в Eclipse), который вы затем расширите пустым классом, который ничего не делает. Затем в своем классе вы делаете то же самое, что и выше. Я использую эту технику довольно часто, потому что мне легче читать:

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= всегда, который будет разветвлять jvm для каждого тестового класса. Однако это не решит вашу проблему с Eclipse.

person Matthew Farwell    schedule 27.07.2012
comment
Я увеличил PermGenSize для отчетов Surefire с настроек по умолчанию до 128 МБ и то же самое для JUnit runner в Eclipse - это помогло. Это только временное решение. Я думаю, что мой тестовый код в любом случае нуждается в некотором рефакторинге, потому что у меня около 250 тестовых случаев, и для его запуска мне нужно 100 МБ постоянной памяти. На мой взгляд, это слишком. - person BlueLettuce16; 31.07.2012
comment
попробуйте @PowerMockIgnore (значение = {org.apache.log4j.*}) gitshah.com/2010/07/how-to-fix-outofmemoryerror-when.html - person Alex Punnen; 29.04.2013
comment
хорошо ответил .. и спасибо за ссылку на пост от дяди Боба :) - person rottweiler; 13.11.2013

Я также получаю ошибки PermGen от Junit в Eclipse. Но я не использую никаких издевательских библиотек, таких как Mockito или EasyMock. Однако моя кодовая база велика, и мои тесты Junit используют Spring-Test (и это интенсивные и сложные тестовые примеры). Для этого мне нужно действительно увеличить PermGen для всех моих тестов Junit.

Eclipse применяет настройки Installed JRE к запускам Junit, а не настройки eclipse.ini. Итак, чтобы изменить их:

  • Окно > Настройки > Java > Установленные JRE
  • выберите кнопку JRE по умолчанию, Изменить...
  • добавить к аргументам виртуальной машины по умолчанию: -XX:MaxPermSize=196m

Этот параметр позволит тестам Junit запускать более интенсивные тестовые случаи в Eclipse и избежать ошибки OutOfMemoryError: PermGen. Это также должно быть низким риском, потому что большинство простых тестов Junit не будут выделять всю эту память.

person Jay Meyer    schedule 12.09.2013

Во-первых: Mockito использует CGLIB для создания макетов, а PowerMock использует Javassist для некоторых других вещей, таких как удаление конечных маркеров, Powermock также загружает классы в новый ClassLoader. CGLIB известен тем, что поедает постоянное поколение (просто погуглите CGLIB PermGen чтобы найти соответствующие результаты по этому вопросу).

Это не прямой ответ, поскольку он зависит от деталей вашего проекта:

  1. Как вы указали, есть статический вспомогательный класс, я не знаю, содержит ли также статические переменные с макетами, я не знаю деталей вашего кода, так что это чистое предположение, и другие читатели, которые действительно знают лучше, могут меня исправить .

    Это может быть загрузчик ClassLoader (и, по крайней мере, некоторые из его дочерних элементов), который загрузил этот статический класс, может поддерживаться в тестах - это может быть из-за статики (которая живет в области классов< /em>) или из-за какой-то ссылки где-то - это означает, что если ClassLoader все еще жив (т.е. не собирается мусор), его загруженные классы не отбрасываются, т.е.< /em> классы, включая сгенерированные, все еще находятся в PermGen.

  2. Эти классы также могут быть огромными по размеру, если у вас есть много этих классов для загрузки, это может иметь значение для более высоких значений PermGen, тем более что Powermock необходимо перезагружать классы в новом загрузчике классов для каждого теста.

Опять же, я не знаю деталей вашего проекта, поэтому я просто предполагаю, но ваша проблема с постоянной генерацией может быть вызвана либо пунктом 1, либо пунктом 2, либо даже обоими.

В любом случае, вообще говоря, я бы сказал да: наличие статического класса, который может возвращать статический имитированный объект, здесь выглядит как плохая практика, как это обычно бывает в производственном коде. Если он плохо обработан, это может привести к утечке ClassLoader (это неприятно!).

На практике я видел сотни тестов (только с Mockito) без изменения параметров памяти и без выгрузки прокси-серверов CGLIB, и я не использую статические вещи, кроме тех, что из Mockito API.

Если вы используете JVM Sun/Oracle, вы можете попробовать следующие варианты, чтобы отслеживать происходящее:

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

Надеюсь, это поможет.


Выходит за рамки этого вопроса:

Лично мне все равно не нравится использовать Powermock, я использую его только в крайних случаях, например, для тестирования немодифицируемого устаревшего кода. Powermock слишком навязчив, имхо, он должен создавать для каждого теста новый загрузчик классов для выполнения своих действий (изменение байт-кода), вам нужно сильно аннотировать тестовые классы, чтобы иметь возможность издеваться... По моему мнению, для обычной разработки все эти небольшие неудобства перевешивают преимущества возможности имитировать финалы. Даже Йохан, автор Powermock, однажды сказал мне, что вместо этого он рекомендует Mockito и держит Powermock для какой-то конкретной цели.

Не поймите меня неправильно: Powermock — это фантастическая технология, которая действительно помогает, когда вам приходится иметь дело с (плохо) разработанным унаследованным кодом, который вы не можете изменить. Но не для повседневной разработки, особенно если практикуешь TDD.

person Brice    schedule 27.07.2012
comment
Что вы имеете в виду под огромным размером? Огромные тестовые классы или огромные тестируемые классы? У меня более 250 тестовых случаев, и они используют 100 МБ от permgen. Вы думаете, что это слишком? Я также использую Powermock только в крайних случаях. Спасибо за ваш ответ - варианты отслеживания помогли мне. - person BlueLettuce16; 31.07.2012
comment
@ Адам, да, это гигантский, у меня есть чистый набор модульных тестов из ~ 250 тестовых классов с ~ 1220 методами тестирования, в которых широко используется Mockito, а размер постоянного поколения составляет около 12 МБ. Я видел только сотни МБ PerMGen в запущенных приложениях. Это может занять некоторое время, но вы действительно должны попытаться определить, что вызывает поглощение PermGen. Возможно, тесты на самом деле не являются модульными тестами и используют контексты Spring и т. д., это может быть законным объяснением. - person Brice; 01.08.2012
comment
Проблема в том, что когда я пишу тестовые примеры для одного класса, который использует mockito или powermock (например, какой-то класс менеджера), тогда также загружается класс, над которым издеваются. Проблема заключается в том, что каждый раз, когда этот класс издевается, он загружается. Я обнаружил это, когда запускал свои тесты с рекомендованными вами параметрами и -XX:+PrintGCDetails. Я инициализирую макеты в методе, аннотированном с помощью Bore, и в методе, аннотированном с помощью After, я назначаю каждому из них значение null. Я не знаю, почему эти классы не собираются мусором. Я попытаюсь вызвать сборку мусора напрямую. - person BlueLettuce16; 02.08.2012
comment
Сборка мусора Runtime.getRuntime().gc() не помогла :( Есть другие идеи? - person BlueLettuce16; 02.08.2012
comment
Да, это проблема этого CGLIB, если эти классы не собираются мусором, это, безусловно, потому, что на загрузчик классов все еще где-то ссылаются, может быть, через какой-то другой класс, на который ссылается статическое поле, или что-то в этом роде. В любом случае, если это действительно утечка загрузчика классов, это неприятная утечка, и ее будет трудно найти и решить. Также вы можете отслеживать классы, которые загружаются несколько раз. И было бы интересно измерить память для каждого тестового класса. Кроме того, вам может понадобиться фактически проанализировать память с помощью таких инструментов, как jmap/Eclipse MAT/профилировщик Netbeans. - person Brice; 02.08.2012
comment
PowerMock — замечательная технология, но это правда, что интенсивное использование пользовательской загрузки классов иногда может вызывать проблемы. К счастью, пользовательская загрузка классов (которую, кстати, делает и CGLIB) не требуется для включения неограниченного мока. JMockit делает это, изменяя байт-код уже загруженных классов, не создавая новых. (Кроме того, это не требует аннотирования тестовых классов.) - person Rogério; 02.08.2012
comment
@Rogerio Это невозможно в Java без помощи агента. Использует ли JMockit его? - person Brice; 03.08.2012
comment
@Brice Да, он использует java.lang.instrument, что требует загрузки агента Java в работающую JVM. Однако это прозрачно для пользователей, т. е. им не нужно добавлять параметр -javaagent. (Другие инструменты на основе агентов требуют этого, но только потому, что они не используют преимущества API Attach.) - person Rogério; 03.08.2012
comment
@Rogerio Хорошо, но API прикрепления доступен только в Java 6, если я правильно помню (я помню слайды JBoss Byteman). Я все еще знаю, что многие проекты все еще полагаются на JDK 5. В любом случае, это действительно полезно знать для будущих работ, теперь, когда мы создали точку расширения для создания макетов с использованием другого движка. - person Brice; 04.08.2012
comment
@Brice Да, для использования Attach API требуется JDK 1.6+. Однако это не является реальной проблемой, так как большинство пользователей уже запускают свои тесты на Java 6+, а те, кто застрял с JDK 1.5, всегда могут использовать параметр -javaagent. (На самом деле происходит то, что некоторые проекты по-прежнему развертываются на сервере Java 5, но это не мешает им безопасно запускать тесты на машинах разработки/сборки с JDK 1.6 или даже новее — просто используйте javac - источник 1.5 - цель 1.5.) - person Rogério; 04.08.2012
comment
@Rogerio Согласен, все же я бы не хотел навязывать это ни параметру агента, ни запуску тестов в JDK6 :) - person Brice; 04.08.2012