2014-12-07 1 views
5

Я часто работаю с методами, которые принимают обратные вызовы, и обратные вызовы кажутся несколько трудными для тестирования. Давайте рассмотрим следующий сценарий, если есть метод, который принимает обратный вызов с помощью одного метода (для простоты я предполагаю, что метод тестирования является синхронным), следующий шаблонный шаблон может быть написан только для обеспечения вызова метода обратного вызова:Тестирование модуляции Java: самый простой способ проверить, вызван ли обратный вызов

@Test 
public void testMethod() { 
    final boolean[] passed = {false}; 
    method(new Callback() { 
     @Override 
     public void handle(boolean isSuccessful) { 
      passed[0] = isSuccessful; 
     } 
    }); 
    assertTrue(passed[0]); 
} 

Это похоже на суррогат. Я хотел бы знать: есть ли более элегантный способ протестировать такой код, чтобы сделать код выше похожим на псевдокод ниже?

@Test 
public void testMethod() { 
    // nothing explicit here, implicit boolean state provided by a test-runner 
    method(new Callback() { 
     @Override 
     public void handle(boolean isSuccessful) { 
      if (isSuccessful) { 
       pass(); // not sure how it should look like: 
         // * an inherited method that sets the state to "true" 
         // * or an object with the pass method 
         // * whatever 
         // but doesn't exit testMethod(), just sets the state 
      } 
     } 
    }); 
    // nothing explicit here too: 
    // test runner might check if the state is changed to true 
    // otherwise an AssertionError might be thrown at the end of the method implicitly 
} 

Немного чище. Возможно ли это в JUnit, TestNG или любой другой структуре тестирования? Благодаря!


UPDATE

К сожалению, я, кажется, задал смутный вопрос, который на самом деле не соответствует то, что я хотел спросить. Я в основном имел в виду любой код (не обязательно обратный вызов), который может быть вызван, если определенные условия удовлетворяются только до , чтобы установить состояние результата в значение. Проще говоря, я просто хочу избавиться от начального boolean[] passed и окончательного assertTrue(passed[0]), считая, что они являются своего рода прологом и эпилогом соответственно и предполагают, что начальное состояние установлено на false, поэтому необходимо вызвать pass(), чтобы установить состояние на true , Независимо от того, , какpassed[0] установлен в true, где бы ни было. Но, к сожалению, я задал этот вопрос, используя контекст обратных вызовов, однако это просто вариант, а не требование. Таким образом, название вопроса не отражает то, что я действительно хотел спросить, но перед обновлением были опубликованы некоторые ответы.

ответ

8

Это обычно то, что насмешливое фреймворк может сделать для вас.

С Mockito, например:

// imports ommited for brevity 
@Test 
public void callbackIsCalled() 
{ 
    final CallBack callBack = mock(CallBack.class); 
    method(callBack); 

    verify(callBack, only()).handle(any()); 
} 

Конечно, это пример режима проверки (only()) и значением согласовани (any()). Вы можете сделать больше ...

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

+0

Спасибо за ответ! Да, Mockito - действительно хорошая библиотека. Честно говоря, я не принимал во внимание насмешливые библиотеки: я просто хотел избавиться от суррогатного флага и завершающих утверждений. С помощью Mockito необходимо правильно вызвать 'verify'. Я просто подумал, что последнее утверждение может быть неявным и «поставлено» тестовым бегуном. –

+0

Ну, как я уже сказал, это только знак соответствия; если вы хотите, вы можете заменить аргумент 'true', или даже' ArgumentCaptor '. – fge

+0

Почему downvote? – fge

7

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

public class FakeCallback implements Callback { 
    private boolean wasSuccessful; 
    private boolean handleCalled; 

    @Override public void handle(boolean isSuccessful) { 
     this.wasSuccessful = isSuccessful; 
     handleCalled = true; 
    } 

    // Getters for fields above 
} 

Вы можете использовать что-то вроде:

// Arrange... 
FakeCallback callback = new FakeCallback(); 

// Act... 
method(callback); 

// Assert 
assertTrue(callback.wasHandleCalled()); 
assertTrue(callback.wasSuccessful()); 

Вы могли бы абсолютно использовать насмешливую структуру для этого вместо этого, но лично я нахожу, что часто проще создать единую фальшивую реализацию, чем настраивать mocks повторно. Оба способа будут работать.

+0

Спасибо за ответ. Я также думаю о том, чтобы избавиться от завершающих утверждений (это тоже часть неопределенного вопроса), но я не уверен, что это хорошая идея. –

+0

@LyubomyrShaydariv: Что вы пытаетесь проверить? Если вам неважно, был ли вызван обратный вызов или нет, или что аргумент был, то вы можете легко его заглушить ... –

1
список

Дают :: добавить обратного вызова

Когда задача состоит в том, чтобы проверить функцию обратного вызова, который является функциональным интерфейсом принимать один параметр (здесь логическое значение, может также быть строка или любой случайный тип), представляется наиболее кратким, чтобы подготовить список, передать метод обратного вызова List.add(e) как и проверьте содержимое списка:

List<Boolean> callbackArgs = new ArrayList<>(); 
methodUnderTest(callbackArgs::add); 
// assert that the callback was called exactly once and with a "true" value: 
assertEquals(Arrays.asList(true), callbackArgs); 

Альтернативный случай для обратного вызова, который принимает строки:

List<String> callbackArgs = new ArrayList<>(); 
methodUnderTest(callbackArgs::add); 
// assert that the callback was called twice with "foo" and "bar" values respectively: 
assertEquals(Arrays.asList("foo", "bar"), callbackArgs); 

Аналогично, класс счетчика может служить для тестирования обратного вызова, который не принимает никаких параметров. Здесь с помощью AtomicInteger, как кажется, только счетчик типа класса доступен в стандартных библиотеках - свойство атомарности не нужно здесь:

AtomicInteger callbackCounter = new AtomicInteger(); 
methodUnderTest(callbackCounter::incrementAndGet); 
// assert that the callback was called 5 times: 
assertEquals(5, callbackCounter.get()); 
+0

Только заголовок дал мне понять, насколько легко тестировать обратные вызовы с этим. У меня есть класс, который принимает параметр Consumer . Просто перейдите в список :: add, поскольку потребитель сделал тестирование намного проще, так как теперь я могу просто проверить, что размер списка - это то, что я ожидал, вместо того, чтобы делать какие-то странные вещи с помощью mocks. Итак, +1. – Chewtoy

Смежные вопросы