定风波

莫听穿林打叶声,何妨吟啸且徐行。竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生。

java 常用集成测试代码总结

Junit 的基本用法

  1. @Test: 测试方法 测试方法必须使用public void 进行修饰,不能带任何的参数
    • (expected=XXException.class)如果程序的异常和XXException.class一样,则测试通过    

    • (timeout=100)如果程序的执行能在100毫秒之内完成,则测试通过

  2. @Ignore: 被忽略的测试方法:加上之后,暂时不运行此段代码
  3. @Before: 每一个测试方法之前运行, 一般用来初始化当前测试所需要的数据,bean 等
  4. @After: 每一个测试方法之后运行,用来销毁测试产生的数据,bean 等
  5. @BeforeClass: 方法必须必须要是静态方法(static 声明),所有测试开始之前运行,注意区分before,是所有测试方法
  6. @AfterClass: 方法必须要是静态方法(static 声明),所有测试结束之后运行,注意区分 @After
  7. @ClassRule
  8. @Rule

执行顺序 @ClassRule –> @BeforeClass –> @Rule –> @Before –>@After –>@AfterClass –>@ClassRule

参考文档:https://www.cnblogs.com/caoyuanzhanlang/p/3534846.html

SpringMvc 测试

springmvc 的测试,主要就是验证结果,状态码,头信息等,spring 提供了一些很方便的工具。可以全文验证也可以进行局部验证(使用jsonpath 进行验证)

// 获取上下文中的MockMvc的bean 
@Autowired
private MockMvc            mvc;

//模拟发送请求,对请求状态,头部信息,以及请求结果等进行验证
 @Test
 public void mysqlWithError() throws Exception {
        this.mvc.perform(get("/v1.0/demo/mysql?key=1")).andDo(print())
            .andExpect(status().is5xxServerError())
            //.andExpect(content().string("Hello World"));
            .andExpect(jsonPath("$.code").value("199999"));
  }

关于数据库层的测试

在测试环境时,我们采用H2 来做数据库,DBUnit 做数据库测试工具,DBUnit 支持测试前初始化数据,测试完成后进行数据比对。

H2

h2 是一种支持内存的数据,只需要一个jar 包即可,很轻量级的。由于mysql 和h2 的数据库创建语句存在差异,所以提供了一个工具,在启动测试前先进行了一次转换。

dbUnit

dbunit 可以方便的进行数据初始化,数据比对以及数据清理

DatabaseAssertionMode.NON_STRICT 忽略那些没有出现在数据集文件中的列和表
DatabaseAssertionMode.NON_STRICT_UNORDERED 忽略那些没有出现在数据集文件中的列和表,并且不关注顺序

注意点:

  1. 对于excel 文件,由于是在ide 外部打开后进行修改的,所以修改完后跑测试时会发现数据没有修改,原因在于idea 执行的是target 目录下的代码,所以要重新编译一次后在执行
  2. 使用@MockBean 的时候,会替换原来的bean ,也就是说被mock 的bean 的任何方法都已经不在是预期的返回,如果不想被替换,可以使用@SpyBean 这个是原来的bean 的一个代理,只会替换指定的方法的返回值,如果没有被模拟,那么会调用原来的方法。
  3. excel 中的tab 项为表名称,需要存在列名称
  4. 使用原生开源的nosql-unit 时存在一个问题,当同一个测试类中使用了两组nosql-unit 提供的@rule 时,即使指定了identifier 也会存在调用关系出错的情况,比如同时存在redisRule 和mongodbRule ,redis 的比对数据可能会使用mongo链接去获取数据。(我已经修复了这个问题,有需要可以联系qq: 153933313)

nosql-unit

nosql-unit 我们用来进行redis 和mongodb 的测试,nosql-unit 同样支持自动数据初始化以及测试完成后的数据比对工作

@UsingDataSet(locations="my_data_set.json", loadStrategy=LoadStrategyEnum.INSERT)

locations 会从当前测试类所在目录进行查找资源,并且提供了默认值 ,默认值格式为:

​ [test class name]#[test method name].[format] 如 DemoService#demo.json

loadStrategy 有三种 INSERT 只新增数据 ,CLEAN_INSERT 先删除在新增 DELETE_ALL 删除所有数据

@ShouldMatchDataSet(location="my_expected_data_set.json")   

location 默认值 [test class name]#[test method name]-expected.[format]

另外,当同一个类中有多个不同的Rule 时,用法稍有不同,参考Redis 的列子

自定义插入策略和比较策略

// 在类声明的地方加入以下注解,并指定策略类
@CustomInsertionStrategy()
@CustomComparisonStrategy()

关于Redis 的测试

pom 中加入一个嵌入式的redis 服务器依赖,以及nosqlutil-redis

<dependency>
    <groupId>it.ozimov</groupId>
    <artifactId>embedded-redis</artifactId>
    <version>0.7.2</version>
</dependency>

<dependency>
    <groupId>com.lordofthejars</groupId>
    <artifactId>nosqlunit-redis</artifactId>
    <version>1.0.0-rc.5</version>
</dependency>

redis中UsingDataSet 属性文件的格式:

