Общие комментарии
В дополнение к моим комментариям, а также потому, что вы, кажется, новичок в автоматизации тестирования, особенно в фиктивном тестировании, некоторые общие советы:
- Тесты в основном не являются инструмент проверки качества, это только желаемый побочный эффект.
- Вместо этого они являются инструментом дизайна для вашего приложения, особенно при использовании TDD. Т.е. написание тестов помогает вам реорганизовать ваш код для простоты, элегантности, удобочитаемости, удобства сопровождения, тестируемости (вы можете прочитать о чистом коде и мастерстве программного обеспечения):
- Тесты возвращаются в код приложения, т.е. Трудно что-то протестировать, вам следует провести рефакторинг кода.
- Если у вас хорошее тестовое покрытие, вы также можете безбоязненно рефакторировать, т. е. если ваш рефакторинг нарушает логи существующего приложения c, ваши автоматические c тесты будут немедленно обнаружите его, и вы можете исправить небольшой сбой, прежде чем он станет большим беспорядком.
- Один из типичных типов рефакторинга - это удаление сложности из методов путем выделения вложенных слоев логики c в многослойные вспомогательные методы или даже в определенные c классы, обеспечивающие определенный аспект. Это облегчает понимание кода и его тестирование.
- Ознакомьтесь с шаблоном проектирования Dependency Injection (DI). Общий принцип называется Inversion of Control (Io C).
Сказав это, я хотел бы упомянуть, что один типичный анти-паттерн в разработке программного обеспечения ведет к проблемным c проектам приложений и плохим Тестируемость - это если классы и методы создают свои собственные встроенные зависимости вместо того, чтобы разрешать (или даже требовать) пользователю вводить их.
Ответ на заданный вопрос
Ваша ситуация является хорошим примером: вы хотите проверить, что ваши ListenableFutureCallback
перехватчики обратного вызова вызываются, как и ожидалось, но вы не можете, потому что этот объект создан внутри метода sendMessage
как анонимный подкласс и назначен локальной переменной. Local = не тестируется простым способом и без грязных уловок, таких как злоупотребление службой журналов для проверки побочного эффекта этих перехватчиков обратного вызова. Только представьте, что произойдет, если методы больше не будут регистрироваться или будут основаны только на заданном c уровне журнала или условии отладки: тест прервется.
Так почему бы вам не выделить создание экземпляра обратного вызова в специальный сервис или хотя бы метод? Этот метод даже не нужно публиковать c, достаточно защищенного или пакетного уровня - просто не частного, потому что вы не можете высмеивать частные методы.
Вот мой MCVE для вас. Я убрал некоторые сложности, заменив вашу службу журналов на прямую консольную регистрацию, чтобы продемонстрировать, что вам не нужно проверять какие-либо побочные эффекты.
package de.scrum_master.stackoverflow.q61100974;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.SendResult;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
public class KafkaService {
private KafkaTemplate<String, String> kafkaTemplate;
public KafkaService(KafkaTemplate kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
public void sendMessage(String topicName, String message) {
ListenableFuture<SendResult<String, String>> future = kafkaTemplate.send(topicName, message);
future.addCallback(createCallback());
}
protected ListenableFutureCallback<SendResult<String, String>> createCallback() {
return new ListenableFutureCallback<SendResult<String, String>>() {
@Override
public void onSuccess(SendResult<String, String> result) {
System.out.print("Success -> " + result);
}
@Override
public void onFailure(Throwable ex) {
System.out.print("Failed -> " + ex);
}
};
}
}
package de.scrum_master.stackoverflow.q61100974
import org.springframework.kafka.core.KafkaTemplate
import org.springframework.kafka.support.SendResult
import org.springframework.util.concurrent.ListenableFuture
import org.springframework.util.concurrent.ListenableFutureCallback
import org.springframework.util.concurrent.SettableListenableFuture
import spock.lang.Specification
class KafkaServiceTest extends Specification {
KafkaTemplate<String, String> kafkaTemplate = Mock()
ListenableFutureCallback callback = Mock()
// Inject mock template into spy (wrapping the real service) so we can verify interactions on it later
KafkaService kafkaService = Spy(constructorArgs: [kafkaTemplate]) {
// Make newly created helper method return mock callback so we can verify interactions on it later
createCallback() >> callback
}
SendResult<String, String> sendResult = Stub()
String topicName = "test.topic"
String message = "test message"
ListenableFuture<SendResult<String, String>> future = new SettableListenableFuture<>()
def "sending message succeeds"() {
given:
future.set(sendResult)
when:
kafkaService.sendMessage(topicName, message)
then:
1 * kafkaTemplate.send(topicName, message) >> future
1 * callback.onSuccess(_)
}
def "sending message fails"() {
given:
future.setException(new Exception("uh-oh"))
when:
kafkaService.sendMessage(topicName, message)
then:
1 * kafkaTemplate.send(topicName, message) >> future
1 * callback.onFailure(_)
}
}
Пожалуйста, обратите внимание в отношении теста:
- Мы используем
Spy
на KafkaService
, то есть специальный тип частичной имитации обертывания исходного экземпляра. - На этом шпионе мы заглушаем новый метод
createCallback()
для того, чтобы ввести ложный обратный вызов в класс. Это позволяет нам позже проверить, были ли вызваны такие взаимодействия, как onSuccess(_)
или onFailure(_)
, как и ожидалось. - Нет необходимости высмеивать или создавать какие-либо из
RecordMetadata
или TopicPartition
.
Наслаждайтесь! : -)
Обновление: Еще несколько замечаний:
- Шпионы работают, но всякий раз, когда я пользуюсь шпионом, у меня возникает чувство неловкости. Возможно, потому что ...
- выделение методов в защищенные вспомогательные методы - это простой способ дать шпиону возможность заглушить метод или протестировать метод отдельно. Но многие разработчики недовольны практикой делать методы видимыми (даже если они защищены и не публикуются c) только (?), Потому что это облегчает тестирование кода. Я не согласен в основном потому, что, как я уже сказал, тесты - это инструмент проектирования, а более мелкие и более целенаправленные методы лучше понять, поддерживать и использовать повторно. То, что вспомогательный метод не может быть закрытым из-за необходимости заглушить его, иногда не очень приятно. С другой стороны, защищенный вспомогательный метод позволяет нам переопределить его в производственном подклассе, так что есть еще одно преимущество, не связанное с тестированием.
- Так что же является альтернативой? Как я уже говорил выше, вы можете извлечь код в дополнительный класс (внутренний класс stati c или отдельный) вместо дополнительного метода. Этот класс может быть проверен отдельно и может быть подвергнут насмешке и инъекции без использования шпиона. Но тогда, конечно, вам нужно предоставить интерфейс для внедрения экземпляра соавтора через конструктор или установщик.
Не существует идеального решения, с которым согласились бы все разработчики. Я показал вам один, который я считаю довольно чистым, и упомянул еще один.