在 JUnit 测试上下文中使用 OSGi 声明性服务

发布于 2024-12-01 03:09:27 字数 2380 浏览 2 评论 0原文

我试图弄清楚如何使用 JUnit 在 OSGi 中实现多包集成测试。

对于集成测试,我的意思是实例化捆绑包的子集以自动验证该子系统中的功能。

我们正在运行 Equinox 并使用 Eclipse 作为工具链。 Eclipse 提供了“Run as 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.functiontest.MyTester 包含要执行的测试方法,遵循典型的 JUnit 实践。

到目前为止,一切都是“按书本”进行的。然而,如果 Junit 运行,它将在访问 FooService 的引用时抛出 NullPointerException。原因是 OSGi 框架与 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服务注入依赖项,否则超时后会失败。

我花了相当长的时间才完全弄清楚这个问题。我希望它能为将来的程序员朋友们省去一些麻烦。

I'm trying to figure out how to implement multi-bundle integration test in OSGi using JUnit.

With integration test, I mean instantiating a subset of the bundles to automatically validate functionality in that subsystem.

We're running Equinox and using Eclipse as toolchain. Eclipse offers the "Run as JUnit Plug-in" option which brings the OSGi framework up and instantiates the configures bundles, so I guess this is the path to follow, but I don't find a way to inject DS references into my tests.
I've seen the use of the ServiceTracker as a programmatic means to access the different service bundles, but that beats the purpose of having DS, isn't it?

I am just getting started with OSGI, so I figure I'm just missing some piece of the puzzle that would let me put my multi-bundle tests together.

Any ideas?

Thanks, Gerard.

* EDIT : SOLUTION *

After looking further into this issue, I finally figured out how to put this mult-bundle integration tests in place using the JUnit plug-in feature:

For the dynamic services injection to work, one must create a service definition file where the injected dependencies must be declared, as it's usually done when working with DS.
This file goes (typically) under the OSGI-INF/ directory. e.g. OSGI-INF/service.xml

service.xml must declare the required dependencies for this test, but does not offer a service of its own:

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>

This will instruct DS to inject the dependency on FooService using the declared onServiceUp method. onServiceDown must be implemented as it's called during the OSGi shutdown phase after the tests are run.

com.test.functionaltest.MyTester contains the test methods to be executed, following the typical JUnit practices.

Up to here, it's all 'by the book'. Yet, if the Junit is run, it will throw NullPointerException when accessing a reference to FooService. The reason for that is that the OSGi framework is in a race condition with the JUnit tests runner context, and usually, the Junit test runner wins that race, executing the tests before the reference to the required service is injected.

To solve this situation, it's required to make the Junit test to wait for the OSGi runtime to do its work. I addressed this issue by using a CountDownLatch, that is initialized to the number of dependent services required in the test.
Then every dependency injection method counts down and when they are all done, the test will start. The code looks like this:

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();
}

Note that the fooService reference needs to be static to allow sharing the service reference between the OSGi and the JUnit execution contexts. The CountDownLatch provides a high-level synchronization mechanism for safe publication of this shared reference.

Then, a dependency check should be added before the test execution:

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

This way the Junit framework waits for the OSGi DS service to inject the dependencies or fails after the timeout.

It took me quite some time to completely figure this one out. I hope it saves some headache to fellow programmers in the future.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

享受孤独 2024-12-08 03:09:27

* 编辑:解决方案*

进一步研究这个问题后,我终于弄清楚如何使用 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.functiontest.MyTester 包含要执行的测试方法,遵循典型的 JUnit 实践。

到目前为止,一切都是“按书本”进行的。然而,如果 Junit 运行,它将在访问 FooService 的引用时抛出 NullPointerException。原因是 OSGi 框架与 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服务注入依赖项,否则超时后会失败。

我花了相当长的时间才完全弄清楚这个问题。我希望它能为将来的程序员朋友们省去一些麻烦。

* EDIT : SOLUTION *

After looking further into this issue, I finally figured out how to put this mult-bundle integration tests in place using the JUnit plug-in feature:

For the dynamic services injection to work, one must create a service definition file where the injected dependencies must be declared, as it's usually done when working with DS.
This file goes (typically) under the OSGI-INF/ directory. e.g. OSGI-INF/service.xml

service.xml must declare the required dependencies for this test, but does not offer a service of its own:

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>

