如果你比較偏好黑箱測試,或是你的測試環境沒辦法真的提供個資料庫如 circleci,那可以用用看 下面方法
首先你需要個測試時期的資料庫
buildscript {
ext {
springBootVersion = '1.5.2.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile('org.springframework.boot:spring-boot-starter-security')
compile 'org.springframework.security.oauth:spring-security-oauth2:2.0.12.RELEASE'
compile 'org.springframework.security:spring-security-jwt:1.0.7.RELEASE'
compile 'org.apache.commons:commons-lang3:3.5'
compile 'org.modelmapper:modelmapper:0.7.7'
compile 'mysql:mysql-connector-java:6.0.5'
compile 'io.jsonwebtoken:jjwt:0.7.0'
compileOnly('org.projectlombok:lombok')
testRuntime('com.h2database:h2')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
使用 testRuntime 加入 H2
再來在你測試資料夾下配置資料庫
\src\test\resources\application.yml
spring:
application:
name: AuthService
profiles:
active: test
jackson:
date-format: com.fasterxml.jackson.databind.util.ISO8601DateFormat
time-zone: UTC
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:test;MODE=MySQL;DB_CLOSE_DELAY=-1
initialize: false
sql-script-encoding: UTF-8
jpa:
hibernate:
ddl-auto: create-drop
generate-ddl: false
database-platform: H2
show-sql: true
datasource 的部分
initialize 要為 fasle 表示我們不需要 spring jdbc 對資料庫初始化
jpa 部分
database-platform 指定目標數據庫的類型
generate-ddl 是否在啟動時初始化schema,默認為false
ddl-auto 為 create-drop 表示 hibernate 會用 Entity 來自動產生出對應的資料表格
這樣測試啟動的時候就會幫我們做這些事
如果你有 \src\test\resources\import.sql 他也會幫你執行這樣就有初始資料了
INSERT INTO `account` (`accountid`, `username`, `email`, `password`, `enabled`, `expired`, `locked`, `credentialsexpired`, `createddate`, `createdby`, `lastmodifieddate`, `lastmodifiedby`) VALUES ('0000000000', 'admin', 'admin@pousheng.com', '$2a$10$c85hYXPx4niZCCkmxeqXHOriQvvaWBSd9SVpYoq2ZAbs0uUa1ESL.', 1 , 0 , 0, 0, '2017-02-20 03:10:53', '0000000000', '2017-02-20 03:15:02', '0000000000');
INSERT INTO `role` (`roleid`, `code`, `label`, `createddate`, `createdby`, `lastmodifieddate`, `lastmodifiedby`) VALUES ('0000000000', 'ROLE_ADMIN', '系統管理員', '2017-02-17 04:58:12', '0000000000', '2017-02-17 04:58:12', '0000000000');
INSERT INTO `account_role` (`serid`, `accountid`, `roleid`, `createddate`, `createdby`, `lastmodifieddate`, `lastmodifiedby`) VALUES ('0000000000', '0000000000', '0000000000', '2017-03-01 21:50:32', '0000000000', '2017-03-01 21:50:32', '0000000000');
H2其他配置
DB_CLOSE_DELAY:要求最後一個正在連接的連接斷開後,不要關閉資料庫
MODE=MySQL:兼容模式,H2兼容多種資料庫,該值可以為:DB2、Derby、HSQLDB、MSSQLServer、MySQL、Oracle、PostgreSQL
AUTO_RECONNECT=TRUE:連接丟失後自動重新連接
AUTO_SERVER=TRUE:啟動自動混合模式,允許開啟多個連接,該參數不支持在內存中運行模式
TRACE_LEVEL_SYSTEM_OUT、TRACE_LEVEL_FILE:輸出跟蹤日誌到控制台或文件, 取值0為OFF,1為ERROR(默認值),2為INFO,3為DEBUG
SET TRACE_MAX_FILE_SIZE mb:設置跟蹤日誌文件的大小,默認為16M
原文網址:https://read01.com/m3ke4.html
寫你的測試
很多人會用 Mockito ,不過這算是白箱,也就是不用真實啟動一個服務用模擬的資料來提供測試,這樣的測試很難測出真正的問題
你可以試著用這方法
package com.ps;
import com.ps.model.Account;
import com.ps.model.Role;
import com.ps.repository.AccountRepository;
import com.ps.repository.RoleRepository;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import java.nio.charset.StandardCharsets;
import java.util.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertNotNull;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
//@ActiveProfiles(profiles = "test")
public class AuthApplicationTests {
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private AccountRepository accountRepository;
@Autowired
private RoleRepository roleRepository;
@Before
public void setup() {
}
@Test
public void accountIsExist() throws Exception {
// 確認帳號存在
Account account = accountRepository.findOne("0000000000");
assertNotNull(account);
}
@Test
public void accountHasRole() throws Exception {
// 確認帳號可以取得角色資訊
List<Role> roleList = accountRepository.findRoleListByUsername("admin");
assertThat(roleList.size()).isEqualTo(1);
}
@Test
public void roleIsExist() throws Exception {
// 確認角色存在
Role role = roleRepository.findOne("0000000000");
assertThat(role.getCode()).isEqualTo("ROLE_ADMIN");
}
@Test
public void testOauthToken() {
// 確認取得 token
String clientid = "clientapp";
String clientpw = "123456";
String username = "admin";
String password = "123456";
String granttype = "password";
String scope = "account role";
ResponseEntity<Map> response = this.getOauthToken(clientid, clientpw, username, password, granttype, scope);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@Test
public void testRefreshToken() {
String clientid = "clientapp";
String clientpw = "123456";
String username = "admin";
String password = "123456";
String scope = "account role";
ResponseEntity<Map> response = this.getOauthToken(clientid, clientpw, username, password, "password", scope);
String refreshToken = null;
if (response.getStatusCode().equals(HttpStatus.OK)) {
refreshToken = (String) response.getBody().get("refresh_token");
}
response = this.refreshNewToken(clientid, clientpw, "refresh_token", refreshToken);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
// 使用 TestRestTemplate
private ResponseEntity<Map> getOauthToken(String clientid, String clientpw, String username, String password, String granttype, String scope) {
TestRestTemplate basicAuthTemplate = restTemplate.withBasicAuth(clientid, clientpw);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
map.add("username", username);
map.add("password", password);
map.add("grant_type", granttype);
map.add("scope", scope);
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<MultiValueMap<String, String>>(map, headers);
ResponseEntity<Map> response = basicAuthTemplate.exchange("/oauth/token", HttpMethod.POST, entity, Map.class);
return response;
}
// 更新 token
private ResponseEntity<Map> refreshNewToken(String clientid, String clientpw, String granttype, String refreshtoken) {
TestRestTemplate basicAuthTemplate = restTemplate.withBasicAuth(clientid, clientpw);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
map.add("grant_type", granttype);
map.add("refresh_token", refreshtoken);
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<MultiValueMap<String, String>>(map, headers);
ResponseEntity<Map> response = basicAuthTemplate.exchange("/oauth/token", HttpMethod.POST, entity, Map.class);
return response;
}
// 自己配置比較多的細節
private ResponseEntity<Map> getOauthTokenCust(String clientid, String clientpw, String username, String password, String granttype, String scope) {
Base64.Encoder encoder = Base64.getEncoder();
String normalString = clientid + ":" + clientpw;
String basicAuthEncoded = encoder.encodeToString(normalString.getBytes(StandardCharsets.UTF_8));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.add("Authorization", String.format("%s %s", "Basic", basicAuthEncoded));
MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
map.add("username", username);
map.add("password", password);
map.add("grant_type", granttype);
map.add("scope", scope);
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<MultiValueMap<String, String>>(map, headers);
ResponseEntity<Map> response = restTemplate.exchange("/oauth/token", HttpMethod.POST, entity, Map.class);
return response;
}
// @Test
// public void testItemGroupReport() throws Exception {
// ResponseEntity<byte[]> response = this.restTemplate.getForEntity(
// "/api/v1/ItemGroupReport?id={id}&itemGroupName={itemGroupName}&shopIdPmt={shopIdPmt}&brand={brand}&isSpecial={isSpecial}", byte[].class,
// "1", "itemGroupName", "1", "brand", "1");
// assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
// //assertThat(entity.getBody()).isEqualTo("Hello, world");
// }
//
// @Test
// public void testItemReport() throws Exception {
// ResponseEntity<byte[]> entity = this.restTemplate.getForEntity(
// "/api/v1/ItemReport?id=1&upcCode=1&itemName=1&type=1&itemGroupName=1", byte[].class);
// assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
// }
}
這會啟動一個真實的 service 不過會聽一個隨機的 port ,好處是如果同時很多服務在測,大家不會搶
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
既然是隨機的 port 你會想說那到底要怎麼呼叫 API ,請使用 TestRestTemplate
這是一個測試專用的物件,只有在 @SpringBootTest 才會初始化
既然是spring的組件,當然就省去一些麻煩,直接寫路徑就可以測試了,port 他會處理
@Test
public void testItemGroupReport() throws Exception {
ResponseEntity<byte[]> response = this.restTemplate.getForEntity(
"/api/v1/ItemGroupReport?id={id}&itemGroupName={itemGroupName}&shopIdPmt={shopIdPmt}&brand={brand}&isSpecial={isSpecial}", byte[].class,
"1", "itemGroupName", "1", "brand", "1");
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
Done!