這篇文章將為大家詳細(xì)講解有關(guān)spring boot之集成測(cè)試的示例分析,小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。
為企業(yè)提供做網(wǎng)站、網(wǎng)站設(shè)計(jì)、網(wǎng)站優(yōu)化、營(yíng)銷型網(wǎng)站、競(jìng)價(jià)托管、品牌運(yùn)營(yíng)等營(yíng)銷獲客服務(wù)。創(chuàng)新互聯(lián)擁有網(wǎng)絡(luò)營(yíng)銷運(yùn)營(yíng)團(tuán)隊(duì),以豐富的互聯(lián)網(wǎng)營(yíng)銷經(jīng)驗(yàn)助力企業(yè)精準(zhǔn)獲客,真正落地解決中小企業(yè)營(yíng)銷獲客難題,做到“讓獲客更簡(jiǎn)單”。自創(chuàng)立至今,成功用技術(shù)實(shí)力解決了企業(yè)“網(wǎng)站建設(shè)、網(wǎng)絡(luò)品牌塑造、網(wǎng)絡(luò)營(yíng)銷”三大難題,同時(shí)降低了營(yíng)銷成本,提高了有效客戶轉(zhuǎn)化率,獲得了眾多企業(yè)客戶的高度認(rèn)可!
如果希望很方便針對(duì)API進(jìn)行測(cè)試,并且方便的集成到CI中驗(yàn)證每次的提交,那么spring boot自帶的IT絕對(duì)是不二選擇。
迅速編寫一個(gè)測(cè)試Case
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles({Profiles.ENV_IT}) public class DemoIntegrationTest{ @Autowired private FooService fooService; @Test public void test(){ System.out.println("tested"); } }
其中 SpringBootTest 定義了跑IT時(shí)的一些配置,上述代碼是用了隨機(jī)端口,當(dāng)然也可以預(yù)定義端口,像這樣
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, properties = {"server.port=9990"})
ActiveProfiles 強(qiáng)制使用了IT的Profile,從最佳實(shí)踐上來說IT Profile所配置的數(shù)據(jù)庫或者其他資源組件的地址,應(yīng)該是與開發(fā)或者Staging環(huán)境隔離的。因?yàn)楫?dāng)一個(gè)IT跑完之后很多情況下我們需要清除測(cè)試數(shù)據(jù)。
你能夠發(fā)現(xiàn)這樣的Case可以使用 Autowired 注入任何想要的Service。這是因?yàn)閟pring將整個(gè)上下文都加載了起來,與實(shí)際運(yùn)行的環(huán)境是一樣的,包含了數(shù)據(jù)庫,緩存等等組件。如果覺得測(cè)試時(shí)不需要全部的資源,那么在profile刪除對(duì)應(yīng)的配置就可以了。這就是一個(gè)完整的運(yùn)行環(huán)境,唯一的區(qū)別是當(dāng)用例跑完會(huì)自動(dòng)shutdown。
測(cè)試一個(gè)Rest API
強(qiáng)烈推薦一個(gè)庫,加入到gradle中
testCompile 'io.rest-assured:rest-assured:3.0.3'
支持JsonPath,十分好用,具體文檔戳 這里
@Sql(scripts = "/testdata/users.sql") @Test public void test001Login() { String username = "demo@demo.com"; String password = "demo"; JwtAuthenticationRequest request = new JwtAuthenticationRequest(username, password); Response response = given().contentType(ContentType.JSON).body(request) .when().post("/auth/login").then() .statusCode(HttpStatus.OK.value()) .extract() .response(); assertThat(response.path("token"), is(IsNull.notNullValue())); assertThat(response.path("expiration"), is(IsNull.notNullValue())); }
@Sql 用于在測(cè)試前執(zhí)行sql插入測(cè)試數(shù)據(jù)。注意 given().body()
中傳入的是一個(gè)java對(duì)象 JwtAuthenticationRequest ,因?yàn)閞est-assured會(huì)自動(dòng)幫你用 jackson 將對(duì)象序列化成json字符串。當(dāng)然也可以將轉(zhuǎn)換好的json放到body,效果是一樣的。
返回結(jié)果被一個(gè)Response接住,之后就可以用JsonPath獲取其中數(shù)據(jù)進(jìn)行驗(yàn)證。當(dāng)然還有一種更直觀的辦法,可以通過 response.asString() 獲取完整的response,再反序列化成java對(duì)象進(jìn)行驗(yàn)證。
至此,最基本的IT就完成了。 在Jenkins增加一個(gè)step gradle test 就可以實(shí)現(xiàn)每次提交代碼都進(jìn)行一次測(cè)試。
一些復(fù)雜的情況
數(shù)據(jù)混雜
這是最容易發(fā)生,一個(gè)項(xiàng)目有很多dev,每個(gè)dev都會(huì)寫自己的IT case,那么如果數(shù)據(jù)之間產(chǎn)生了影響怎么辦。很容易理解,比如一個(gè)測(cè)試批量寫的場(chǎng)景,最后驗(yàn)證方式是看寫的數(shù)據(jù)量是不是10w行。那么另外一個(gè)dev寫了其他的case恰好也新增了一條數(shù)據(jù)到這張表,結(jié)果變成了10w+1行,那么批量寫的case就跑不過了。
為了杜絕這種情況,我們采用每次跑完一個(gè)測(cè)試Class就將數(shù)據(jù)清空。既然是基于類的操作,可以寫一個(gè)基類解決。
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles({Profiles.ENV_IT}) public abstract class BaseIntegrationTest { private static JdbcTemplate jdbcTemplate; @Autowired public void setDataSource(DataSource dataSource) { jdbcTemplate = new JdbcTemplate(dataSource); } @Value("${local.server.port}") protected int port; @Before public void setupEnv() { RestAssured.port = port; RestAssured.basePath = "/api"; RestAssured.baseURI = "http://localhost"; RestAssured.config = RestAssured.config().httpClient(HttpClientConfig.httpClientConfig().httpMultipartMode(HttpMultipartMode.BROWSER_COMPATIBLE)); } public void tearDownEnv() { given().contentType(ContentType.JSON) .when().post("/auth/logout"); } @AfterClass public static void cleanDB() throws SQLException { Resource resource = new ClassPathResource("/testdata/CleanDB.sql"); Connection connection = jdbcTemplate.getDataSource().getConnection(); ScriptUtils.executeSqlScript(connection, resource); connection.close(); } }
@AfterClass 中使用了jdbcTemplate執(zhí)行了一個(gè)CleanDB.sql,通過這種方式清除所有測(cè)試數(shù)據(jù)。
@Value("${local.server.port}")
也要提一下,因?yàn)槎丝谑请S機(jī)的,那么Rest-Assured不知道請(qǐng)求要發(fā)到losthost的哪個(gè)端口上,這里使用 @Value 獲取當(dāng)前的端口號(hào)并設(shè)置到 RestAssured.port 就解決了這個(gè)問題。
共有數(shù)據(jù)怎么處理
跑一次完整的IT,可能需要經(jīng)歷數(shù)十個(gè)Class,數(shù)百個(gè)method,那么如果一些數(shù)據(jù)是所有case都需要的,只有在所有case都跑完才需要清除怎么辦?換句話說,這種數(shù)據(jù)清理不是基于 類 的,而是基于一次 運(yùn)行 。比如初始用戶數(shù)據(jù),城市庫等等
我們耍了個(gè)小聰明,借助了 flyway
@Configuration @ConditionalOnClass({DataSource.class}) public class UpgradeAutoConfiguration { public static final String FLYWAY = "flyway"; @Bean(name = FLYWAY) @Profile({ENV_IT}) public UpgradeService cleanAndUpgradeService(DataSource dataSource) { UpgradeService upgradeService = new FlywayUpgradeService(dataSource); try { upgradeService.cleanAndUpgrade(); } catch (Exception ex) { LOGGER.error("Flyway failed!", ex); } return upgradeService; } }
可以看到當(dāng)Profile是IT的情況下, flyway 會(huì)drop掉所有表并重新依次執(zhí)行每次的upgrade腳本,由此創(chuàng)建完整的數(shù)據(jù)表,當(dāng)然都是空的。在項(xiàng)目的test路徑下,增加一個(gè)版本極大的sql,這樣就可以讓 flyway 在最后插入共用的測(cè)試數(shù)據(jù),例如 src/test/resources/db/migration/V999.0.1__Insert_Users.sql
,完美的解決各種數(shù)據(jù)問題。
關(guān)于“spring boot之集成測(cè)試的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,使各位可以學(xué)到更多知識(shí),如果覺得文章不錯(cuò),請(qǐng)把它分享出去讓更多的人看到。