接口测试之Kotlin篇(下)

达芬奇密码2018-07-18 13:49

三、接口测试实战

    接下来是接口测试实战环节。
     这个接口非常简单,HTTP Method为GET,只有一个header参数authorization(同时也会检查Cookie),返回值为一个包含个人信息的json。测试代码中,将使用HttpClient发送请求,使用TestNG控制测试流程。
    首先创建一个工具类文件Ktutil.kt。虽然这个接口是GET方法的,可以直接使用HttpClient的HttpGet类型,但是考虑到以后可能会用到其他Method,因此决定仿照HttpGet的实现方式,继承HttpRequestBase类实现一个自定义的类型,代码实现如下:

internal class HttpSimpleMethods constructor(var uri:String, val METHOD_NAME:String): HttpRequestBase() {
    init {
        if(METHOD_NAME !in listOf("GET", "DELETE", "OPTIONS", "TRACE", "HEAD")){
            throw Exception("Method $METHOD_NAME not supported!")
        }
        setURI(URI.create(uri))
    }
    override fun getMethod(): String {
        return METHOD_NAME
    }
}

    类似的,仿照HttpPost自定义一个类型:

internal class HttpEntityMethods constructor(var uri:String, val METHOD_NAME:String): HttpEntityEnclosingRequestBase() {
    init {
        if(METHOD_NAME !in listOf("POST", "PUT")){
            throw Exception("Method $METHOD_NAME not supported!")
        }
        setURI(URI.create(uri))
    }
    override fun getMethod(): String {
        return METHOD_NAME
    }
}

    接着,封装一下HttpClient执行的过程(写过的同学可以对比一下Java的版本):

data class HttpResult(val code:String, val data:String) /** * 第二步, 执行HttpSimpleMethods类型的请求 */ fun httpSimpleRun(sp: HttpSimpleMethods): HttpResult {
    var client = HttpClients.createDefault()
    var response = try {
        client.execute(sp)
    }catch (e:Exception){
        logger.error(e.toString())
        throw Exception("request falied") } val entity = response.entity
    val code = response.statusLine.statusCode
    val data = EntityUtils.toString(entity, "utf-8")
    response.close()
    client.close()
    return HttpResult(code.toString(), data) } 

    可携带body的请求类型(POST、PUT)要复杂一些,还需要一个添加body的函数:

