基於Groovy+HttpRestful的超輕量級的接口測試用例配置的設計方案及DEMO實現


目標###

設計一個輕量級測試用例框架,接口測試編寫者只需要編寫測試用例相關的內容(入參及結果校驗),不需要理會系統的實現,不需要寫跟測試校驗無關的內容。

思路###

測試用例分析####

一個用例由以下部分組成:

(1) 測試用例名稱 ; (2) 接口名及URL/Path; (3) 接口入參; (4) 接口返回結果校驗。

測試框架需要讀取用例配置信息,根據指定接口及入參調用服務,並根據指定校驗函數來對接口返回結果做檢驗,判斷測試用例是否執行成功。

設計考量####

為了靈活調用不同接口,針對以上的配置,(2) 采用 http restful 的方式; (3) 采用 Map ; (4) 需要一套校驗語法。這里暫時直接采用 groovy 腳本。

DEMO實現###

使用者需要做什么####

接口測試用例編寫者只需要定義一個 TestCase 類即可。這個類含有如下信息:

(1) 待測試接口的 url : http://ip:7001 及 restful 路徑 /xxx

(2) 帶有 @Case 注解的方法。 里面返回一個 Map,

name: 測試用例名 ;

param: 入參map ;

check: 校驗函數。 data 就是返回的頂層。

比如搜索測試用例類SearchTestCases:

package cc.lovesq.study.testcase.qa

import cc.lovesq.study.testcase.Case

class SearchTestCases {

    def url = 'http://ip:7001'
    def path = '/searchApp/order/search'

    @Case
    def get() {
        return [
           'name': 'testSearchOrderNo',
           'param': ['shopId': 55, 'orderNo': 'E20180507200552032000001', 'source':'service-test'],
           'check': { data ->
               data.list.each {
                   order ->
                       order.orderNo == 'E20180507200552032000001'
               }
           }
        ]
    }

}


詳情測試用例類

package cc.lovesq.study.testcase.qa

import cc.lovesq.study.testcase.Case

class DetailTestCases {

    def url = 'http://ip:7001'
    def path = '/detailApp/orderInfo/byOrderNo'

    @Case
    def get() {
        return [
           'name': 'testSingleOrderDetail',
           'param': ['shopId': 55, 'orderNo': 'E20180507200552032000001', 'source':'service-test', 'bizGroup': 'trade'],
           'check': { data ->
               data.mainOrderInfo.orderNo == 'E20180507200552032000001'
           }
        ]
    }

}


框架基本實現####

通過指定的 TestCase 類,讀取其用例配置信息,識別其中的用例配置,通過Http調用接口,然后回調指定的校驗函數來校驗結果。

package cc.lovesq.study.testcase

import groovyx.net.http.ContentType
import groovyx.net.http.HTTPBuilder
import org.slf4j.Logger
import org.slf4j.LoggerFactory

import static groovyx.net.http.Method.POST

class CaseExecutor {

    static Logger log = LoggerFactory.getLogger(CaseExecutor.class)

    def invokeAllCases(testCase) {
        Object tc = testCase
        tc.getClass().getDeclaredMethods().findAll {

            // 識別測試用例: 帶有 @Case
            it.getAnnotation(Case.class) != null

        }.each {
            it ->
                def caseInfo = it.invoke(tc, null)
                def result = exec(caseInfo['name'], tc.url, tc.path, caseInfo['param'], caseInfo['check'])
                println("case=${caseInfo['name']}, result=${result}")
        }
    }

    def exec(name, url, path, param, check) {

        def http = new HTTPBuilder(url)

        def result

        http.request(POST) {
            uri.path = path
            requestContentType = ContentType.JSON
            body = param

            response.success = { resp, json ->
                def data = json.data.data
                try {
                    log.info("Enter Test Case : {}", name)
                    check(data)
                    result = "success"
                } catch (Throwable e) {
                    result = "failed"
                } finally {
                    log.info("Exit Test Case : {}", name)
                }
            }
            response.failure = { resp ->
                println "Unexpected error: ${resp.statusLine.statusCode} : ${resp.statusLine.reasonPhrase}"
                def errorInfo = """
                    Call error: ${resp.statusLine.statusCode} : ${resp.statusLine.reasonPhrase}
                    Name: ${name}
                    Url: ${url}
                    Path: ${path}
                    Param: ${param}
                """
                log.warn(errorInfo)
                result = "failed"
            }
        }
        result
    }
}

客戶端運行測試用例集合:

package cc.lovesq.study.testcase

import cc.lovesq.study.testcase.qa.DetailTestCases
import cc.lovesq.study.testcase.qa.SearchTestCases

class ClientTest {

    def static main(args) {

        CaseExecutor caseExecutor = new CaseExecutor()

        caseExecutor.invokeAllCases(new SearchTestCases())
        caseExecutor.invokeAllCases(new DetailTestCases())

    }
}

代碼講解###

  • 使用了HttpBuilder 類來發送HTTP請求。需要添加POM配置:
<dependency>
      <groupId>org.codehaus.groovy.modules.http-builder</groupId>
      <artifactId>http-builder</artifactId>
      <version>0.6</version>
</dependency>
  • 使用注解 @Case 來表示測試用例。注解可以用於標識一類對象。

  • 使用Groovy閉包來傳遞校驗邏輯。閉包可以用來傳遞變化的邏輯。


自動加載用例集合###

實際應用中,往往不會直接 new 一個 XXXTestCases 對象,而是將這些對象標記為 Component 后,在應用啟動時加載這些 TestCases,建立映射。只要對 CaseExecutor 做一些擴展即可。 如下代碼所示:

@Component("caseExecutor")
class CaseExecutor implements ApplicationContextAware {

    static Logger log = LoggerFactory.getLogger(CaseExecutor.class)

    ApplicationContext context

    def casePathMap = [:]

    @PostConstruct
    def init() {
        Map<String, Object> components = context.getBeansWithAnnotation(Component.class)
        log.info("{}", components)
        components.each {
            name, comp ->
                try {
                    def property = comp.metaClass.getProperty(comp, 'path')
                    if ( property) {
                        casePathMap[property] = comp
                    }
                } catch (e) {
                    log.warn("not having restPath, omit")
                }

        }
    }

   // other codes

    @Override
    void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext
    }
}

小結###

本文講解了一種基於Groovy+HttpRestful的超輕量級的接口測試用例配置的設計方案及DEMO實現。基於這種方法,可以配置化地快速增加指定服務接口的測試用例集合,而不需要額外編寫冗余的測試代碼。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM