JMock и EasyMock: сравнение и howto в примерах и не только

  • Tutorial
Практически ни для кого не секрет, что при тестировании кода, использующего какие-то внешние компоненты, часто применяют подход mock-объектов. Для тех, кто всё же о нём не знает, кратко поясню: это такие объекты, которые имеют тот же интерфейс, что и используемые компоненты, но их поведение полностью задаётся в тесте, и их использование позволяет избежать поднятия полной инфраструктуры, необходимой приложению для запуска. Что ещё более важно, можно легко и непринуждённо проконтролировать, что код вызывал те или иные методы у mock-объекта с теми или иными аргументами.

В этой статье я проведу сравнительный анализ двух распространённых в Java библиотек для работы с mock'ами: EasyMock и JMock. Для осознания достаточно базового знания JUnit, а после прочтения этой статьи у вас будет весьма хорошее представление о том, как пользоваться обеими этими библиотеками.

Рассматриваемая задача

В качестве примера того, что необходимо оттестировать, будем рассматривать некоторое приложение, имеющее примерно такую структуру:
1
2
3
4
5
6
7
8
9
public class WayTooComplexClass {
    
    public WayTooComplexClass(String serverAddress) {/*...*/}
    
    public boolean save(long id, String data) {/*...*/}
    
    public String get(long id) {/*...*/}
    
}
Пусть реализация, скрытая троеточиями, в качестве хранилища использует какой-нибудь сервис, имеющий простое HTTP API (например, elliptics). С тем, чтобы для тестов постоянно где-то держать этот сервер, есть как минимум две проблемы:
  1. Необходимо, чтобы с каждой машины, на которой запускается тест, был доступ к этому серверу
  2. Описание поведения mock-сервера находится вне кода, и потому могут возникнуть различные неприятности, особенно если один разработчик обновит тест и в коде и на сервере, а другой не обновится, и тест, который вчера проходил, неожиданно сломается
Некоторые на этом моменте опускают руки, говорят, что их «код слишком сложен для Unit-тестов»™, и забивают их писать. К счастью, мы не из таких, и потому поднимем прямо из теста маленький HTTP-сервер, который будет отвечать на нужные запросы нужным образом. В этом примере я для таких целей использовал jetty. Такой код будет общим при использовании обеих mock-библиотек:

