定制你的Springboot Test

  • 时间:2025-10-27 23:02 作者: 来源: 阅读:5
  • 扫一扫,手机访问
摘要:一般spring 项目中的test 都是unit test, 只要用原生的spring test 的功能就足够了。但是如果想进一步, 做一个e2e flow 那就不是特别方便。 列如我要测试一个k8s的集群, 测试流量切换功能。 一般 create 一个原来的service create 一个在k8s 的service 配置防火墙 创建监控器 进行流量切换

一般spring 项目中的test 都是unit test, 只要用原生的spring test 的功能就足够了。但是如果想进一步, 做一个e2e flow 那就不是特别方便。 列如我要测试一个k8s的集群, 测试流量切换功能。 一般

  1. create 一个原来的service

  2. create 一个在k8s 的service

  3. 配置防火墙

  4. 创建监控器

  5. 进行流量切换。

一般一个流量切换的controller 开发者要进行测试的时候,需要把这5步都做完了。但是对流量切换的开发者前4步对他们来说超级麻烦。所以他们只能做个简单的controller单元测试,然后部署在在产线上,由实施的人去进行集成测试。 这样导致在产线上的人测出问题,再去找流量迁移的开发者去改bug。 来来回回效率超级低。那么就有个想法,如果让前4步自动化的情况下,会怎么样。 所以框架团队提供一个component, 让流量开发者只关注在第5步,前面的4 步自动完成。因此我们采取了如下方案。

Test Lister

TestExecutionListener的定义与作用

@TestExecutionListener是TestContextFramework下的一个类级别注解,与@ContextConfiguration连用。

它的作用是:

  • 在执行测试的声明周期中可以做一些自定义的事情。

  • 并将当前测试方法的TestContext暴露出来。

@ContextConfiguration@TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class}) class CustomTestExecutionListenerTests {    // class body...}

Spring默认提供的TestExecutionListener实现类

翻译自 3.5.3. TestExecutionListener Configuration

  • ServletMockExecutionListener 为mock WebApplicationContext提供Servlet相关的API。

  • DirtiesContextBeforeModesTestExecutionListener 在before模式中,处理@DirtiesContext注解。

  • ApplicationEventTestExecutionListener 用于处理ApplicationEvent

  • DependencyInjectionTestExecutionListener 用于处理依赖注入。

  • DirtiesContextTestExecutionListener 用于处理after模式中的@DirtiesContext注解。

  • **TransactionalTestExecutionListener **用于处理事务注解。

  • SqlScriptsTestExecutionListener 用于处理@Sql注解。

  • EventPublishingTestExecutionListener 向mock出来的ApplicationContext中增加test execution events

自定义TestExecutionListener

在 spring.factories 里定义自己的 org.springframework.test.context.TestExecutionListener。
类的实现为

public class IntegrationTestExecutionListener extends AbstractTestExecutionListener {
 @Override
  public void beforeTestClass(TestContext testContext) throws Exception {
  }
}

AbstractTestExecutionListener 有几个接口,在这里我只需要用到beforeTestClass

自定义TestExecutionListener的实现

定义Annotation

我们想实现这样一个能力,用户在testcase 上个annotation 就可以运行我们的lisener。

@RunWith(SpringRunner.class)
@SpringBootTest
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@EnvPrepare(properties = {"application=ABC", "sourcePool=sourcepoolid",
    "manifest=buildpakcageidr"})

则定义annoation为

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EnvPrepare {
  /*
    Properties in form {@literal key=value} that should be added
   */
  @AliasFor("properties")
  String[] value() default {};

  @AliasFor("value")
  String[] properties() default {};
}

定义相关的Bean

实现一个context bean, 这个context 可以传给后续的用户得到前几步做的信息。

public class IntegrationContext {
  private CloudWorkload workload;

  public IntegrationContext(){
    workload = new CloudWorkload();
  }
  public CloudWorkload getCloudWorkload() {
    return workload;
  }

定义相关的IntegrationContextbean在一个Auto Configuration 里 名字为IntegrationInitializer。

@Configuration
public class IntegrationInitializer {
  @Bean(name="integrationContext")
  public IntegrationContext integrationContext(){
    return new IntegrationContext();
  }
}

在Auto Configuration定义好了之后,通过注册spring.factories 生效。

org.springframework.boot.autoconfigure.EnableAutoConfiguration= com.integration.initializer.IntegrationInitializer

实现接口beforeTestClass

public void beforeTestClass(TestContext testContext) throws Exception {

}

在beforeClass 里主要做如下几步

  1. 取annotation 的值 estContext.getTestClass().getAnnotation(EnvPrepare.class);

  2. 获取相关操作的Bean 以及context ,initBean
    3.. 根据annotation的值进行操作

  3. 把操作结果放入context

  public void beforeTestClass(TestContext testContext) throws Exception {
    EnvPrepare envPrepare = testContext.getTestClass().getAnnotation(EnvPrepare.class);
    if (envPrepare != null) {
      Properties props = resolveParams(envPrepare.properties());
      initBean(testContext);
      IntegrationParams params = new IntegrationParams();
      params.setManifest(String.valueOf(props.get(Constants.MANIFEST)));
      params.setPaasRealm(String.valueOf(props.get(Constants.PAASREALM)));
      params.setPoolId(String.valueOf(props.get(Constants.SOURCE_POOL_ID)));
      params.setAppName(String.valueOf(props.get(Constants.APPLICATION)));
      WorkloadComponent workloadComponent = new WorkloadComponent(altusProvService, mxapiService, cmsService);
      AltusPool workload = workloadComponent.createWorkload(params);
      context.getCloudWorkload().setNameSpace(workloadComponent.getNamespace(workload.getId(), workload.getPaasRealm()));
      context.getCloudWorkload().setApplicationInstance(workloadComponent.getApplicationInstance(workload.getId()));
      context.getCloudWorkload().setAltusPool(workload);
    }
  }


  private void initBean(TestContext testContext) {
    tessClient = (TessService) testContext.getApplicationContext().getBean("getTessService");
    altusProvService = (AltusProvService) testContext.getApplicationContext().getBean("getProvService");
    mxapiService = (MXAPIService) testContext.getApplicationContext().getBean("getCloudAPIService");
    cmsService = (CMSService) testContext.getApplicationContext().getBean("getService");
    context = (IntegrationContext) testContext.getApplicationContext().getBean("integrationContext");
  }

为啥create service 之类的bean 没有定义,例如altusProvService = (AltusProvService) testContext.getApplicationContext().getBean("getProvService"); 是怎么来的。这个是由于在被测试的项目中一般都有了。我们不需要再重复定义。 如果没有,当然要定义。

用法

对于test case 的人来说,他只要关注第5步就可以了。 他的test case 如下

@RunWith(SpringRunner.class)
@SpringBootTest
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@EnvPrepare(properties = {"application=ABC", "sourcePool=sourcepoolid",
    "manifest=buildpakcageidr"})

public class AIMCreationIT {

  private List<String> healthMonitors;

  private String aimName = "cloud-e2e-aim";

  private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss z");

  private static final ObjectMapper MAPPER = new ObjectMapper();

  @Inject
  private IntegrationContext integrationContext;

 public void test2AIMCreate() throws IOException {
 ... 
  }

这样只要简单的配置个@EnvPrepare 就可以专心的写自己的test case了,所有的信息都可以通过IntegrationContext 来获得

  @Inject
  private IntegrationContext integrationContext;

  • 全部评论(0)
手机二维码手机访问领取大礼包
返回顶部