一、什么是Moco:
Moco 是一个搭建模拟服务器的工具,其支持 API 和独立运行两种方式,前者通常是在 junit 、testng等测试框架中使用,后者则是通过运行一个 jar 包开启服务。
二、Moco在开发中会起到的作用:
Moco是针对HTTP集成而生的,不过,现在也有人把它用在其它需要一个模拟服务器的场景中。比如,在移动开发中,有人开发一个移动应用,需要有一个远端服务,但在开发时,这个服务还不存在,他就用Moco模拟了一个服务,保证移动应用可以顺利的开发。同样,也有人把它用在Web前端开发里,当我们的页面需要通过与服务器交互时,就可以用Moco模拟这样一个服务。这种做法在开发一个页面原型时,非常有用,因为那个时候,我们还来不及开发一个完整的服务。
三、Moco在接口测试中的用处:
既然开发人员可以通过 Moco 模拟一个还不存在的服务来进行开发、调试,那对于接口测试来说,也可以模拟一个服务进行测试。
一般而言,在项目的接口文档输出后,开发人员会进行接口开发工作,测试人员会进行接口用例的设计,但往往完成用例设计会先于接口开发工作,此时如果要进行接口用例的执行,则前提是开发人员完成接口开发工作。
而通过 Moco 框架,就可以在接口文档输出后,在接口开发、接口用例设计的同时,使用 Moco 搭建一个模拟服务器,这样在用例设计完成后,即使接口开发工作还未完成,也可以立即进行执行接口用例,在这个过程中可以修改、补充用例,如此的话,在接口开发完成以后,只需要简单的去执行所有的用例就 OK,省去了很大的工作量,并且这些完善的用例,用自动化去执行,效果更佳。
四、Moco的应用:
本章会通过示例讲如下信息:
模拟get请求
模拟带参数的get请求
模拟post请求
模拟带form格式参数的post请求
模拟request请求中带cookies信息的get请求
模拟request请求中带cookies的post请求,post请求带json格式参数,返回结果也使用json格式
模拟response返回中带有cookies信息的get请求
模拟带有headers信息的mock请求
模拟有重定向的接口
使用httpclient调moco的接口
Moco jar下载地址:
https://repo1.maven.org/maven2/com/github/dreamhead/moco-runner/0.11.0/
下载红框中的内容:
把jar包放到项目根目录下:
新建一个json文件,文件名为startup1.json
json中内容为:
[
{
"description": "这是第一个moco操作,格式要写正确,中括号、大括号一定要有,description是描述,request是请求,uri是请求路径,response是返回结果,text是返回的是文本类型内容",
"request": {
"uri": "/demo"
},
"response": {
"text": "this is the first moco demo"
}
}
]
格式一定要写对,中括号、大括号一定要有,description是描述,request是请求,uri是请求路径,response是返回结果,text是返回的是文本类型内容
进入项目目录中,执行命令启动服务:
java -jar ./moco-runner-0.11.0-standalone.jar http -p 8888 -c startup1.json
http:是指是http协议,-p 8888是端口号 -c是执行当前文件
然后在postman中调接口执行就可以了,默认是get请求:
如果在text中写中文,会出现乱码情况,需要加配置信息:
{"description":"这是第二个moco操作,模拟返回值为中文",
"request":{
"uri":"/demotwo"
},
"response":{
"text":"这是第二个demo"
}
}
在postman中调接口返回乱码:
在返回结果中加入:
"headers":{
"Content-Type":"text/html;charset=gbk"
}
{"description":"这是第二个moco操作,模拟返回值为中文",
"request":{
"uri":"/demotwo"
},
"response":{
"text":"这是第二个demo",
"headers":{
"Content-Type":"text/html;charset=gbk"
}
}
}
修改json文件不需要重启,服务热部署会自动重启
在浏览器中调接口能成功返回中文:
为什么要在response中加一个header呢?
是因为服务器要通过实体头(header)的形式告诉浏览器一些信息,Content-Type就是指服务器告诉浏览器它发送的数据属于什么文件类型。 "Content-Type":"text/html;charset=gbk"就是服务器告诉浏览器返回的内容是text/html格式要以html的格式展示,charset=gbk就是字符集,告诉浏览器可以解析中文字符信息。
扩展一个小知识:
gbk和utf8的区别
我们这里将以最简单最容易理解的方式来描述GBK和UTF8的区别,以及它们分别是什么。
GBK编码:是指中国的中文字符,其它它包含了简体中文与繁体中文字符,另外还有一种字符“gb2312”,这种字符仅能存储简体中文字符。
UTF-8编码:它是一种全国家通过的一种编码,如果你的网站涉及到多个国家的语言,那么建议你选择UTF-8编码。
GBK和UTF8有什么区别?
UTF8编码格式很强大,支持所有国家的语言,正是因为它的强大,才会导致它占用的空间大小要比GBK大,对于网站打开速度而言,也是有一定影响的。
GBK编码格式,它的功能少,仅限于中文字符,当然它所占用的空间大小会随着它的功能而减少,打开网页的速度比较快。
本小节中使用的字符集是gbk而不是utf-8可能是网页过于简单的原因。
五、带参数的get方法实现
在上一个json中写也可以,新建一个json文件也可以,新建json文件后需要执行命令
java -jar ./moco-runner-0.11.0-standalone.jar -p 8888 -c startupget.json
重启服务
示例如下:
[
{
"description":"这是一个带参数的get请求",
"request":{
"uri":"/getdemo",
"method":"get",
"queries":{
"name":"qinzhenxia",
"age":"28"
}
},
"response":{
"text":"返回这是一个带参数的get请求",
"headers":{
"Content-Type":"text/html;charset=gbk"
}
}
}
]
在浏览器中访问:
六、模拟不带参数的post请求:
"method":"post"
{
"description":"这是一个post请求",
"request":{
"uri":"/postdemo",
"method":"post"
},
"response":{
"text":"返回这是一个post请求",
"headers":{
"Content-Type":"text/html;charset=gbk"
}
}
}
在jemeter工具中执行post接口:
七:模拟带form参数的post请求:
post请求参数格式是key:value,不能使用queries,要使用forms
{
"description":"这是一个带参数的post请求,请求参数格式是key:value,不能使用queries,要使用forms",
"request":{
"uri":"/postwithparam",
"method":"post",
"forms":{
"name":"qinzhenxia",
"age":"28"
}},
"response":{
"text":"这是一个带表单参数的post请求",
"headers":{
"Content-Type":"text/html;charset=gbk"
}
}
}
在jemeter中请求接口:
八、request请求中带cookies信息的get请求
增加cookies值的设置:
{
"description":"这是一个request请求中带cookies信息的get请求",
"request":{
"uri":"/getwithcookies",
"method":"get",
"cookies":{
"login":"true"
}
},
"response":{
"text":"返回这是一个request请求中带cookies信息的get请求",
"headers":{
"Content-Type":"text/html;charset=gbk"
}
}
}
在jemeter中请求接口时要先设置cookies信息:
cookies值设置:
执行结果:
九、request请求中带cookies的post请求
使用json格式的参数、返回结果也使用json格式
{
"description":"这是一个request请求中带cookies、使用json格式传参、返回结果为json格式的post请求",
"request":{
"uri":"/postwithcookiesandjson",
"method":"post",
"cookies":{
"login":"true"
},
"json":{
"name":"qinzhenxia",
"age":"28"
}
},
"response":{
"status":200,
"json":{
"code":"0",
"msg":"success",
"p2pdata":{
"name":"moguzhixing",
"address":"beisanhuan"
}
}
}
}
使用jemeter调接口,注意要先把cookie加入cookie管理器中:
请求接口的参数要写到bodydata中 是json格式的入参:
执行接口查看返回结果:
response请求中返回中文信息:
{
"description":"这是一个request请求中带cookies、使用json格式传参、返回结果为json格式的post请求,json返回中有中文,json可以解析中文",
"request":{
"uri":"/postwithcookiesandjsontwo",
"method":"post",
"cookies":{
"login":"true"
},
"json":{
"name":"moguzhixing",
"age":"28"
}
},
"response":{
"status":200,
"json":{
"code":"0",
"msg":"success",
"p2pdata":{
"name":"和平西桥",
"address":"北三环中路"
}
}
}
}
在jemeter中把cookies加入cookie管理器中,执行接口
十、模拟response返回中带有cookies信息的get请求
{
"description":"模拟response返回中带有cookies信息的get请求",
"request":{
"uri":"/getcookies"
},
"response":{
"cookies":{
"login":"true",
"token":"1234567890"
},
"json":{
"name":"zhoujielun",
"age":"38"
}
}
}
请求返回结果:
十一、带有headers信息的mock请求
十二、moco有重定向的接口
需要用到"redirectTo":"/path",代码如下:
{
"description":"模拟重定向接口",
"request":{
"uri":"/redirectto"
},
"redirectTo":"/redirectedpath"
},
{
"description":"模拟被重定向的接口",
"request":{
"uri":"/redirectedpath"
},
"response":{
"text":"返回重定向结果",
"headers":{
"Content-Type":"text/html;charset=gbk"
}
}
}
在浏览器中输入http://localhost:8889/redirectto查看结果:
十三、使用httpclient调moco接口
httpclient是模拟客户端发送http请求并获取返回结果的编程工具包,httpclient的使用方法:
使用HttpClient发送请求、接收响应很简单,一般需要如下几步即可。
创建HttpClient对象。
创建请求方法的实例,并指定请求URL。如果需要发送GET请求,创建HttpGet对象;如果需要发送POST请求,创建HttpPost对象。
如果需要发送请求参数,可调用HttpGet、HttpPost共同的setParams(HetpParams params)方法来添加请求参数;对于HttpPost对象而言,也可调用setEntity(HttpEntity entity)方法来设置请求参数。
调用HttpClient对象的execute(HttpUriRequest request)发送请求,该方法返回一个HttpResponse。
调用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可获取服务器的响应头;调用HttpResponse的getEntity()方法可获取HttpEntity对象,该对象包装了服务器的响应内容。程序可通过该对象获取服务器的响应内容。
释放连接。无论执行方法是否成功,都必须释放连接
首先要加入依赖包,因为要用到testng,也加入testng的依赖包
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.9.10</version>
</dependency>
加入依赖包后,写入不带参数的get请求
package com.course.httpclient.demo;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.testng.annotations.Test;
import java.io.IOException;
public class MyHttpClient {
//加入@Test注解
@Test
public void testHttpClient() throws IOException {
//定义httpclient对象
HttpClient httpClient1=new DefaultHttpClient();
//HttpGet类,参数为要执行的url,实际测试中可以为接口地址
HttpGet httpGet=new HttpGet("http://localhost:8889/redirectto");
//用httpclient对象执行httpGet,并把返回结果存到httpresponse中
HttpResponse httpResponse=httpClient1.execute(httpGet);
//httpResponse的结果要调getEntity方法接收,同时需要转换成String类型才可以打印出来。
String result= EntityUtils.toString(httpResponse.getEntity());
System.out.println(result);
((DefaultHttpClient) httpClient1).close();
}
}
运行后查看结果:
十四、模拟从上一个接口中获取cookie,供下一个接口入参使用
moco的返回cookie的get接口代码如下:
{
"description":"模拟返回接口中有一个cookies,该cookie被用到下一个接口中的情况",
"request":{
"uri":"/response/cookies",
"method":"get"
},
"response":{
"cookies":{
"token":"1234567890"
},
"json":{
"name":"kunling",
"age":25
}
}
}
moco的携带cookie的post接口如下:
cookies的内容必须跟上一个接口的cookies内容一致才行。
{
"description":"这是一个request请求中带cookies、使用json格式传参、返回结果为json格式的post请求",
"request":{
"uri":"/post/with/cookies/usejson",
"method":"post",
"cookies":{
"token":"1234567890"
},
"headers":{
"Content-Type":"application/json;charset=gbk"
},
"json":{
"name":"qinzhenxia",
"age":"28"
}
},
"response":{
"status":200,
"json":{
"code":"0",
"msg":"success",
"p2pdata":{
"name":"moguzhixing",
"address":"北三环"
}
}
}
}
httpclient调接口代码如下:
package com.course.httpclient.cookies;
import com.sun.xml.internal.ws.api.streaming.XMLStreamReaderFactory;
import org.apache.http.HttpResponse;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.json.JSONObject;
import org.testng.Assert;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
public class MyCookiesForPost {
//定义url存储请求地址
private String url;
//定义resourcebundle 从配置文件中获取配置新
private ResourceBundle resourceBundle;
//设置cookiestore全局变量
private CookieStore cookieStore;
@BeforeTest
public void BeforeTest(){
//调用getBundle方法,告诉resourceBundle从哪个配置文件中获取配置信息,定义Locale.CHINA是中文信息
resourceBundle=ResourceBundle.getBundle("application", Locale.CHINA);
//传入test.url配置名称,获取配置信息并赋值给url
url=resourceBundle.getString("test.url");
}
/*
模拟httpclient请求,并返回结果
*/
@Test(enabled = false)
public void testGetCookies() throws IOException {
//拼接url
String uri=url+resourceBundle.getString("test.responsecookies");
//httpclient请求接口获取返回结果并打印
HttpClient httpClient=new DefaultHttpClient();
HttpGet httpGet=new HttpGet(uri);
HttpResponse response=httpClient.execute(httpGet);
String result= EntityUtils.toString(response.getEntity());
System.out.println(result);
((DefaultHttpClient) httpClient).close();
}
/*
模拟获取接口中返回的cookie,本接口返回的cookie供下一个接口使用。
*/
@Test
public void testGetAndResponseCookie() throws IOException {
//拼接url
String uri=url+resourceBundle.getString("test.responsecookies");
//使用DefaultHttpClient类生成对象,不再使用HttpClient,这样才能获取到cookie
DefaultHttpClient defaultHttpClient=new DefaultHttpClient();
//使用getCookieStore()方法获取cookies信息,并赋值给cookieStore 全局变量
cookieStore=defaultHttpClient.getCookieStore();
//httpget请求
HttpGet httpGet=new HttpGet(uri);
HttpResponse httpResponse=defaultHttpClient.execute(httpGet);
String result=EntityUtils.toString(httpResponse.getEntity());
System.out.println(result);
//获取cookies信息并打印出来
List<Cookie> cookieStoreList=cookieStore.getCookies();
//使用foreach循环返回cookie的name和value信息
for(Cookie cookie : cookieStoreList){
String name=cookie.getName();
String value=cookie.getValue();
System.out.println("cookiename:"+name+",cookievalue:"+value);
}
defaultHttpClient.close();
}
/*
从上一个接口中获取cookie信息作为本接口的cookie
*/
//使用dependsOnMethods = {"testGetAndResponseCookie"}参数,依赖testGetAndResponseCookie方法,也就是上一个方法返回的cookie信息
@Test(dependsOnMethods = {"testGetAndResponseCookie"})
public void testpostmethod() throws IOException {
String uri=url+resourceBundle.getString("test.postwithcookiesusejson");
//生成httpclient对象
DefaultHttpClient defaultHttpClient=new DefaultHttpClient();
//创建post请求
HttpPost httpPost=new HttpPost(uri);
//使用json入参,入参是中文时不行。
JSONObject param=new JSONObject();
param.put("name","qinzhenxia");
param.put("age","28");
//设置请求头header
httpPost.setHeader("Content-Type","application/json;charset=gbk");
//传入参数
StringEntity entity=new StringEntity(param.toString(),"gbk");
httpPost.setEntity(entity);
//设置cookie是用defaultHttpClient调setCookieStore方法,不是用httppost调
defaultHttpClient.setCookieStore(this.cookieStore);
//执行请求并接收返回结果
HttpResponse httpResponse = defaultHttpClient.execute(httpPost);
String result=EntityUtils.toString(httpResponse.getEntity());
System.out.println(result);
//判断返回code值
int statuscode=httpResponse.getStatusLine().getStatusCode();
Assert.assertEquals(statuscode,200);
//获取返回结果并转换为jsonobject格式,然后获取某个具体的值通过断言判断
JSONObject resultjson=new JSONObject(result);
String msg =resultjson.getString("msg");
//判断msg等于success
Assert.assertEquals(msg,"success");
JSONObject p2pdata=resultjson.getJSONObject("p2pdata");
String address=p2pdata.getString("address");
System.out.println(address);
Assert.assertEquals(address,"北三环");
}
}