Использование декларативных сервисов OSGi в контексте теста JUnit

Я пытаюсь выяснить, как реализовать тест интеграции нескольких пакетов в OSGi с помощью JUnit.

С тестом интеграции я подразумеваю создание экземпляра набора для автоматической проверки функциональности в этой подсистеме.

Мы запускаем Equinox и используем Eclipse в качестве инструментальной цепочки. Eclipse предлагает вариант "Запускать как JUnit Plug-in", который поддерживает платформу OSGi и создает пакеты конфигурации, поэтому я предполагаю, что это путь к следующему примеру, но я не нашел способ внедрить ссылки DS в мои тесты. Я видел использование ServiceTracker в качестве программного средства для доступа к различным пакетам услуг, но это бьет цель иметь DS, не так ли?

Я только начинаю работать с OSGI, поэтому я полагаю, что я просто пропустил часть головоломки, которая позволила бы мне объединить мои тесты с несколькими пучками.

Любые идеи?

Спасибо, Джерард.

* РЕДАКТИРОВАТЬ: РЕШЕНИЕ *

После дальнейшего изучения этой проблемы я, наконец, понял, как установить эти тесты интеграции с несколькими путями с помощью функции плагина JUnit:

Для внедрения динамических служб необходимо создать файл определения службы, в котором должны быть объявлены вложенные зависимости, как это обычно делается при работе с DS. Этот файл идет (обычно) в каталоге OSGI-INF/. например OSGI-INF/service.xml

service.xml должен объявить необходимые зависимости для этого теста, но не предлагает собственную услугу:

service.xml
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="MyTest" activate="startup" deactivate="shutdown">

   <implementation class="com.test.functionaltest.MyTester"/>
   <reference name="OtherService" interface="com.product.service.FooService" policy="static" cardinality="1..1" bind="onServiceUp" unbind="onServiceDown"/>

</scr:component>

Это даст указание DS ввести зависимость от FooService с помощью объявленного метода onServiceUp. onServiceDown должен быть реализован так, как он вызывается во время фазы останова OSGi после запуска тестов.

com.test.functionaltest.MyTester содержит методы тестирования, которые должны выполняться, следуя типичным практикам JUnit.

До сих пор все это "по книге". Тем не менее, если Junit запущен, он будет генерировать исключение NullPointerException при доступе к ссылке на FooService. Причина этого заключается в том, что инфраструктура OSGi находится в состоянии гонки с использованием контекста Running JUnit, и обычно победитель теста Junit выигрывает эту гонку, выполняя тесты до того, как вводится ссылка на требуемую услугу.

Чтобы решить эту проблему, потребовалось, чтобы тест Junit дождался, когда OSGi выполнит свою работу. Я обратился к этой проблеме с помощью CountDownLatch, который инициализируется количеством зависимых сервисов, необходимых в тесте. Затем каждый метод инъекции зависимости рассчитывается, и когда все будет сделано, начнется тест. Код выглядит следующим образом:

private static CountDownLatch dependencyLatch = new CountDownLatch(1);// 1 = number of dependencies required    
static FooService  fooService = null;   
public void onFooServiceUp(FooService service) {
  fooService = service;
  dependencyLatch.countDown();
}

Обратите внимание, что ссылка fooService должна быть статической, чтобы разрешить совместное использование ссылки на службу между OSGi и контекстами выполнения JUnit. CountDownLatch обеспечивает механизм синхронизации на высоком уровне для безопасной публикации этой общей ссылки.

Затем перед выполнением теста следует добавить проверку зависимостей:

@Before
public void dependencyCheck() {
  // Wait for OSGi dependencies
    try {
      dependencyLatch.await(10, TimeUnit.SECONDS); 
      // Dependencies fulfilled
    } catch (InterruptedException ex)  {
      fail("OSGi dependencies unfulfilled");
    }
}

Таким образом, инфраструктура Junit ожидает, что служба OSGi DS будет вводить зависимости или терпеть неудачу после таймаута.

Мне потребовалось некоторое время, чтобы полностью понять это. Надеюсь, это сэкономит головную боль другим программистам в будущем.

Ответ 1

* РЕДАКТИРОВАТЬ: РЕШЕНИЕ *

После дальнейшего изучения этой проблемы я, наконец, понял, как установить эти тесты интеграции с несколькими путями с помощью функции плагина JUnit:

Для внедрения динамических служб необходимо создать файл определения службы, в котором должны быть объявлены вложенные зависимости, как это обычно делается при работе с DS. Этот файл идет (обычно) в каталоге OSGI-INF/. например OSGI-INF/service.xml

service.xml должен объявить необходимые зависимости для этого теста, но не предлагает собственную услугу:

