Статьи

Тестирование OSGi с экзаменом Pax

После небольшой задержки я наконец-то представлю вам следующий выпуск моей серии статей, посвященных OSGi. На этот раз мы рассмотрим написание тестов для пакетов OSGi.

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

Несколько месяцев назад я заявил, что тестировать OSGi довольно просто. Сегодня я собираюсь показать это, чтобы показать вам, Pax Exam.

Pax Exam — это набор инструментов для тестирования, который решает проблему тестирования на уровне пакета. Что особенно интересно в Pax Exam, так это то, как он работает. Когда выполняется тест Pax Exam, он запускает среду OSGi, устанавливает и запускает выбор пакетов, а затем делает доступным OSGi BundleContext, с помощью которого вы можете делать утверждения о своих пакетах, сервисах, которые они публикуют, и эффектах, которые они есть друг на друга.

Прежде чем мы начнем, вы должны знать, что я немного переработал проект переводчика Pig Latin. Новая версия о переводческих услугах в целом, одной из которых является Pig Latin. Некоторые имена пакетов изменены, и теперь это проект на основе Pax Construct. Я не собираюсь подробно останавливаться на новом коде, но он должен быть достаточно знаком, чтобы вы могли его понять. Загрузите пример кода, чтобы следовать.

Чтобы начать работу с Pax Exam, мы собираемся создать отдельный проект для размещения наших тестов. Есть много способов сделать это, но зная, что наш пакетный тестовый проект будет содержать только файл Maven pom.xml и один тестовый класс, я нахожу достаточно легким воспользоваться командой Unix mkdir и ее опция -p:

translators% mkdir -p bundle-tests-exam/src/test/java/com/habuma/translator/test

Это устанавливает всю необходимую нам структуру каталогов. Чтобы заполнить его, мы поместим файл pom.xml в каталог bundle-tests-exam и класс test в каталог test нижнего уровня. (Извините пользователей Windows … Я не думаю, что ваш mkdir имеет эквивалент -p, поэтому вам придется создавать структуру каталогов вручную.)

Итак, давайте создадим файл pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

<modelVersion>4.0.0</modelVersion>
<groupId>com.habuma.translator</groupId>
<artifactId>bundle-tests-exam</artifactId>
<version>1.0.0</version>

<packaging>jar</packaging>

<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
</plugins>
</build>

<dependencies>
<dependency>
<groupId>com.habuma.translator</groupId>
<artifactId>interface</artifactId>
<version>1.0.0</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam</artifactId>
<version>1.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-container-default</artifactId>
<version>1.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-junit</artifactId>
<version>1.0.0</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.5</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

Есть несколько вещей, которые я хотел бы выделить в этом файле pom.xml:

  • Сначала обратите внимание, что я установил плагин компилятора для использования Java 1.5. Экзамен Pax основан на JUnit 4, который сам основан на аннотациях Java 1.5. Эта конфигурация плагина гарантирует, что Maven не захлебнется, когда увидит аннотации @Test и @RunWith в нашем тестовом классе.
  • Существует пять зависимостей, первой из которых является пакет интерфейса переводчика. Наш тестовый класс не собирается напрямую использовать реализацию сервиса Pig Latin, но он будет работать с интерфейсом Translator, поэтому нам понадобится этот комплект
  • Следующие 3 зависимости — это сам экзамен Pax.
  • Последняя зависимость — JUnit 4.5.

С установкой Maven мы готовы написать наш тестовый класс. PigLatinTranslatorBundleTest использует JUnit4TestRunner Pax Exam для запуска серии тестов OSGi.

package com.habuma.translator.test;
import static org.junit.Assert.*;
import static org.ops4j.pax.exam.CoreOptions.*;
import static org.ops4j.pax.exam.container.def.PaxRunnerOptions.*;
import java.util.List;
import java.util.Properties;
import org.junit.Test;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.Inject;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.Configuration;
import org.ops4j.pax.exam.junit.JUnit4TestRunner;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;

import com.habuma.translator.Translator;