Mock-сервер для тестирования (можно не вчитываться)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class WayTooComplexClassTest {

    static interface RequestHandler {
        String handle(String target, String data);
    }   

    private static class MockHttpServerHandler extends AbstractHandler {

        private RequestHandler handler;

        public void setRequestHandler(RequestHandler handler) {
            this.handler = handler;
        }   

        @Override
        public void handle(String target, HttpServletRequest request,
                           HttpServletResponse response, int dispatch
                          ) throws IOException, ServletException {

            String req = IOUtils.toString(request.getInputStream(), "UTF-8");
            String result = handler.handle(target, req);
            response.setStatus(HttpStatus.ORDINAL_200_OK);
            response.setContentType("text/plain");

            final ServletOutputStream outputStream = response.getOutputStream();
            try {
                outputStream.print(result);
            } finally {
                outputStream.close();
            }   
        }   
    }   

    private static final MockHttpServerHandler SERVER_HANDLER = new MockHttpServerHandler();

    @BeforeClass
    public static void startServer() throws Exception {
        org.mortbay.jetty.Server server = new org.mortbay.jetty.Server();
        server.setHandler(SERVER_HANDLER);
        server.start();
    }   

    private final WayTooComplexClass wayTooComplex = 
              new WayTooComplexClass("http://localhost/9001");

    //Tests go here
}
Тут у нас есть три интересных момента. Первый — это строчки 3-5, описывающие интерфейс RequestHandler, который получает на вход цель запроса (например, в адресе http://habrahabr.ru/blogs/java/136466/ целью будет выделенный жирным /blogs/java/136466/) и данные, отправленные пользователем в теле запроса. Второй — строки с 7 по 32 — класс MockHttpServerHandler. Ему устанавливается RequestHandler, которому делегируется вся «бизнес-логика», а результат его работы записывается в HTTP-ответ. И третий — строки 36-41 — это метод startServer, который, как можно догадаться из аннотации и названия, вызывается перед тем, как начнут запускаться какие-либо тесты, перечисленные в этом классе, и запускает HTTP-сервер.

Первый и самый простой тест

Предположим, что в теории код, спрятанный в методе save, должен пройти по урлу {serverAddress}/upload/{id} и передать туда data. Проверим, происходит ли это в действительности.

JMock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Mockery context = new JUnit4Mockery();

@Test
public void testSaveWithJMock() {
    final long id = 13;
    final String data = "nanofilters";
    
    final RequestHandler requestHandler = context.mock(RequestHandler.class);

    context.checking(new Expectations() {{
        one(requestHandler).handle("/upload/" + id, data);
        will(returnValue("Saved " + id));
    }});

    SERVER_HANDLER.setRequestHandler(requestHandler);

    wayTooComplex.save(id, data);

    context.assertIsSatisfied();
}

В первой же строке нам необходимо создать JMock-контекст, в котором будет выполняться тест. Такого контекста достаточно одного на весь тест, но обойтись без него никак. Чтобы избежать проблем с несколькими mock-ами одного и того же, следует контекст создавать заново перед каждым тестом (то есть, внутри метода, помеченного аннотацией @Before) В восьмой строке мы легко и непринуждённо создаём mock для нашего интерфейса. Далее, в строках 10-13 мы описываем, какие вызовы должны произойти. Синтаксис на первый взгляд не очень интуитивно понятен, но со временем привыкаешь. В строке 11 мы указали, что ожидаем ровно один вызов метода handle с аргументами ("/upload/" + id) и (data). В строке 12 мы говорим, что последний вызов вернёт значение ("Saved " + id). Тут, как вы можете догадаться, нет типовой безопасности. Мы можем случайно передать туда значение не того типа и узнать об этом только в рантайме, схлопотав исключение. Зато если возвращаемое значение не важно, то можно этого вообще не писать: JMock автоматически вернёт значение по умолчанию (0, false, null или пустую строку). Далее мы говорим нашему мок-серверу, что нужно использовать свежесозданный мок-обработчик, вызываем у приложения тестируемый метод и в 19 строке проверяем, что все ожидаемые вызовы были сделаны. От последнего можно избавиться, добавив к самому тест-классу аннотацию @RunWith(JMock.class)

EasyMock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
IMocksControl control = EasyMock.createControl();

@Test
public void testSaveWithEasyMock() {
    final long id = 15;
    final String data = "cold fusion reactor";

    final RequestHandler requestHandler = control.createMock(RequestHandler.class);

    expect(requestHandler.handle("/upload/" + id, data)).andReturn("Saved " + id);
    control.replay();

    SERVER_HANDLER.setRequestHandler(requestHandler);

    wayTooComplex.save(id, data);

    control.verify();
}
В первой строчке мы создаём control, который является аналогом контекста JMock. Далее в восьмой строке создаём мок-объект, в десятой указываем, что ожидаем вызов метода handle с определёнными аргументами и говорим в таком случае возвращать определённое значение. Тут есть типовая безопасность: попытка передать в andReturn аргумент типа, отличного от String, приведёт к ошибке компиляции.
У EasyMock, с моей точки зрения, спецификация ожидаемого поведения более понятна. У такого подхода, правда, есть и недостаток: как вы можете видеть в строке 11, необходимо явно указывать, что запись ожидаемого поведения закончена. После всей «бизнес-логики» мы доходим до строки 17 и проверяем, что все ожидаемые методы были вызваны. Кстати, если нам плевать, что метод возвращает, то для void-методов можно опустить конструкцию expect и просто сделать вызов: requestHandler.handle("/upload/" + id, data). Кроме того, использовать control не обязательно, и можно просто сделать так:
1
2
3
4
5
final RequestHandler requestHandler = EasyMock.createMock(RequestHandler.class);
//...
EasyMock.replay(requestHandler);
//...
EasyMock.verify(requestHandler);


Более сложная реакция на вызов метода


Предположим теперь, что нам необходимо протестировать, что наше приложение ведёт себя корректно и при сбое внешнего компонента. Для этого достаточно организовать исключение в методе handle, и тогда jetty сам поставит http status 500.

JMock

1
2
3
4
5
6
7
8
@Test
public void testErrorHandlingWithJMock() {
    //...
    context.checking(new Expectations() {{
        one(requestHandler).handle("/upload/" + id, data);
        will(throwException(new RuntimeException("Somebody set up us the bomb.")));
    }});
}
Ничего сложного. Думаю, комментарии излишни. Можно разве что добавить, что из коробки will ещё умеет returnIterator, но можно устроить и что-нибудь собственное, реализовав интерфейс Action. Он, правда, не особо понятен, да и документация не ахти.

EasyMock

1
2
3
4
5
6
@Test
public void testErrorHandlingWithEasyMock() {
    //...
    expect(requestHandler.handle("/upload/" + id, data))
            .andThrow(new RuntimeException("All your base are belong to us."));
}
Тут тоже всё просто, но, как вы уже наверняка подозреваете, если метод ничего не возвращает (т.е. имеет тип void), приходится писать по-другому, и теряется общность стиля. Выглядело бы это так:
1
2
3
4
5
6
@Test
public void testErrorHandlingWithEasyMock() {
    //...
    requestHandler.handle("/upload/" + id, data); expectLastCall()
            .andThrow(new RuntimeException("You have no chance to survive make your time."));
}
Кроме andThrow можно ещё использовать andDelegateTo, который, как можно догадаться, делегирует вызов метода какому-то другому объекту (нет типовой безопасности во время компиляции!) и andAnswer. Для последнего нужно реализовать интерфейс IAnswer, и внутри метода answer написать любой код. Тут несколько меньше мощности, чем у JMock, но зато гораздо, гораздо больше удобства.

Matching аргументов при вызове метода


Теперь предположим, что мы точно не знаем, какие именно аргументы должны быть переданы в вызов mocked метода. Единственное, в чём мы точно уверены — это то, что аргумент target должен где-то содержать в себе id.

JMock

1
2
3
4
5
6
7
@Test
public void testArgumentMatchingWithJMock() {
    //...
    context.checking(new Expectations() {{
        one(requestHandler).handle(with(containsString(String.valueOf(id))), anything());
    }});
}
JMock использует matcher-ы от hamcrest и позволяет при необходимости добавить блекдж что-то нестандартное написать свой matcher.

EasyMock

1
2
3
4
5
6
@Test
public void testArgumentMatchingWithEasyMock() {
    //...
    expect(requestHandler.handle(contains(String.valueOf(id)), anyObject(String.class)))
            .andReturn(null);
}
Тут используются собственные matcher-ы, и потому набор уже готовых matcher-ов меньше. Однако то, что уже есть, сделано довольно прилично и удовлетворяет все основные потребности, а остальные точно так же, как и в JMock, можно удовлетворить, реализовав нужный matcher самостоятельно.

Число вызовов

Теперь немного поменяем условия, сказав, что какой-то метод должен вызваться несколько раз. Пример стал довольно далёким от реальности, но выдумывать более жизненный исключительно для того, чтобы показать что-то простое, не хочется.

JMock

1
2
3
4
5
6
7
@Test
public void testMultipleInvocationsWithJMock() {
    //...
    context.checking(new Expectations() {{
        between(2, 5).of(requestHandler).handle(anything(), anything());
    }});
}
Как мог догадаться внимательный читатель, во всех предыдущих примерах слово one было не просто так, а указывало на то, сколько вызовов ожидать. Поддерживаются все необходимые количества вызовов: будь то ноль или пофиг-сколько.

EasyMock

1
2
3
4
5
6
@Test
public void testMultipleInvocationsWithEasyMock() {
    //...
    expect(requestHandler.handle(anyObject(String.class), anyObject(String.class)))
            .andReturn(null).times(2, 5);
}
Тут тоже всё довольно просто, но есть небольшой минус: нет возможности сказать «как минимум n раз» без ограничения сверху. Это слегка странно, учитывая, что метод atLeastOnce есть. Чтобы ожидать как минимум три вызова, нужно написать примерно такой код:

1
2
3
4
5
6
7
@Test
public void testMultipleInvocationsWithEasyMock() {
    //...
    expect(requestHandler.handle(anyObject(String.class), anyObject(String.class)))
            .andReturn(null).times(3);
    expectLastCall().andReturn(null).anyTimes();
}


Заглушки


Нередко бывает так, что нам в принципе не очень важно, как и когда будут вызывать какой-то метод, а интересно лишь чтобы он существовал и что-нибудь возвращал.

JMock

1
2
3
4
5
6
7
8
@Test
public void testStubMethodsWithJMock() {
    //...
    context.checking(new Expectations() {{
        allowing(requestHandler).handle(anything(), anything());
        will(returnValue("There will be cake"));
    }});
}
Заметно, что это делается единообразно с определением числа раз. Вместо allowing можно сказать ignoring. Также при желании легко сделать весь Mock-объект одной большой заглушкой:
1
2
3
4
5
6
7
@Test
public void testStubObjectsWithJMock() {
    //...
    context.checking(new Expectations() {{
        allowing(requestHandler);
    }});
}

EasyMock

1
2
3
4
5
6
@Test
public void testStubMethodsWithEasyMock() {
    //...
    expect(requestHandler.handle(anyObject(String.class), anyObject(String.class)))
            .andStubReturn("Greetings, human.");
}
Слегка менее единообразно по стилю, но тоже вполне удобно. А вот сделать объект затычкой можно только при создании:
1
2
3
4
5
@Test
public void testStubObjectsWithEasyMock() {
    final RequestHandler requestHandler = createNiceMock(RequestHandler.class);
    //...
}
Методы тогда при вызове будут возвращать значение по умолчанию для своего типа (0, false или null). null будет возвращён в том числе и для String, тогда как в JMock для этого класса значение по умолчанию — пустая строка.

Проверка порядка вызова методов


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

JMock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void testStrictOrderingWithJMock() {
    //...
    final Sequence paranoia = context.sequence("shhh-they-are-watching-us");

    context.checking(new Expectations() {{
        one(requestHandler).handle("/upload/" + id, data);
        will(returnValue("Saved " + id));
        inSequence(paranoia);

        one(requestHandler).handle("/get/" + id, "");
        will(returnValue(data));
        inSequence(paranoia);
    }});
}
Тут нас интересуют строки 9 и 13, в которых мы, собственно, и проверяем порядок выполнения. Важно помнить, что inSequence будет следить за порядком только того вызова mock-объекта, непосредственно за которым он указан. Поэтому чтобы отследить, что десять разных вызовов будут сделаны в строгом порядке, придётся десять раз написать inSequence. Зато можно навешивать на вызов сразу несколько последовательностей.

EasyMock

1
2
3
4
5
6
7
@Test
public void testStrictOrderingWithEasyMock() {
    //...
    EasyMock.checkOrder(requestHandler, true);
    EasyMock.expect(requestHandler.handle("/upload/" + id, data)).andReturn("Saved " + id);
    EasyMock.expect(requestHandler.handle("/get/" + id, "")).andReturn(data);
}
Тут всё несколько проще: проверку можно включать и выключать по несколько раз для каждого мока по отдельности. Кроме того, строгим может быть целый control (см. первый пример), и тогда будет проверяться общий порядок для всех входящих в него mock-ов. Кроме того, мок или контрол можно сделать строгим сразу при создании, сказав createStrictMock. Конечно, такое количество зелёного нужно разбавить серьёзным минусом: один мок (или контрол) не может принимать участия в нескольких последовательностях. Тут даже понятия такого нет.

Условия, при которых вызов метода разрешён


В некоторых случаях необходимо промоделировать состояние приложения, которое будет изменяться при вызове некоторых методов, и проверяться при вызове некоторых других (или тех же). Пусть наше приложение может обратиться на один из двух инстансов elliptics (инстансы называются panola и yarbo) для загрузки файла, и потом должно скачать его с того же самого инстанса, на который загрузило. Это тоже можно проверить:

JMock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
public void testStatesWithJMock() {
    //...
    final States progress = context.states("progress").startsAs("none");

    context.checking(new Expectations() {{
        one(requestHandler).handle("/panola/upload/" + id, data);
        will(returnValue("Saved " + id));
        when(progress.is("none")); then(progress.is("panola"));

        one(requestHandler).handle("/yarbo/upload/" + id, data);
        will(returnValue("Saved " + id));
        when(progress.is("none")); then(progress.is("yarbo"));

        one(requestHandler).handle("/panola/get/" + id, "");
        will(returnValue(data));
        when(progress.is("panola")); then(progress.is("done"));

        one(requestHandler).handle("/yarbo/get/" + id, "");
        will(returnValue(data));
        when(progress.is("yarbo")); then(progress.is("done"));
    }});
}
Смотреть нужно на строки 9, 13, 17 и 21. В каждой из них мы проверяем с помощью when, что приложение находится сейчас в правильном состоянии, и затем выставляем новое с помощью then. Очень удобно. Приверженцы парадигмы автоматного программирования, наверное, сейчас думают о том, что нашли способ тестирования их мечты.

EasyMock: аналога нет



Тестирование многопоточного кода


Вообще говоря, тестировать многопоточный код очень и очень сложно, потому что различных комбинаций того, когда тот или иной поток выполнит то или иное действие, очень много. Особенно если вы не объявляете всё критическими секциями, а стараетесь обойтись минимумом блокировок. Абстрагируемся сейчас от нашего замечательного приложения, и посмотрим прямо на то, как дела с поддержкой многопоточности у разных библиотек.

JMock


Честно сказать, для меня это было страшной мукой: документация по этому вопросу (как, впрочем, и по многим другим) довольно фиговая, и потому действовать приходилось методом проб и ошибок (а поскольку тогда я был в Амстердаме, и к тому же в аэропорте, это было не так-то и просто). В итоге меня спасли вручную скачанная версия JMock 2.6.0-RC2 с вручную же скачанной версией hamcrest-core 1.3.0RC1 и следующий код:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private Synchroniser synchroniser = new Synchroniser();
private Mockery context = new JUnit4Mockery() {{
    this.setThreadingPolicy(synchroniser);
}};

@Test
public void testConcurrencyWithJMock() {
    //...
    final States progress = context.states("progress").startsAs("none");

    context.checking(new Expectations() {{
        //check something
        then(progress.is("done"));
    }});
    //do something in multiple threads
    
    synchroniser.waitUntil(states.is("done"), TimeUnit.SECONDS.toMillis(1));
}
Тут мы в первой строке создаём новый синхронизатор, в третьей говорим контексту, что его нужно использовать, а в 17й дожидаемся того, что все нужные операции были выполнены. То, что States позволяет это сделать — очень здорово, и в некоторых случаях избавляет от необходимости дожидаться того, когда приложение закончит свою работу.

EasyMock


Здесь есть полная поддержка многопоточности из коробки. Более того, её можно отключить, используя во время записи makeThreadSafe(mock, false), а при необходимости и проверить, что mock использовался из одного потока, сказав checkIsUsedInOneThread(mock, true)

Mocking классов


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

JMock

1
2
3
4
5
6
7
8
private Mockery context = new JUnit4Mockery() {{
    this.setImposteriser(ClassImposteriser.INSTANCE);
}};

@Test
public void testClassMockingWithJMock() {
    //...
}
Всё, что нужно сделать, это вызвать у контекста метод setImposteriser. В примере это происходит во второй строке.

EasyMock

1
2
3
4
5
6
import static org.easymock.classextension.EasyMock.*;

@Test
public void testClassMockingWithEasyMock() {
    //...
}
Тут достаточно использовать методы из другого класса, который, правда, находится в другом артефакте maven. Впрочем, с classextension вы можете спокойно создавать моки как на классы, так и на интерфейсы.

Частичный mocking


Ещё иногда может оказаться необходимым сделать mock только на часть методов класса, не трогая остальные.

JMock: такой возможности нет



EasyMock

1
2
3
4
5
6
7
@Test
public void testPartialMockingWithEasyMock() {
    //...
    IntArraySorter sorter = EasyMock.createMockBuilder(IntArraySorter.class)
        .addMockedMethod("sort", int[].class).createMock();
    //...
}
Всё просто и понятно. Также можно указывать, какие параметры передавать в какой конструктор. Все не-mocked методы будут делегированы настоящему классу.

Заключение

На этом примеры и сравнения закончены. Надеюсь, все, дочитавшие досюда, теперь неплохо умеют пользоваться и той и другой библиотекой, и в будущем, используя обретённые навыки, спасут себя от многих ошибок.

Возможно, читатель ждёт тут от меня ответа на вопрос «Так что же мне лучше использовать в своём проекте? JMock или EasyMock?». Ответ тут очень простой и однозначный: «Зависит от требований проекта. Каждая из этих библиотек — инструмент, и каждым из них нужно уметь пользоваться, а выбирать какой-то один нужно для конкретной задачи».

На этом всё. Жду интересных вопросов и замечаний!
Поделиться публикацией

Комментарии 25

    +8
    интересный обзор.
    сам из них всех использую mockito. не рассматривали его как кандидата в сравнения?
      +1
      «А я ждал этого вопроса» ©

      Дело в том, что Mockito и JMockit я знаю только в теории и небольшой практике, но на реальных проектах пока возможности использовать не было. Потому написал о том, что пробовал в жизни. Вскоре, впрочем, наконец смогу опробовать JMockit, и тогда напишу продолжение.
      +10
      А как же Mockito? Мне кажется, он круче обоих )).
        +2
        Не вы один так считаете: stackoverflow.

        PS При работе с Mockito появился термин: замОчить класс/метод =)
          0
          Но там же пишет создатель PowerMock, который рекламирует свой продукт. Его я не пробовал.
            +1
            Не понимаю о чём вы — выдержка из самого популярного ответа:
            I've had good success using Mockito.

            When I tried learning about JMock and EasyMock, I found the learning curve to be a bit steep (though maybe that's just me).

            I like Mockito because of its simple and clean syntax that I was able to grasp pretty quickly. The minimal syntax is designed to support the common cases very well, although the few times I needed to do something more complicated I found what I wanted was supported and easy to grasp.
              0
              I am the creator of PowerMock so obviously I must recommend that! :-)

              PowerMock extends both EasyMock and Mockito with the ability to mock static methods, final and even private methods. The EasyMock support is complete, but the Mockito plugin needs some more work. We are planning to add JMock support as well.

              А вот сравнение с JMockit других фреймворков:
              code.google.com/p/jmockit/wiki/MockingToolkitComparisonMatrix
                0
                Мокать статику не есть хорошо, однако. Хотя когда активно используется какая-нибудь (гуи-)либа, иногда этого не избежать…
                  0
                  Не только гуи. Представьте, что вы, например, пишете что-нить типа ListUtils. Там все методы статические. А тестировать надо. Может возникнуть ситуация, что вам надо что-то промОкнуть…
          0
          Ответил выше
            +2
            Подтверждаю, игрался как с JMock и EasyMock, но как увидел Mockito сразу же забыл о первых двух. Если по возможностям не могу сказать что-то конкретного (первые два уже не стал глубоко изучать и писать «продакшн тесты», то по синтаксису dsl от Mockito приятнее: docs.mockito.googlecode.com/hg/org/mockito/Mockito.html
              0
              А вы пробовали jMockit?
                0
                Нет, остановило, что он был следующим у меня в списке после мокиты — остановил похожий на jmock dsl с new Verifications() {{

                Yо в планах посмотреть на него поближе нежели «кусочек экземпла», есть люди которые уверяют что он гораздо круче мокиты.

                Думаю этот момент наступит когда упрусь в возможности мокиты
                  0
                  Я вот буквально час назад уперся в то, что надо мокнуть статический метод ((.
                    0
                    Первый раз я уперся в то, что мне нужно было протестировать метод который делает некоторые вещи на основе аннотаций. На j.l.Class Мокито, как оказалось натянуть моки не в состоянии…
            +2
            Mockito — самый элегантный фреймворк для создания моков.
            0
            А чем вы код подсвечивали?
              0
              Этому трюку меня научил один хабрапользователь, но я, к сожалению, забыл его имя. Всё просто:
              <table><tr><td><sou_rce>
              1
              </sou_rce></td><td><sou_rce lang="java">
              System.out.println("hello, world!");
              </sou_rce></td></tr></table>
              

              Даёт такой результат:
              1
              
              System.out.println("hello, world!");
              


              Только подчёркивание из source уберите :)
                0
                Эээ, не вышло. В комментариях, похоже, не работает.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое