[译]Spring Boot 1.4测试的改进

原文:Testing improvements in Spring Boot 1.4

作者:PHIL WEBB    译者:杰微刊兼职翻译张迪  

对Pivotal团队来说,工作上的好事情是他们拥有一个被叫做Pivotal Labs的灵活发展部门,拥有Labs团队的Lean 和 XP程序设计方法学的强大支持,例如结对编程和测试驱动开发。他们对于测试的酷爱已经对Spring Boot 1.4产生独特的影响,正如我们已经开始通过获取重大的反馈来对事物本身进行改善。这篇文章重点介绍了一些新的测试功能,这些功能刚刚使用在M2最新的版本中。

一、没有Spring的实验测试

单元测试任何Spring @Component的最简单的方式就是不要涉及到Spring的任何方面。

这个方法同时也是尝试和建构代码的最好方式,所以classes可以被实例化和直接测试。通常,归结为几方面:

1、用干净的注点分离组织你的代码,以便于每个部分都可以被单元测试。TDD是一种不错的实现方式。2、使用constructor injection(构造子注入)确保目标对象可以直接被实例化的。不要使用field injection,因为它只会让你的测试更难写。使用Spring框架4.3会让编写组件变得很容易,就好像你不再需要使用@Autowired就可以使用constructor injections了。只要你有一个单独的构造函数,Spring会暗中将它当做一个自动配置的目标:

@Componentpublic class MyComponent {     

   private final SomeService service; 

   public MyComponent(SomeService service) {       

 this.service = service;    

}

现在,测试mycomponent,就像直接创建它一样简单,调用一些方法:

@Testpublic void testSomeMethod() {    

SomeService service = mock(SomeService.class);   

 MyComponent component = new MyComponent(service);  

  // setup mock and class component methods

}

二、Spring Boot 1.3概括

当然,你经常需要将堆栈稍微提升,开始编写包含Spring的综合测试。幸运的是,Spring框架提供spring测试模块的帮助,不幸的是,通过Spring Boot 1.3,大家有很多不同的方式来使用它。你可能会使用@ContextConfiguration注释和SpringApplicationContextLoader的组合:

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(classes=MyApp.class, loader=SpringApplicationContextLoader.class)public class MyTest { 

   // ...

}

你可能已经选择了

@SpringApplicationConfiguration:

@RunWith(SpringJUnit4ClassRunner.class)

@SpringApplicationConfiguration(MyApp.class)

public class MyTest {  

  // ...

}

你可能会通过@IntegrationTest结合其中任何一个:@RunWith(SpringJUnit4ClassRunner.class)

@SpringApplicationConfiguration(MyApp.class)

@IntegrationTestpublic class MyTest { 

  // ...

}或者是利用

@WebIntegrationTest(或者可能是@IntegrationTest + @WebAppConfiguration):

@RunWith(SpringJUnit4ClassRunner.class)

@SpringApplicationConfiguration(MyApp.class)

@WebIntegrationTestpublic class MyTest {  

  // ...

}

你也可以到混合随机端口上运行的服务器中(@WebIntegrationTest(randomPort=true))添加属性(使用@IntegrationTest("myprop=myvalue")或者@TestPropertySource(properties="myprop=myvalue"))。

你有很多选择可以选。

三、Spring Boot 1.4简单化使用

Spring Boot 1.4之后,事情就会变得简单多了。这里是一个用于常规测试的@SpringBootTest注释,与你应用程序中测试插件的一些特定变体一样(稍后将详细介绍)。下面展示的是一个Spring Boot 1.4综合测试的典型案例:@RunWith(SpringRunner.class)

@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)

public class MyTest {  

  // ...   

 }

以下是发生了的一个故障:

1、@RunWith(SpringRunner.class) 告诉JUnit运行使用Spring的测试支持。SpringRunner是SpringJUnit4ClassRunner的新名字,这个名字只是让名字看起来简单些。

2、@SpringBootTest的意思是“带有Spring Boot支持的引导程序”(例如,加载应用程序、属性,为我们提供Spring Boot的所有精华部分)。