@RunWith(JUnit4TestRunner.class)
public class PigLatinTranslatorBundleTest {
@Inject
private BundleContext bundleContext;

private Translator translator;

@Before
public void setup() throws Exception {
translator = retrievePigLatinService();
}

@Configuration
public static Option[] configuration()
{
return options(equinox(), profile("spring.dm"), provision(
mavenBundle().groupId("com.habuma.translator").artifactId("interface"),
mavenBundle().groupId("com.habuma.translator").artifactId("pig-latin")
));
}

// ... Test methods go here ...

private Translator retrievePigLatinService() throws InterruptedException {

ServiceTracker tracker = new ServiceTracker(bundleContext,
Translator.class.getName(), null);
tracker.open();
Translator pigLatinService = (Translator) tracker.waitForService(5000);
tracker.close();
assertNotNull(pigLatinService);
return pigLatinService;
}
}

Вероятно, наиболее интересной частью PigLatinTranslatorBundleTests является метод configuration (). Он помечен @Configuration, чтобы указать, что этот метод будет возвращать массив параметров экзамена Pax — эффективно конфигурировать тестовый пример экзамена Pax.

В методе configuration () я использую набор статических методов, предоставляемых классами Pax Exam CoreOptions и PaxRunnerOptions. В частности, я прошу Pax Exam запустить тесты в последней версии Equinox, используя профиль Spring-DM Pax Runner. Что касается пакетов, которые я хочу протестировать, я прошу его установить интерфейс проекта переводчика и латинские пакеты.

Когда тест Pax Exam запускается, первое, что он сделает, это запустит платформу OSGi (в данном случае — последнюю версию Equinox), установит пакеты, указанные в профиле Spring-DM, а затем установит наши пакеты, которые нам нужны. тестировать. На этом этапе среда выполнения OSGi запущена, загружена и готова к работе.

Но прежде чем мы сможем протестировать наш пакет Pig Latin, нам понадобится ссылка на сервис Pig Latin и BundleContext. Для этого Pax Exam предлагает аннотацию @Inject для автоматического предоставления BundleContext для теста. При наличии BundleContext метод setup () вызывает метод retrievePigLatinService () для поиска службы.

Теперь мы готовы начать тестирование наших пакетов. Первое, что мы должны сделать, это убедиться, что BundleContext доступен. Если что-то пойдет не так во время запуска OSGi, у нас не будет BundleContext для работы, и нет смысла тестировать что-либо еще. Итак, давайте напишем тест, который просто утверждает, что переменная bundleContext не равна нулю:

@Test
public void bundleContextShouldNotBeNull() throws Exception {
assertNotNull(bundleContext);
}

Большой! Предполагая, что BundleContext доступен, мы можем теперь сделать что-то более интересное, например, проверить, что мы получаем сервис, который реализует интерфейс Translator, который мы ожидаем:

@Test
public void serviceReferenceShouldExist() {
ServiceReference serviceReference =
bundleContext.getServiceReference(Translator.class.getName());
assertNotNull(serviceReference);
assertEquals("Pig Latin",
serviceReference.getProperty("translator.language"));
}

Здесь я ищу ссылку на сервис для интерфейса Translator и утверждаю, что сервис был зарегистрирован со свойством translationator.language, установленным в «Pig Latin».

Если этот тест пройден, то мы знаем, что среда OSGi запущена и работает, и есть служба, которая претендует на реализацию интерфейса Translator. Наконец, давайте напишем еще один тест, чтобы убедиться, что сервис делает то, что, по нашему мнению, должен:

@Test
public void shouldTranslateText() {
assertNotNull(translator);
assertEquals("id-DAY is-thAY ork-wAY",
translator.translate("Did this work"));
}

Этот тест не должен быть всесторонним тестом способностей переводчика — должен быть модульный тест, который выполняет каждый угол теста. Этот тест является простым тестом на дым, чтобы быть уверенным, что мы получим услугу, соответствующую нашим ожиданиям.

Тестирование с разными средами исполнения OSGi

Одна из вещей, которая делает Pax Exam настолько мощным, — это то, что он очень гибкий и может быть настроен для тестирования пакетов с использованием практически любых платформ OSGi. Под прикрытием Pax Exam использует Pax Runner для запуска среды выполнения OSGi, поэтому тест на основе Pax Exam может выполняться практически в любой конфигурации, которую может предоставить Pax Runner.

До сих пор мы фокусировали наш тест на запуске в последней версии Equinox. Вот для чего нужна опция equinox (). Но давайте предположим, что мы хотим протестировать с последней версией Felix. Нет проблем, просто измените метод configuration ():

@Configuration
public static Option[] configuration()
{
return options(felix(), profile("spring.dm"), provision(
mavenBundle().groupId("com.habuma.translator").artifactId("interface"),
mavenBundle().groupId("com.habuma.translator").artifactId("pig-latin")
));
}

