美文网首页
定制你的Springboot Test

定制你的Springboot Test

作者: 程序员札记 | 来源:发表于2022-05-09 09:23 被阅读0次

通常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;

相关文章

网友评论

      本文标题:定制你的Springboot Test

      本文链接:https://www.haomeiwen.com/subject/eoacurtx.html