3、webEnvironment属性允许为测试配置特定的“网络环境”。你可以利用一个MOCK小服务程序环境开始你的测试,或者使用一个运行在RANDOM_PORT 或者 DEFINED_PORT上的真正的HTTP服务器。

4、如果我们想要加载一个特定的配置,我们可以用@SpringBootTest class属性。在这个实例中,我们省略classes就意味着测试要首次尝试从任意一个inner-classes中加载@ configuration,如果这个尝试失败了,它会在你主要的@SpringBootApplicationclass中进行搜索。

特性现在都以相同的方式加载,如同Spring的常规@TestPropertySource 注释。

@SpringBootTest注释通常含有特性属性,这个属性可以被用来指定任何应该在环境中定义的额外特性。这是一个更具体的例子,真正REST末端实际上的采样数:@RunWith(SpringRunner.class)

@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)

public class MyTest {

        @Autowired

    private TestRestTemplate restTemplate;

    @Test 

  public void test() {

        this.restTemplate.getForEntity( 

           "/{username}/vehicle", String.class, "Phil");

    }

}

请注意,TestRestTemplate现在是随意使用的,就像@SpringBootTest随时都能使用bean一样。这是预配置解决http://localhost:${local.server.port}的相对路径。

我们也可以使用@LocalServerPort注释注入实际的端口,这样服务器就能在测试领域上运行。四、模仿和侦查当你开始测试真正的系统时,你经常会发现它有助于模拟特定的beans。模仿的常见场景包括模拟服务,这个服务在你测试或者测试失败场景的时候是不可用的,因为这很难在同一个系统中触发。使用Spring Boot 1.4你可以轻松地建造一个Mockito mocks,可以取代现有的bean,或者创建一个新的:@RunWith(SpringRunner.class)

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)public class SampleTestApplicationWebIntegrationTests {

    @Autowired    private TestRestTemplate restTemplate;

    @MockBean    private VehicleDetailsService vehicleDetailsService; 

   @Before    public void setup() {

        given(this.vehicleDetailsService.

           getVehicleDetails("123")

        ).willReturn( 

           new VehicleDetails("Honda", "Civic"));

    }

   @Test

    public void test() {

        this.restTemplate.getForEntity("/{username}/vehicle",

            String.class, "sframework");

    }

}

以下是一些例子:?为 VehicleDetailsService创建一个 Mockito mock。?将它如同bean一样注入ApplicationContext。?将它注入到在测试领域。?在?设置方法上存根。?触发最终会调用mock的东西。测试模拟将自动重置。它们还会形成一部分Spring Test使用的缓存键(所以,没有必要添加@DirtiesContext)。Spies以类似的方式工作。

用@SpyBean简单地注释一个测试领域来让spy隐藏所有ApplicationContext中现有的bean。五、JSON断言如果你使用pring-boot-starter-test POM来导入测试依赖关系,从1.4开始,你将有很好的AssertJ文库。AssertJ 提供了一个流畅的assertion API 来代替JUnit的基础org.junit.Assert class。如果你之前没有遇到过,哪买看下面的实例,一个基础的AssertJcall看起来就是这样的:

assertThat(library.getName()).startsWith("Spring").endsWith("Boot");Spring Boot 

1.4 提供了扩展的断言,这样你就能用它来检查JSON marshalling 和 unmarshalling。

public class VehicleDetailsJsonTests {

    private JacksonTesterjson;

    @Before

    public void setup() { 

       ObjectMapper objectMappper = new ObjectMappper();

        // Possibly configure the mapper

        JacksonTester.initFields(this, objectMappper);

    }

    @Test

    public void serializeJson() {

        VehicleDetails details =

            new VehicleDetails("Honda", "Civic");        assertThat(this.json.write(details))

            .isEqualToJson("vehicledetails.json");        assertThat(this.json.write(details))

            .hasJsonPathStringValue("@.make");        assertThat(this.json.write(details))            .extractingJsonPathStringValue("@.make")

            .isEqualTo("Honda");    }

    @Test

    public void deserializeJson() {

        String content = "{\"make\":\"Ford\",\"model\":\"Focus\"}";        assertThat(this.json.parse(content)) 

           .isEqualTo(new VehicleDetails("Ford", "Focus"));        assertThat(this.json.parseObject(content).getMake())

            .isEqualTo("Ford");

   }

}