service.xml
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="MyTest" activate="startup" deactivate="shutdown">

   <implementation class="com.test.functionaltest.MyTester"/>
   <reference name="OtherService" interface="com.product.service.FooService" policy="static" cardinality="1..1" bind="onServiceUp" unbind="onServiceDown"/>

</scr:component>

Это даст указание DS ввести зависимость от FooService с помощью объявленного метода onServiceUp. onServiceDown должен быть реализован так, как он вызывается во время фазы останова OSGi после запуска тестов.

com.test.functionaltest.MyTester содержит методы тестирования, которые должны выполняться, следуя типичным практикам JUnit.

До сих пор все это "по книге". Тем не менее, если Junit запущен, он будет генерировать исключение NullPointerException при доступе к ссылке на FooService. Причина этого заключается в том, что инфраструктура OSGi находится в состоянии гонки с использованием контекста Running JUnit, и обычно победитель теста Junit выигрывает эту гонку, выполняя тесты до того, как вводится ссылка на требуемую услугу.

Чтобы решить эту проблему, потребовалось, чтобы тест Junit дождался, когда OSGi выполнит свою работу. Я обратился к этой проблеме с помощью CountDownLatch, который инициализируется количеством зависимых сервисов, необходимых в тесте. Затем каждый метод инъекции зависимости рассчитывается, и когда все будет сделано, начнется тест. Код выглядит следующим образом:

private static CountDownLatch dependencyLatch = new CountDownLatch(1);// 1 = number of dependencies required    
static FooService  fooService = null;   
public void onFooServiceUp(FooService service) {
  fooService = service;
  dependencyLatch.countDown();
}

Обратите внимание, что ссылка fooService должна быть статической, чтобы разрешить совместное использование ссылки на службу между OSGi и контекстами выполнения JUnit. CountDownLatch обеспечивает механизм синхронизации на высоком уровне для безопасной публикации этой общей ссылки.

Затем перед выполнением теста следует добавить проверку зависимостей:

@Before
public void dependencyCheck() {
  // Wait for OSGi dependencies
    try {
      dependencyLatch.await(10, TimeUnit.SECONDS); 
      // Dependencies fulfilled
    } catch (InterruptedException ex)  {
      fail("OSGi dependencies unfulfilled");
    }
}

Таким образом, инфраструктура Junit ожидает, что служба OSGi DS будет вводить зависимости или терпеть неудачу после таймаута.

Мне потребовалось некоторое время, чтобы полностью понять это. Надеюсь, это сэкономит головную боль другим программистам в будущем.

Ответ 2

Я не знаком с инструментами Eclipse, которые вы упомянули, но мы успешно использовали Pax Exam для тестирования интеграции в Apache Sling. Если вы знакомы с Maven, POM в https://svn.apache.org/repos/asf/sling/trunk/installer/it/pom.xml может помочь вам при запуске и https://github.com/tonit/Learn-PaxExam также выглядит хорошей отправной точкой.

инструменты тестирования Sling также могут помочь в этом контексте, позволяя пакетам вносить вклад в JUnit-тесты в среду OSGi во время выполнения, что полезно, если ваш проект генерирует исполняемую банку, которая может быть использована для тестирования.

Ответ 3

Вы настроили его с помощью вкладок в конфигурации запуска.

Итак, щелкните правой кнопкой мыши, выберите "запуститься как", выберите "выполнить конфигурации...", дважды щелкните "JUnit Plug-in Test", затем добавьте свои зависимости на вкладке плагинов - почти так же, как и обычная программа запуска

Некоторые ссылки: http://publib.boulder.ibm.com/infocenter/ratdevz/v8r0/index.jsp?topic=/org.eclipse.pde.doc.user/guide/tools/launchers/junit_launcher.htm и http://publib.boulder.ibm.com/infocenter/ratdevz/v8r0/index.jsp?topic=/org.eclipse.pde.doc.user/guide/tools/launchers/junit_main.htm

Ответ 4

Я думаю, было бы немного чище завладеть org.apache.felix.scr.ScrService и активно ждать, пока компонент станет ACTIVE. Этот интерфейс реализуется как равноденствием, так и felix.

Java doc и Использование API.

Ответ 5

Я думаю, что в вышеупомянутом решении CountDownLatch не требуется.

Проблема заключается в том, что JUnit в DS Context создает экземпляр класса JUnitTest для себя. Первый DS Context создает экземпляр класса JUnitTest и вызывает привязку onFooServiceUp для FooService, но после этого JUnit создает экземпляр собственного класса JUnitTest, не вызывая метод привязки onFooServiceUp. В этом случае FooService недоступен в JUnitTest.

Если вы объявляете FooService статичным (как вы это сделали) и назначаете в методе onFooServiceUp, вам не нужна конструкция с CountDownLatch.