1

我有一个 Web 应用程序,它分为两部分(在不同的 jvm 中运行):

  • @RestController 层;
  • @Service 层(业务和数据访问逻辑)。

它们通过 Spring Remoting 相互通信:

(org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean 

在@RestController 层和

org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter 

在@Service 层上)。

这两部分部署在不同的应用服务器上。大多数情况下,它们是通过 Spring RestTemplate 进行测试的(@Service 部分必须手动部署和启动,然后运行集成测试)。

但是当我过去使用 Spring Test 和 MockMvc 并发现它是一个很棒的工具时,我想一次又一次地使用它。不幸的是,我不明白如何将@Service 层上下文添加到测试上下文配置中,这样它就可以从测试中访问(它拥有@RestController 上下文,并增加了一些模拟)。

如果我使用@Service 层工件(在本地主机上)手动启动应用程序服务器并运行我的 MockMvc 驱动的测试,我可以看到来自 MockMvc 的远程请求到达它们的目的地 - @Service 层(当然是通过 httpInvoker)。

而且我想找到在测试上下文中启动@Service 层上下文的可能性(使用所有需要的 HttpInvokerServiceExporters)。并强制 httpInvoker 将其请求发送到这个“伪”远程服务(实际上是本地的)。

现在我正在考虑使用嵌入式码头来部署@Service 层并针对这个实例运行 MockMvc 测试。 SpringHttpRemoting 与 EmbeddedJettyServer.wiki

我在微服务架构方面的经验非常少,但我的情况似乎很常见。那么也许有一些更自然的(尽管特别是 Spring Test 和 MockMvc)方法来进行这样的测试?

提前致谢。

安德烈。

4

1 回答 1

1

好吧,让我谈谈我对此事的看法。这是域和api:

public class Contact implements Serializable {
    private String firstName;
    private String lastName;
    private DateTime birthDate;
}

public interface ContactService {
    List<String> getContacts();
}

@Service
public class ContactServiceImpl implements ContactService{
    public List<String> getContacts() {
        return new ArrayList<String>(asList("karl marx", " fridrih engels", " !!!"));
    }
}

@RestController
public class ContactController {
    public static final String QUALIFIER = "contactController";
    public static final String MAPPING = "/contact";
    @Autowired
    private ContactService serviceInvoker;
    @RequestMapping(method = RequestMethod.GET)
    public ResponseEntity<String> findAll() {
        List<String> strings = serviceInvoker.getContacts();
        return new ResponseEntity<String>(Arrays.toString(strings.toArray()), HttpStatus.OK);
    }
}
  1. 通过提供带有“调用端”上下文(在我的例子中为restcontroller)和“调用端”上下文(包含真实服务,而不是远程代理)的测试来测试这种配置是相当简单的。就像单体应用程序上下文一样。快速简便的方法。但在某些情况下还不够(例如,出于某些目的,您一方面自定义了 HttpInvokerProxyFactoryBean,另一方面自定义了 HttpInvokerServiceExporter)。

  2. 您可以覆盖 HttpInvokerProxyFactoryBean 类,使其成为 NonRemote。

首先,通过重写一些方法来修改 HttpInvokerServiceExporter;只需要将连接到 RemoteInvocation 和 RemoteInvocationResult 的方法公开。

public class OpenedHttpServiceExporter extends HttpInvokerServiceExporter {

        @Override
        public RemoteInvocation readRemoteInvocation(HttpServletRequest request) throws IOException, ClassNotFoundException {
            return super.readRemoteInvocation(request);
        }
        .
        .
        .
        etc...
    }

让它成为 OpenedHttpServiceExporter。在测试/资源中创建 bean 描述符,将您在测试中需要的生产 bean 定义导入其中,并添加与原始 HttpInvokerServiceExporter 具有相同名称的 OpenedHttpServiceExporter bean - 它需要用于覆盖另一个。

测试上下文描述符opensServiceExporter.xml(没有beans元素):