JSON的比较实际上是使用JSONassert执行的,所以只有JSON的逻辑结构需要匹配。您还可以在上面的示例中看到,JsonPath表达式是如何用于测试或提取数据中的。

六、测试应用程序插件

Spring Boot的自动配置功能对配置应用程序所需要运行的一切是很有用的。

不幸的是,完整的自动配置有时候对于自动测试来说有点多余。

有时候你只是想为应用程序配置一个“插件”——Jackson配置正确吗?我的MVC控制器返回正确的状态代码吗?我的JPA查询运行吗?

使用Spring Boot 1.4这些常见的场景现在很容易被测试。我们也能很容易构建自己的注释,这个注释仅应用了你需要的自动配置classes。

七、测试JPA插件测试

您应用程序的JPA插件(Hibernate +Spring数据)可以使用@DataJpaTest注释。@DataJpaTest将会这样:

1、配置一个内存数据库。

2、自动配置Hibernate,Spring数据和数据源。

3、执行@EntityScan。

4、打开SQL日志记录。以下是一个典型的测试:@RunWith(SpringRunner.class)

@DataJpaTestpublic class UserRepositoryTests {

    @Autowired

    private TestEntityManager entityManager;

    @Autowired    private UserRepository repository; 

   @Test    public void findByUsernameShouldReturnUser() {        this.entityManager.persist(new User("sboot", "123")); 

       User user = this.repository.findByUsername("sboot");                assertThat(user.getUsername()).isEqualTo("sboot");        assertThat(user.getVin()).isEqualTo("123"); 

   }

}以上实验中的TestEntityManager是由Spring Boot支持的。一个标准JPA EntityManager选择所提供的方法通常会在编写测试的过程中使用。

八、测试Spring MVC插件

你可以使用@WebMvcTest注解来测试应用程序的Spring MVC插件。

像这样:

1、自动配置Spring MVC、Jackson,Gson,消息转换器等。

2、加载相关的组件(@Controller, @RestController, @JsonComponent等)。

3、配置MockMVC。

下面是一个测试单个控制器的典型实例:@RunWith(SpringRunner.class)

@WebMvcTest(UserVehicleController.class)public class UserVehicleControllerTests {

    @Autowired    private MockMvc mvc; 

   @MockBean    private UserVehicleService userVehicleService;

    @Test    public void getVehicleShouldReturnMakeAndModel() {        given(this.userVehicleService.getVehicleDetails("sboot"))

            .willReturn(new VehicleDetails("Honda", "Civic"));        this.mvc.perform(get("/sboot/vehicle")

            .accept(MediaType.TEXT_PLAIN))

            .andExpect(status().isOk())

            .andExpect(content().string("Honda Civic"));

    }

}

如果你偏爱HtmlUnit,你可以是用WebClient 来代替 MockMvc。如果你更喜欢selenium,你可以切换到WebDriver。

九、测试JSON插件

如果你需要测试JSON 序列化是否如预期般运营,你可以使用 @JsonTest。

像这样:

1、自动配置Jackson和/或Gson。

2、添加你可以定义的任一模块或者 @JsonComonent beans。

3、触发任何JacksonTester或GsonTester字段的初始化。

以下是实例:

@RunWith(SpringRunner.class)

@JsonTestpublic class VehicleDetailsJsonTests {

    private JacksonTesterjson;

@Test

public void serializeJson() {

VehicleDetails details = new VehicleDetails(

"Honda", "Civic");

assertThat(this.json.write(details))

.extractingJsonPathStringValue("@.make")

.isEqualTo("Honda");

}

}

总结

如果你想尝试Spring Boot 1.4中新的测试特性,你可以从http://repo.spring.io/snapshot/上抓取M2。还有一个GitHub上的示例项目以及更新的文档可以用。如果你有任何我们应该支持的,关于添加“插件”的建议或者改进,请提出及时提出。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,063评论 6 510
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,805评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,403评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,110评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,130评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,877评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,533评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,429评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,947评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,078评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,204评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,894评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,546评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,086评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,195评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,519评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,198评论 2 357

推荐阅读更多精彩内容