This will instruct DS to inject the dependency on FooService using the declared onServiceUp method. onServiceDown must be implemented as it's called during the OSGi shutdown phase after the tests are run.

com.test.functionaltest.MyTester contains the test methods to be executed, following the typical JUnit practices.

Up to here, it's all 'by the book'. Yet, if the Junit is run, it will throw NullPointerException when accessing a reference to FooService. The reason for that is that the OSGi framework is in a race condition with the JUnit tests runner context, and usually, the Junit test runner wins that race, executing the tests before the reference to the required service is injected.

To solve this situation, it's required to make the Junit test to wait for the OSGi runtime to do its work. I addressed this issue by using a CountDownLatch, that is initialized to the number of dependent services required in the test.
Then every dependency injection method counts down and when they are all done, the test will start. The code looks like this:

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();
}

Note that the fooService reference needs to be static to allow sharing the service reference between the OSGi and the JUnit execution contexts. The CountDownLatch provides a high-level synchronization mechanism for safe publication of this shared reference.

Then, a dependency check should be added before the test execution:

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

This way the Junit framework waits for the OSGi DS service to inject the dependencies or fails after the timeout.

It took me quite some time to completely figure this one out. I hope it saves some headache to fellow programmers in the future.

玩心态 2024-12-08 03:09:27

我不熟悉您提到的 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 框架,如果您的项目生成可用于测试的可运行 jar,这将非常有用。

I'm not familiar with the Eclipse tools that you mention, but we've been successfully using Pax Exam for integration testing in Apache Sling. If you are familiar with Maven, the POM at https://svn.apache.org/repos/asf/sling/trunk/installer/it/pom.xml might help you in getting started, and https://github.com/tonit/Learn-PaxExam looks like a good starting point as well.

The Sling testing tools can also help in this context by allowing bundles to contribute JUnit tests to an OSGi framework at runtime, which is useful if your project generates a runnable jar that can be used for testing.

痴情 2024-12-08 03:09:27

您可以使用运行配置上的选项卡进行设置。

因此,右键单击,选择“运行方式”,选择“运行配置...”,双击“JUnit 插件测试”,然后在插件选项卡上添加依赖项 - 与普通启动器几乎相同

一些链接:
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

You set it up using the tabs on the run configuration.

So right click, select "run as", select "run configurations...", double click "JUnit Plug-in Test", then add your dependencies on the plugins tab - pretty much the same as the normal launcher

Some links:
http://publib.boulder.ibm.com/infocenter/ratdevz/v8r0/index.jsp?topic=/org.eclipse.pde.doc.user/guide/tools/launchers/junit_launcher.htm and http://publib.boulder.ibm.com/infocenter/ratdevz/v8r0/index.jsp?topic=/org.eclipse.pde.doc.user/guide/tools/launchers/junit_main.htm

满栀 2024-12-08 03:09:27

我想掌握 org.apache.felix.scr.ScrService 并积极等待组件变为活动状态会更干净一些。该接口由Equinox 和felix 均实现。

Java 文档API 使用

I guess it would be a bit cleaner to get hold of the org.apache.felix.scr.ScrService and actively wait for the component to become ACTIVE. This interface is implemented by both equinox and felix.

Java doc and API Usage.

ま昔日黯然 2024-12-08 03:09:27

我认为在上述解决方案中,CountDownLatch 是不必要的。

问题在于 DS Context 中的 JUnit 为自己实例化了一个 JUnitTest 类。首先 DS Context 实例化你的 JUnitTest 类并为 FooService 调用绑定 onFooServiceUp ,但是此后 JUnit 实例化他自己的 JUnitTest 类而不调用绑定方法 onFooServiceUp 。在这种情况下,FooService 在 JUnitTest 中不可用。

如果您将 FooService 声明为静态(正如您所做的那样)并在 onFooServiceUp 方法中进行分配,则不需要使用 CountDownLatch 进行构造。

I think that in the the above solution the CountDownLatch is not necessary.

The problem is that JUnit in DS Context instantiates a JUnitTest class each for his own. First DS Context instantiates your JUnitTest class and call the binding onFooServiceUp for FooService, but after this JUnit instantiates his own JUnitTest class without calling the binding method onFooServiceUp. In this case the FooService is in the JUnitTest not available.

If you declare the FooService as static (as you have done) and assign in the method onFooServiceUp you don't need the construction with the CountDownLatch.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文