<import resource="classpath:spring/serviceExporter.xml"/>
<bean id="contactExporter" class="pmp.testingremoting.service.OpenedHttpServiceExporter">
    <property name="service" ref="contactServiceImpl"/>
    <property name="serviceInterface" value="pmp.testingremoting.service.ContactService"/>
</bean>

并导入描述符:

<context:annotation-config/>
<context:component-scan base-package="pmp.testingremoting.service">
    <context:exclude-filter type="annotation" expression="org.springframework.context.annotation.Configuration"/>
</context:component-scan>
<bean name="contactExporter" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
    <property name="service" ref="contactServiceImpl"/>
    <property name="serviceInterface" value="pmp.testingremoting.service.ContactService"/>
</bean>

扩展 HttpInvokerProxyFactoryBean,制作这个子类的 bean,自动将 HttpInvokerServiceExporter 字段插入其中。

覆盖公共对象调用(方法调用方法调用)

通过调用OpenedHttpServiceExporter.invoke(createRemoteInvocation(methodInvocation), exporter.getService()); 它。

public class NonRemoteInvoker extends HttpInvokerProxyFactoryBean {

    @Autowired
    private OpenedHttpServiceExporter exporter;

    public void setExporter(OpenedHttpServiceExporter exporter) {
        this.exporter = exporter;
    }

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        return exporter.invoke(createRemoteInvocation(methodInvocation), exporter.getService());
    }
}

我们称这个新类为 NonRemoteInvoker。它必须只覆盖超类的一种方法,并将充当从“调用方”上下文到“被调用方”上下文的桥梁。

使用 NonRemoteInvoker 的实例创建“调用端”测试上下文描述符 (nonRemoteInvokerContext.xml)(同样,与原始 HttpInvokerProxyFactoryBean 具有相同的名称;也用于覆盖)。

<import resource="classpath:spring/webContext.xml"/>

<bean id="serviceInvoker" class="pmp.testingremoting.controller.NonRemoteInvoker">
    <property name="serviceUrl" value="http://localhost:8080/remote/ContactService" />
    <property name="serviceInterface" value="pmp.testingremoting.service.ContactService" />
</bean>

和 webContext.xml 是

<mvc:annotation-driven/>
<context:annotation-config/>
<context:component-scan base-package="pmp.testingremoting.controller">
    <context:exclude-filter type="annotation" expression="org.springframework.context.annotation.Configuration"/>
</context:component-scan>

为“被调用方”创建测试配置。我使用了静态 @Configuration 类,只是将测试上下文描述符导入其中。

@Configuration
@ImportResource(locations = {
        "classpath:openedServiceExporter.xml"
})
static class TunedBusinessConfig {

}

为“调用方”创建测试配置。我也是这样做的。

@Configuration
@ImportResource(locations = {
        "classpath:nonRemoteInvokerContext.xml",
})
static class TunedRemoteInvokerConfig {

}

现在是测试班。它将通过@WebAppConfiguration 标记并具有这样的@ContextHierarchy,这将允许“调用方”上下文使用“调用方”上下文(调用-父,调用-子)。将 OpenedHttpServiceExporter 注入 NonRemoteInvoker 需要它。

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextHierarchy({
        @ContextConfiguration(classes = {
                ContactControllerIntegrationTest.TunedBusinessConfig.class
        }),
        @ContextConfiguration(classes = {
                ContactControllerIntegrationTest.TunedRemoteInvokerConfig.class
        })
})
public class ContactControllerIntegrationTest {
.
.
.
}

这种方法使我不仅可以在测试中涵盖休息和服务层逻辑,还可以涵盖自定义 RemoteInvoker 的逻辑(我们称之为传输逻辑)。

这里有更多细节: https ://github.com/PmPozitron/TestingRemoting/tree/lightVersion

我不确定这种方法是否正确,因此在适当的时候不会将答案标记为已接受。

于 2017-11-12T23:23:28.853 回答