Статьи

Модульное тестирование именованных очередей: Spring 3 + maven2 + Google App Engine

Проблема, у вас есть задание, которое, как вы знаете, может занять более 30 секунд, что вы делаете? Что если эту задачу нужно запускать каждый день в определенное время? Google предоставляет несколько механизмов для решения именно этой проблемы, очереди и запланированной задачи соответственно.

Очереди
Сначала рассмотрим систему «очередей», реализованную в GAE. Обратите внимание, что в настоящее время «очередь» все еще является экспериментальной, что означает, что API может измениться вокруг некоторых, поэтому убедитесь, что у вас есть сильное модульное тестирование, но не волнуйтесь, мы рассмотрим это чуть ниже на странице. Чтобы реализовать очереди, вам нужно сообщить веб-приложению, что ожидаемые очереди будут использоваться, и для этого вам нужно создать файл с именем queue.xml в папке WEB-INF ( пример:/src/main/webapp/WEB-INF/queue.xml). Файл queue.xml имеет определенный макет, который не будет подробно рассмотрен в этом посте. Пожалуйста, ознакомьтесь со структурой queue.xml в документации Google . Мы расширим знания, так как документация не полная.

Запланированная задача
Запланированная задача — это задача, которая будет запускаться с помощью GAE в повторяющиеся моменты времени, эмулируя Linux- cron или приложение Quartz. Чтобы реализовать задачу cron, просто создайте файл с именем cron.xml и поместите его в папку WEB-INF ( пример: /src/main/webapp/WEB-INF/cron.xml ). В этом посте не будет подробно рассказано о выполнении запланированного задания, поэтому, пожалуйста, ознакомьтесь с документацией Google.чтобы получить твердое понимание, прежде чем читать дальше. Чтобы не упаковывать слишком много в один пост, запланированное задание будет рассмотрено в следующем посте.

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

Для решения этой проблемы мы будем использовать очередь и систему запланированных задач, предоставляемую Google App Engine.

Сначала займемся частью очереди, затем создадим запланированное задание. Документация Google по очередям модульного тестирования очень ограничена и показывает только то, как получить очередь «по умолчанию». Поскольку в нашей системе может быть несколько задач, которые должны выполняться с разной скоростью, мы захотим создать новую очередь. Давайте откроем (или создадим) файл queue.xml и вставим следующее:

<?xml version="1.0" encoding="UTF-8"?>
<queue-entries>
    <queue>
        <name>birthdayemail</name>
        <rate>10/s</rate>
        <bucket-size>10</bucket-size>
    </queue>
</queue-entries>

Далее, давайте создадим простой сервис ( Примечание: предположим, что ресурсы Autowired работают, а импорт включен вверху файла, это было уменьшено, чтобы подчеркнуть решение )

@Service("birthdayService")
public class BirthdayServiceImpl implements BirthdayService {

    // this is the same name as in your queue.xml file
    private static final String EVENT_REMINDER_TASK_NAME = "birthdayemail";
    
    @Autowired
    private BirthdayDAO birthdayDAO;

    public List handleGuestEmailsForBirthday(Date date) {
        if (date == null) {
            throw new IllegalArgumentException("Date can not be null");
        }
        List<Guest> guestBirthdays = birthdayDAO.getByDate(date);
        for (Guest guest : guestBirthdays) {
            String TASK_URL = "/queue/birthday-emails/" + guest.getId()
            final TaskOptions taskOptions = TaskOptions.Builder.url(TASK_URL);
            // create a unique task name, note, must conform to [a-z] regex
            taskOptions.taskName(guest.getName() + "Task");
            taskOptions.method(TaskOptions.Method.POST);

            Queue queue = QueueFactory.getQueue(EVENT_REMINDER_TASK_NAME);
            // commented out to show the default queue shown in the docs
            //Queue queue = QueueFactory.getDefaultQueue();
            queue.add(taskOptions);
        }
        return events;
    }
}

Давайте посмотрим, что здесь: во-
первых, мы должны создать URL-адрес, который будет запускаться при выполнении задачи. Этот URL будет обрабатывать указанную единицу работы и должен работать менее чем за 30 секунд в соответствии с ограничениями GAE. Мы не будем рассказывать о том, что делает этот сервлет, просто предположим, что он использует идентификатор, указанный в пути URL, запрашивает гостя, а затем использует его для получения электронных писем друзей гостя; затем использует это для создания электронной почты и передачи на EmailService. (СОВЕТ: EmailService может также реализовать очередь, поэтому каждое электронное письмо отделяется для индивидуального запуска).

Далее, обратите внимание, что мы реализуем «Имя задачи». Это необязательно, но помогает при отладке выяснить, какая задача не выполняется или что выполняется.

