#java #unit-testing #mocking #jakarta-mail
#java #модульное тестирование #издевательство #джакарта-почта
Вопрос:
Я хочу использовать тесты junit в моем следующем проекте, но я не уверен, какой из нескольких макетных пакетов мне следует использовать. Я также прочитал пару руководств, но я не нашел информации о том, как решить конкретную проблему, описанную ниже. Возможно, эта функция недоступна в пакетах, которые я проверил.
Вот проблема: я хочу написать класс фильтра электронной почты, который выполняет итерацию по List<javax.mail.Message>
и фильтрует сообщения электронной почты по теме, дате, откуда, кому и т.д.Код для тестирования выглядит следующим образом:
public List<Message> doFilter(List<Message> messageList) {
List<Message> newList = new ArrayList<Message>(messageList.size());
try {
for (Message message: messageList) {
if (start != null) {
Date sentDate = message.getSentDate();
if (sentDate == null || sentDate.before(start))
continue;
}
if (end != null) {
Date receivedDate = message.getReceivedDate();
if (receivedDate == null || receivedDate.after(end))
continue;
}
newList.add(message);
}
}
catch(Exception e) {
e.printStackTrace();
}
return newList;
}
Итак, очевидный тестовый пример — создать List
из нескольких сообщений и проверить, содержит ли новый список, возвращаемый фильтром, правильные сообщения.
Но javax.mail.Message
является абстрактным и не может быть создан экземпляр напрямую. Для этого мне нужно было бы настроить реальное хранилище электронной почты, включая имя учетной записи и пароль.
Итак, мои вопросы:
-
Как мне имитировать несколько
javax.mail.Message
объектов с разными значениями, чтобы мой класс filter мог вызыватьmessage.getSentDate()
и другиеMessage
методы, извлекая значения, которые я определил в коде настройки теста? -
Какие из макетных пакетов лучше всего подходят для решения такого рода задач?
Все ответы действительно ценятся.
Ответ №1:
Я попробовал оба подхода, чтобы узнать о преимуществах и недостатках:
Подход 1: создайте новый класс MockMessage, который реализует только те методы, которые мне нужны в моем коде фильтра, затем напишите стандартные тесты JUnit.
Проблема с отсутствующим абстрактным методом на самом деле тривиальна, поскольку эти методы могут быть сгенерированы используемой мной IDE (они будут пустыми, но в любом случае они не нужны тестируемому коду). На самом деле никакой макет пакета не используется.
public class MockMessage extends Message {
private Date sentDate;
public MockMessage(Date sentDate) {
this.sentDate = sentDate;
}
public Date getSentDate() throws MessagingException {
return sentDate;
}
// the rest of the required methods can easily be generated by the IDE
...
}
public void testFilter1() {
try {
DateTimeFormatter dtf = DateTimeFormat.forPattern("yyyy-MM-dd hh:mm:ss");
final Date date0 = dtf.parseDateTime("2011-05-19 05:51:26").toDate();
final Date date1 = dtf.parseDateTime("2011-05-19 05:51:27").toDate();
final Date date2 = dtf.parseDateTime("2011-05-19 05:51:28").toDate();
final List<Message> mockMessages = Arrays.asList(
(Message)new MockMessage(date0),
(Message)new MockMessage(date1),
(Message)new MockMessage(date2)
);
MessageFilter filter = new ByDate(date1, null);
List<Message> result = filter.doFilter(mockMessages);
assertEquals(result.size(),2);
assertEquals(result.get(0).getSentDate(),date1);
assertEquals(result.get(1).getSentDate(),date2);
}
catch(Exception e) {
fail(e.getMessage());
}
}
Подход 2: использование JMock и ClassImposteriser. Объем кода немного меньше, чем в подходе 1, и никакого дополнительного класса MockMessage не требуется.
Я все еще предпочитаю первый подход, поскольку он проще для понимания. Второй подход может быть полезен в других ситуациях, когда объекты создавать намного сложнее, чем javax.mail.Message .
public void testFilter2() {
try {
DateTimeFormatter dtf = DateTimeFormat.forPattern("yyyy-MM-dd hh:mm:ss");
final Date date0 = dtf.parseDateTime("2011-05-19 05:51:26").toDate();
final Date date1 = dtf.parseDateTime("2011-05-19 05:51:27").toDate();
final Date date2 = dtf.parseDateTime("2011-05-19 05:51:28").toDate();
Mockery mockery = new Mockery();
mockery.setImposteriser(ClassImposteriser.INSTANCE);
final List<Message> mockMessages = Arrays.asList(
mockery.mock(Message.class, "message 1"),
mockery.mock(Message.class, "message 2"),
mockery.mock(Message.class, "message 3")
);
mockery.checking(new Expectations() {{
allowing(mockMessages.get(0)).getSentDate(); will(returnValue(date0));
allowing(mockMessages.get(1)).getSentDate(); will(returnValue(date1));
allowing(mockMessages.get(2)).getSentDate(); will(returnValue(date2));
}});
MessageFilter filter = new ByDate(date1, null);
List<Message> result = filter.doFilter(mockMessages);
assertEquals(result.size(),2);
assertEquals(result.get(0).getSentDate(),date1);
assertEquals(result.get(1).getSentDate(),date2);
}
catch(Exception e) {
fail(e.getMessage());
}
}
Ответ №2:
Просто создайте класс MockMessage, который расширяет Message. Затем вы можете реализовать или переопределить поведение, которое вы хотите имитировать.
Что касается макетных фреймворков, я обнаружил, что с ними больше проблем, чем они того стоят, но YMMV.
Комментарии:
1. Если вы не считаете, что насмешливые фреймворки того стоят, тогда вам нужно больше почитать о них. Они необходимы для правильного модульного тестирования.
2. javax.mail.Message имеет множество абстрактных методов, поэтому расширение Message выполнимо, но непрактично.
3. Я не понимаю, почему количество методов в интерфейсе должно влиять на ваше решение о его реализации (за исключением, может быть, того, чтобы дать вам понять, что это дерьмовый интерфейс, и его следует избегать). Любая приличная IDE в любом случае сгенерирует реализации для вас.
4. вы правы, в Jetbrains IDEA действительно есть команда для генерации отсутствующих методов, но я не знал об этом до сих пор.
Ответ №3:
У большинства имитирующих фреймворков не должно возникнуть проблем с этим, хотя тот факт, что вы имитируете класс, а не интерфейс, делает это немного более неудобным.
Вот простой пример использования JMock (фреймворк, с которым я наиболее знаком):
Mockery mockery = new Mockery();
mockery.setImposteriser(ClassImposteriser.INSTANCE);
final List<Message> mockMessages = asList(
mockery.mock(Message.class, "message1"),
mockery.mock(Message.class, "message2")
);
mockery.checking(new Expectations() {{
allowing(mockMessages.get(0)).getSubject(); will(returnValue("some subject"));
allowing(mockMessages.get(1)).getSubject(); will(returnValue("some other subject"));
}});
Обычно я бы отговаривал людей от издевательств над такого рода вещами и просто создавал объекты напрямую, но JavaMail API настолько неудобен в использовании, что понятно, что они не хотят этого делать.