{
"data":[
            {"simple": [
                {
                    "key":"key1", 
                    "value":"value1"
                }
                ]
            },
            {"list": [{
                        "key":"key3",
                        "values":[
                            {"value":"value3"},
                            {"value":"value4"}
                        ]
                      }]
            },

            {"sortset": [{
                     "key":"key4",
                     "values":[
                           {"score":2, "value":"value5" },{"score":3, "value":1 }, {"score":1, "value":"value6" }]
                 }]
            }, 
            {"hash": [
                        {
                            "key":"user",
                            "values":[
                                {"field":"name", "value":"alex"},
                                {"field":"password", "value":"alex"}
                            ]
                        }
                    ]
            },
            {"set":[{
                        "key":"key3",
                        "values":[
                            {"value":"value3"},
                            {"value":"value4"}
                        ]
                      }]
            }
]
}

redis 测试的demo

  @Rule
  public RedisRule           redisRule = RedisRule.RedisRuleBuilder.defaultSpringRedis("jedisWritePool", "redis");

 @Test
 @UsingDataSet(
         withSelectiveLocations = { @Selective(identifier = "redis", locations = "mockredis.json") },
            loadStrategy = LoadStrategyEnum.CLEAN_INSERT
  )
  @ShouldMatchDataSet(withSelectiveMatcher = { @SelectiveMatcher(identifier = "redis", location = "aftermockredis.json") })
public void redisTest() throws Exception {
        this.mvc.perform(get("/v1.0/demo/redis?key=zrc")).andDo(print())
            .andExpect(content().string("ismock"));
    }

RedisRule.RedisRuleBuilder.defaultSpringRedis(“jedisWritePool”, “redis”); 声明使用的Redis 管理器,指定spring bean 的name 为“jedisWritePool” 以及 分组标识为 redis

在测试方法上,通过@selectiv 注解来声明我们所使用的标识为redis

这种用法只在同一个测试类中存在多个@Rule是使用,只有一个@Rule 时,可以直接指定location 即可。

这里nosql-unit会从spring 的上下文中找到对应的redis 数据库链接,并使用这个链接进行数据初始化以及数据比对,所以在测试类中必须注入一个spring 的ApplicationContext

注意:

​ nosql-unit 提供了@classRule 来初始化一个嵌入的redis ,但是由于在使用过程中,这个嵌入的redis 无法被spring 管理生命周期。这里提供两种方式。

  1. 使用外部的嵌入式redis ,(可以随机一个端口,也可以固定端口),并在spring 的配置文件中进行redis 的配置时采用localhost 以及这个端口(可以通过环境变量设置端口,spring 的redis 的端口读取环境变量即可),这就需要自定一个@ClassRule 来初始化这个嵌入式的redis
  2. 修改nosql-unit ,使用一个外部的独立redis ,并且使用spring对redis 进行管理。修改@rule 的执行方法,在执行数据初始化以及比对时所使用jedis 从spring 的连接池中获取(修改一处地方即可,可以参考SpringMongoDb )

关于MongoDb 的测试

mongodb 的测试也采用了nosql-unit 进行,与redis 的测试区别很小,我们只需要指定MongoRule 即可

    @Rule
public MongoDbRule         spring    = newMongoDbRule().defaultSpringMongoDb("test_demo", "mongo");

注意: nosql-unit 提供了一个嵌入式的mongodb ,可以不链接外部进行测试。

第一个参数为需要测试的数据库的名称,第二个参数为分组标识,指定分组标识后需要在UsingDataSet 和ShouldMatchDataSet 中指定分组标识

与redis test 相同,mongodb 的test 也是使用spring 上下文中的mongo 链接进行数据初始化以及数据比对,同样,这里必须注入Spring的ApplicationText

mongo的文本格式为

{
    "name_collection1": [
    {
        "attribute_1":"value1",
        "attribute_2":"value2"
    },
    {
        "attribute_3":2,
        "attribute_4":"value4"
    }
    ],
    "name_collection2": [
        ...
    ],
    ....
}

关于使用远程调用的测试

远程调用时需要对调用的接口采用mock 进行测试。

被@mockbean 标注的bean 会在整个spring 上下文中替换原生的bean (一个全新的bean ),需要注意的是,如果这个bean 在测试上下文中没有被指定mock 的方法与返回值,但是又在被测试的代码中进行了调用,那么这个方法会返回null ,这在使用idea 进行测试时会影响测试结果(idea 所有测试使用同一套spring 上下文),但是在使用mvn test 时不存在(每个测试代码都时一套新的测试环境)

被@SpyBean 标注的bean ,相当于原来bean 的一个代理,被mock 的方法返回mock 的值,没有被mock 的方法则调用原来bean 的返回值。

    @MockBean
    private FeignService feignService;

    @Test
    public void feign() throws Exception {
        given(feignService.recommendArts("112")).willReturn("Hello World");
        this.mvc.perform(get("/v1.0/demo/feign?key=112")).andExpect(status().is(302))
                .andExpect(header().string("isSinger","123"))
            .andExpect(content().string("Hello World"));
    }

注意点

这些测试(被@Test 标注的方法)在使用idea 运行时是共享一个spring 容器,所有的@Test 方法被运行在同一个环境中。

但是当使用mvn test 时,会每个方法都生成一个新的容器。所以,使用的外部中间件,数据库等在方法运行完成后必须要进行清理工作,sql 脚本在创建表时要添加 drop table 语句

点赞
  1. 匿名说道:

    谢谢

发表评论

电子邮件地址不会被公开。