/** * 第二步, 给HttpEntityMethods类型的请求添加body */
fun httpAddBody(em: HttpEntityMethods, data: String, type: String): HttpEntityMethods {
    when (type) {
        "json" -> em.addHeader("Content-Type", "application/json; charset=utf-8")
        "form" -> em.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
        "xml" -> em.addHeader("Content-Type", "application/xml; charset=utf-8")
        else -> throw Exception("type $type not supported!") } // StringEntity,用于把字符串直接加进body,需要在之前先转换好格式 em.setEntity(StringEntity(data, Charset.forName("utf-8"))) return em } 

    这种类型的执行函数和上面的httpSimpleRun相同,这里就不再展示了,其实两者可以整合成一个泛型函数来处理。
    最后,根据在之前部门的使用习惯,还定义了一些judge函数来进行判断,其中最常用的一个代码如下:

fun judge(desc:String, expect:Boolean, real:Boolean, debugInfo:String = ""){
    if(expect != real){
        logger.error("$desc:不通过, expect:$expect, real:$real, debug:$debugInfo")
        Assert.fail(desc)
    }else{
        logger.info("$desc:通过, debug:$debugInfo")
    }
}

    工具类到此编写完毕。鉴于测试环境通常还有多套,因此我们还需要一种方式来指定测试执行的环境。由于测试的代码每次都是编译后再执行,个人习惯于直接写在类中,因此定义一个Environment.kt如下:

class Environment(env: String){
    var host = ""
    val this_env = env
    init {
        when(this_env){
            "dev" -> host = "https://console-dev.163yun.com"
            "qa" -> host = "https://console-qa.163yun.co"      
            "ci" -> host = "https://console-ci.163yun.com"
            "test" -> host = "https://console-ci.163yun.com"
            "yanlian" -> host = "https://console-yanlian.163yun.com"
            "public" -> host = "https://console-public.163yun.com"
        }
    }
}

    (更普遍的做法似乎是从配置文件中读取)

    接下来就是对应接口了。创建一个ConsoleAPI.kt文件,给这个接口定义一个函数:

class ConsoleAPI(var host:String){
    val logger = LogManager.getLogger("ConsoleAPI")

    /**
     * @param authorization token签名
     * @param urs_t urs_t
     * @param urs_u urs_u
     */
    fun basicInfo(authorization:String, urs_t:String, urs_u:String): HttpResult{
        val url = host + "/userinfo/basicInfo"
        var get = httpSimpleMethodInit(url, "GET")
        val cookie = "urs_t=$urs_t; urs_u=$urs_u"
        logger.info("run $url, authorization=$authorization, urs_t=$urs_t, urs_u=$urs_u")
        get.addHeader("authorization", authorization)
        get.addHeader("Cookie", cookie)
        return httpSimpleRun(get)
    }
}

    然后是TestNG的环节。创建一个ConsoleTest.kt,代码如下:

class ConsoleTest constructor(){
    val logger = LogManager.getLogger("ConsoleTest")   //这里是之前写JAVA时用的log4j
    lateinit var env: Environment
    lateinit var api: ConsoleAPI
    lateinit var account: PrimaryAccountTest

    constructor(environment: String):this(){
        //用于在main里调试
        logger.info("test start:env=$environment")
        env = Environment(environment)
        api = ConsoleAPI(env.host)
        account = PrimaryAccountTest(env.this_env)
        loginAndGetToken(true)
    }

    data class TokenInfo(var authorization:String, var urs_t:String, var urs_u:String)
    fun loginAndGetToken(needLogin:Boolean=false): TokenInfo {
        //这里调用了之前账号项目里的登录方法,返回签名authrization和Cookie里的两个值urs_t、urs_u
        if(needLogin){
            account.testNegotiateSecret()
            account.testLoginWithPhone()
            account.testCookieForToken()
        }
        val authorization = account.api.calTokenAuth(account.userId, account.tokenId, account.tokenKey, account.getNounce(), account.h)
        return TokenInfo(authorization, account.urs_t, account.urs_u)
    }

    @BeforeClass(alwaysRun = true)
    @Parameters("environment")
    fun init(environment: String) {
        logger.info("test start:env=$environment")
        env = Environment(environment)
        api = ConsoleAPI(env.host)
        account = PrimaryAccountTest(env.this_env)
        loginAndGetToken(true)
    }

    @Test(groups = arrayOf("p0", "all"))
    fun testBasicInfo(){
        var (authorization, urs_t, urs_u) = loginAndGetToken()
        var (code, data) = api.basicInfo(authorization, urs_t, urs_u)
        qcutil.judge("测试获取控制台用户基本信息", true, code == "200" && data.contains("userName"),
                    "code=$code, data=$data)")
    }

    @Test(groups = arrayOf("p1", "all"), dependsOnMethods = arrayOf("testBasicInfo"), dataProvider = "basicInfoProvider")
    fun testBasicInfoFailed(desc:String, success: Boolean, authorization:String, urs_t:String, urs_u:String){
        var (code, data) = api.basicInfo(authorization, urs_t, urs_u)
        if(!success){
            qcutil.judge("测试获取控制台用户基本信息(非法值)-$desc", true, 
                         code=="400" && !data.contains("userName"),
                        "code=$code, data=$data)")
        }
    }

    @DataProvider(name="basicInfoProvider")
    fun basicInfoProvider():Array<Array<Any>>{
        var (authorization, urs_t, urs_u) = loginAndGetToken()
        return arrayOf( arrayOf("签名错误", false, authorization.split("hash")[0], urs_t, urs_u),
                arrayOf("Cookie缺少urs_t", false, authorization, "", urs_u),
                arrayOf("Cookie缺少urs_u", false, authorization, urs_t, "")
        )
    }
}

fun main(args: Array<String>) {
    //可以在main函数里直接启动指定的一个接口测试
    var test = ConsoleTest("ci")
    test.testBasicInfo()
}

最后,创建一个TestNG的xml配置文件,和以前一样执行即可:

<suite name="Suite1">
    <parameter name="environment" value="qa"/>
    <test name="test1" verbose="2">
        <groups>
            <run>
                <include name="p[0-1]" />
            </run>
        </groups>
        <classes>
            <class name="cloud.console.ConsoleTest"/>
        </classes>
    </test>
</suite>

四、总结

   Kotlin是一门学起来像是Java框架的、吸收了C#和Python的一些优点、Google钦定、IDE给力、(日常)要火的语言。有时间的同学可以学习一下,现在看来,至少在Android领域Kotlin有很大机会取得成功。

五、参考文档


相关阅读:

接口测试之Kotlin篇(上)

本文来自网易实践者社区,经作者hzsunzhengyu授权发布。