接下来是接口测试实战环节。
这个接口非常简单,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有很大机会取得成功。
相关阅读:
本文来自网易实践者社区,经作者hzsunzhengyu授权发布。