Теперь мы находимся в TaskOptions. TaskOptions — это вспомогательная функция, используемая для объединения данных URL и модели для передачи сервлету, обрабатывающему очередь. Просто предложение, но чтобы следовать идеалам REST, желательно установить RequestMethod в POST или PUT, в зависимости от того, что вы пытаетесь сделать. Все задачи должны быть идемпотентными. Это означает, что в случае сбоя или прерывания задачи ее можно запустить снова, не повредив данные.

Наконец, у нас есть QueueFactory, извлекающая именованную очередь (то же имя в файле queue.xml). Просто добавьте ваш объект taskOptions в queue.add и двигайтесь дальше.

Модульное тестирование именованных очередей
Далее, давайте настроим модульный тест для проверки нашей именованной очереди. В документации Google явно не объясняется, как правильно настроить контрольный пример для одного контекста с учетом хранилища данных и именованных запросов. Ниже приведен пример тестового примера, написанного для проверки наших Именованных запросов. ( Обратите внимание, что, как и раньше, импорт был удален, а используемые сервисы использовались ранее. Это не является конечной целью для модульного тестирования GAE, пожалуйста, обратитесь к предыдущему посту за дополнительной помощью, и да, Вы могли бы использовать JMock или Mockito вместо сохраняемых данных, но я не исследовал этот вариант ).

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/testApplicationContext.xml"})
public class QueueBirthdayServiceTest
        extends AbstractJUnit4SpringContextTests {

    @Autowired
    private BirthdayService birthdayService;
    private final LocalServiceTestConfig[] configs = {
        new LocalDatastoreServiceTestConfig(),
    /* NOTE: THE QUEUE XML PATH RELATIVE TO WEB APP ROOT, More info below */
        new LocalTaskQueueTestConfig()
                .setQueueXmlPath("src/test/resources/test-queue.xml")};
    private final LocalServiceTestHelper helper
            = new LocalServiceTestHelper(configs);

    @Before
    public void setUp() {
        helper.setUp();
    }

    @After
    public void tearDown() {
        helper.tearDown();
    }

    @Test
    public void testHandleBirthdayQueueEmails() throws InterruptedException {
        // setup data
        Date birtdayDate = DateUtils.parse("10/16/1900");
        // assume this builds the data for guest/guest's friends
        int guestWithBD = 5; // # of birthdays on date
        int gPerBirthday = 10; // # of emails being sent per birthday
        setupDataForBirthdays(birthdayDate, guestWithBD, gPerBirthday);

        List<Guest> birthdayOnDay = birthdayService.
                handleGuestEmailsForBirthday(birthdayDate);
        assertEquals(guestWithBD, birthdayOnDay.size());
        // pause for a moment to allow queue to fill from previous statment
        Thread.sleep(1000);
        // verify # of birthdays with that day's expire date
        LocalTaskQueue ltq = LocalTaskQueueTestConfig.getLocalTaskQueue();
        final Queue queue = QueueFactory
                .getQueue(BirthdayService.EVENT_REMINDER_TASK_NAME);
        //final Queue queue = QueueFactory.getDefaultQueue();
        QueueStateInfo qsi = ltq.getQueueStateInfo()
                .get(queue.getQueueName());
        assertNotNull(qsi);
        int expectedTaskCount = guestWithBD*gPerBirthday;
        assertEquals(expectedTaskCount, qsi.getTaskInfo().size());
        assertEquals(birthdayOnDay.get(0).getID() + "Task",
                qsi.getTaskInfo().get(0).getTaskName());

    }
}

 ПРИМЕЧАНИЕ. Если синтаксис или модульное тестирование вам чужды, посетите мой предыдущий пост, посвященный модульному тестированию в Google App Engine.

Давайте рассмотрим подробнее
. Первая половина не слишком сложная, просто настройка данных и вызов службы, которая создает очередь. Мы создаем 5 гостей с днями рождения на указанную дату, и у каждого из этих 5 гостей есть 10 друзей, которым мы собираемся написать по электронной почте. Этот сервис уже «выписан» (выше)

На что нужно обратить внимание — в верхней части файла, во время настройки «помощника» мы создали его с помощью объектов LocalDatastoreServiceTestConfig и LocalTaskQueueTestConfig. Второе, «LocalTaskQueueTestConfig» является важным для добавления при использовании очереди. Если вы не используете DefaultQueue, вам нужно будет явно указать, где находится файл queue.xml. Я предлагаю вам создать файл test-queue.xml и поместить его в папку / src / test / resources , чтобы не смешивать производственные данные и тестирование. ПРИМЕЧАНИЕ. Этот файл загружается относительно папки приложения ROOT . Остальная часть теста должна быть достаточно понятной.

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

С http://www.ensor.cc/2010/11/unit-testing-named-queues-spring.html