5 months ago

如果你比較偏好黑箱測試,或是你的測試環境沒辦法真的提供個資料庫如 circleci,那可以用用看 下面方法

首先你需要個測試時期的資料庫

build.gradle
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

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 他也會幫你執行這樣就有初始資料了

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 ,不過這算是白箱,也就是不用真實啟動一個服務用模擬的資料來提供測試,這樣的測試很難測出真正的問題
你可以試著用這方法

AuthApplicationTests.java
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!

← Windows 發生檔案鎖死 將 Swagger 輸出成 HTML 文檔 →
 
comments powered by Disqus