Тестирование с использованием Equinox или Felix — это хорошо, но может быть полезно протестировать в последних версиях Felix и Equinox:

@Configuration
public static Option[] configuration()
{
return options(equinox(), felix(), profile("spring.dm"), provision(
mavenBundle().groupId("com.habuma.translator").artifactId("interface"),
mavenBundle().groupId("com.habuma.translator").artifactId("pig-latin")
));
}

При таком расположении платформ OSGi три метода тестирования будут выполнены дважды — один раз для Equinox и еще раз для Felix.

Возможно, последняя версия фреймворка (ов) — это не то, что вам нужно для тестирования. Может быть, вы хотите вручную выбрать конкретную версию. Как насчет Equinox 3.4.2 и Knopflerfish 2.3.1?

@Configuration
public static Option[] configuration()
{
return options(equinox().version("3.4.2"), knopflerfish().version("2.3.1"),
profile("spring.dm"), provision(
mavenBundle().groupId("com.habuma.translator").artifactId("interface"),
mavenBundle().groupId("com.habuma.translator").artifactId("pig-latin")
));
}

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

@Configuration
public static Option[] configuration()
{
return options(allFrameworksVersions(), profile("spring.dm"), provision(
mavenBundle().groupId("com.habuma.translator").artifactId("interface"),
mavenBundle().groupId("com.habuma.translator").artifactId("pig-latin")
));
}

Когда я запускал тест на всех версиях всех фреймворков, тесты выполнялись на 29 разновидностях времени выполнения OSGi примерно за четыре с половиной минуты. Может быть, это излишне для ваших нужд, и все, что вам нужно, это знать, что он работает в последней версии всех популярных сред выполнения OSGi. В этом случае allFrameworks () — это путь:

@Configuration
public static Option[] configuration()
{
return options(allFrameworks(), profile("spring.dm"), provision(
mavenBundle().groupId("com.habuma.translator").artifactId("interface"),
mavenBundle().groupId("com.habuma.translator").artifactId("pig-latin")
));
}

Или, может быть, вас не волнует Knopflerfish или Felix … но вам нужно точно знать, что он работает для всех версий Equinox:

@Configuration
public static Option[] configuration()
{
return options(allEquinoxVersions(), profile("spring.dm"), provision(
mavenBundle().groupId("com.habuma.translator").artifactId("interface"),
mavenBundle().groupId("com.habuma.translator").artifactId("pig-latin")
));
}

(Или, если хотите, allFelixVersions () или allKnopflerfishVersions ().)

Как видите, вы можете протестировать свои пакеты практически в любой среде OSGi. Но как насчет обеспечения? Вы должны обеспечить все пакеты от Maven?

Варианты предоставления

Учитывая, что наши пакеты создаются с помощью Maven, предоставление их из репозитория Maven очень удобно. Но это не единственный способ предоставления пакетов для теста. Если вы предпочитаете настраивать пакет из HTTP-URL, то нет проблем:

@Configuration
public static Option[] configuration()
{
return options(equinox(), profile("spring.dm"), provision(
bundle("http://www.habuma.com/osgi/bundles/translator-interface.jar"),
mavenBundle().groupId("com.habuma.translator").artifactId("pig-latin")
));
}

Или, возможно, вы хотите получить пакет из файловой системы:

@Configuration
public static Option[] configuration()
{
return options(equinox(), profile("spring.dm"), provision(
bundle("http://www.habuma.com/osgi/bundles/translator-interface.jar"),
bundle("file:/Users/wallsc/bundles/pig-latin-translator.jar")
));
}

К настоящему времени вы должны иметь высокую оценку того, что экзамен Pax приносит на стол. Используя Pax Exam, вы можете протестировать набор пакетов в рамках OSGi по вашему выбору в относительно жестком тесте JUnit. Мы увидели, как создать тест, который утверждает некоторые базовые ожидания в отношении наших пакетов и действует как потребитель сервиса, чтобы утверждать, что сервис работает так, как нам хотелось бы.

В следующий раз я планирую показать вам, как Spring-DM поддерживает пакетное тестирование. Мы увидим, как Spring-DM поставляется с поддержкой тестирования, которая во многих отношениях напоминает Pax Exam, но в ней есть свой особый Spring Spring. И я обещаю не пропускать 2-3 месяца между следующей записью в блоге.

С http://www.jroller.com/habuma