Spring、Spring Boot和TestNG測試指南 - 使用Spring Boot Testing工具

Github地址html

前面一個部分講解了如何使用Spring Testing工具來測試Spring項目,如今咱們講解如何使用Spring Boot Testing工具來測試Spring Boot項目。java

在Spring Boot項目裏既可使用Spring Boot Testing工具,也可使用Spring Testing工具。
在Spring項目裏,通常使用Spring Testing工具,雖然理論上也可使用Spring Boot Testing,不過由於Spring Boot Testing工具會引入Spring Boot的一些特性好比AutoConfiguration,這可能會給你的測試帶來一些奇怪的問題,因此通常不推薦這樣作。git

例子1:直接加載Bean

使用Spring Boot Testing工具只須要將@ContextConfiguration改爲@SpringBootTest便可,源代碼見FooServiceImpltestgithub

@SpringBootTest(classes = FooServiceImpl.class)
public class FooServiceImplTest extends AbstractTestNGSpringContextTests {

  @Autowired
  private FooService foo;

  @Test
  public void testPlusCount() throws Exception {
    assertEquals(foo.getCount(), 0);

    foo.plusCount();
    assertEquals(foo.getCount(), 1);
  }

}

例子2:使用內嵌@Configuration加載Bean

源代碼見FooServiceImpltestspring

@SpringBootTest
public class FooServiceImplTest extends AbstractTestNGSpringContextTests {

  @Autowired
  private FooService foo;

  @Test
  public void testPlusCount() throws Exception {
    assertEquals(foo.getCount(), 0);

    foo.plusCount();
    assertEquals(foo.getCount(), 1);
  }

  @Configuration
  @Import(FooServiceImpl.class)
  static class Config {
  }

}

例子3:使用外部@Configuration加載Bean

Config數據庫

@Configuration
@Import(FooServiceImpl.class)
public class Config {
}

FooServiceImpltestapi

@SpringBootTest(classes = Config.class)
public class FooServiceImplTest extends AbstractTestNGSpringContextTests {

  @Autowired
  private FooService foo;

  @Test
  public void testPlusCount() throws Exception {
    assertEquals(foo.getCount(), 0);

    foo.plusCount();
    assertEquals(foo.getCount(), 1);
  }

}

這個例子和例子2差很少,只不過將@Configuration放到了外部。緩存

例子4:使用@SpringBootConfiguration

前面的例子@SpringBootTest的用法和@ContextConfiguration差很少。不過根據@SpringBootTest文檔springboot

  1. 它會嘗試加載@SpringBootTest(classes=...)的定義的Annotated classes。Annotated classes的定義在ContextConfiguration中有說明。spring-boot

  2. 若是沒有設定@SpringBootTest(classes=...),那麼會去找當前測試類的nested @Configuration class

  3. 若是上一步找到,則會嘗試查找@SpringBootConfiguration,查找的路徑有:1)看當前測試類是否@SpringBootConfiguration,2)在當前測試類所在的package裏找。

因此咱們能夠利用這個特性來進一步簡化測試代碼。

Config

@SpringBootConfiguration
@Import(FooServiceImpl.class)
public class Config {
}

FooServiceImpltest

@SpringBootTest
public class FooServiceImplTest extends AbstractTestNGSpringContextTests {

  @Autowired
  private FooService foo;

  @Test
  public void testPlusCount() throws Exception {
    assertEquals(foo.getCount(), 0);

    foo.plusCount();
    assertEquals(foo.getCount(), 1);
  }

}

例子5:使用@ComponentScan掃描Bean

前面的例子咱們都使用@Import來加載Bean,雖然這中方法很精確,可是在大型項目中很麻煩。

在常規的Spring Boot項目中,通常都是依靠自動掃描機制來加載Bean的,因此咱們但願咱們的測試代碼也可以利用自動掃描機制來加載Bean。

Config

@SpringBootConfiguration
@ComponentScan(basePackages = "me.chanjar.basic.service")
public class Config {
}

FooServiceImpltest

@SpringBootTest
public class FooServiceImplTest extends AbstractTestNGSpringContextTests {

  @Autowired
  private FooService foo;

  @Test
  public void testPlusCount() throws Exception {
    assertEquals(foo.getCount(), 0);

    foo.plusCount();
    assertEquals(foo.getCount(), 1);
  }

}

例子6:使用@SpringBootApplication

也能夠在測試代碼上使用@SpringBootApplication,它有這麼幾個好處:

  1. 自身SpringBootConfiguration

  2. 提供了@ComponentScan配置,以及默認的excludeFilter,有了這些filter Spring在初始化ApplicationContext的時候會排除掉某些Bean和@Configuration

  3. 啓用了EnableAutoConfiguration,這個特性可以利用Spring Boot來自動化配置所須要的外部資源,好比數據庫、JMS什麼的,這在集成測試的時候很是有用。

Config

@SpringBootApplication(scanBasePackages = "me.chanjar.basic.service")
public class Config {
}

FooServiceImpltest

@SpringBootTest
public class FooServiceImplTest extends AbstractTestNGSpringContextTests {

  @Autowired
  private FooService foo;

  @Test
  public void testPlusCount() throws Exception {
    assertEquals(foo.getCount(), 0);

    foo.plusCount();
    assertEquals(foo.getCount(), 1);
  }

}

避免@SpringBootConfiguration衝突

@SpringBootTest沒有定義(classes=...,且沒有找到nested @Configuration class的狀況下,會嘗試查詢@SpringBootConfiguration,若是找到多個的話則會拋出異常:

Caused by: java.lang.IllegalStateException: Found multiple @SpringBootConfiguration annotated classes [Generic bean: class [...]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [/Users/qianjia/workspace-os/spring-test-examples/basic/target/test-classes/me/chanjar/basic/springboot/ex7/FooServiceImplTest1.class], Generic bean: class [me.chanjar.basic.springboot.ex7.FooServiceImplTest2]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [...]]

好比如下代碼就會形成這個問題:

@SpringBootApplication(scanBasePackages = "me.chanjar.basic.service")
public class Config1 {
}

@SpringBootApplication(scanBasePackages = "me.chanjar.basic.service")
public class Config2 {
}

@SpringBootTest
public class FooServiceImplTest extends AbstractTestNGSpringContextTests {
  // ...
}

解決這個問題的方法有就是避免自動查詢@SpringBootConfiguration

  1. 定義@SpringBootTest(classes=...)

  2. 提供nested @Configuration class

最佳實踐

除了單元測試(不須要初始化ApplicationContext的測試)外,儘可能將測試配置和生產配置保持一致。好比若是生產配置裏啓用了AutoConfiguration,那麼測試配置也應該啓用。由於只有這樣纔可以在測試環境下發現生產環境的問題,也避免出現一些由於配置不一樣致使的奇怪問題。

在測試代碼之間儘可能作到配置共用,這麼作的優勢有3個:

  1. 可以有效利用Spring TestContext Framework的緩存機制,ApplicationContext只會建立一次,後面的測試會直接用已建立的那個,加快測試代碼運行速度。

  2. 當項目中的Bean不少的時候,這麼作可以下降測試代碼複雜度,想一想若是每一個測試代碼都有一套本身的@Configuration或其變體,那得多嚇人。

參考文檔

相關文章
相關標籤/搜索