系统测试利器之挡板实战

发表于:2019-5-31 08:21

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:一盘花生米    来源:51CTO

   shellTransform.js文件如下:
   // 使用 Mock
  var Mock = require('mockjs')
  var request = JSON.parse(process.argv[2]),
  response = JSON.parse(process.argv[3]);
  response.body = response.body.replace('${city}', Mock.mock('@city'));
  console.log(JSON.stringify(response));
  那么通过http://localhost:8086访问后的结果如下:
 Hello, 厦门市!
  那么我们可以联想下,能够shellTransform.js使用外部数据源,解耦性更强了,只要外部输出json数据到控制台就可以拿到数据了。 shellTransform.js本身可以是nodejs,那么连接数据库、nosql都是分钟级的事情了,有包含上下文的业务场景挡板不就直接搞定了。
  至此mountebank的使用已经讲解完了,有不明白的可以和我联系(微信号dcs678),加之前请备注mountebank或mb,否则拒绝通过。 接着看后面的实战干活哈,go on!
  mockjs简介
  功能介绍
   |主要功能   |   功能描述|
  |-- |-- |
  |前后端分离                      |让前端攻城师独立于后端进行开发。                                                                       |
  |增加单元测试的真实性 |通过随机数据,模拟各种场景。                                                                             |
  |开发无侵入                      |不需要修改既有代码,就可以拦截 Ajax 请求,返回模拟的响应数据。         |
  |用法简单                           |符合直觉的接口。                                                                                                       |
  |**数据类型丰富**                 |**支持生成随机的文本、数字、布尔值、日期、邮箱、链接、图片、颜色等。**  |
  |方便扩展                           |支持支持扩展更多数据类型,支持自定义函数和正则。                        
   其实人家也是可以直接mock的,根据名字也能看出,但我们可以使用他的丰富数据类型,说白了就是随机可以动态造数据,有兴趣同学可以研究下他们的功能。
  常用功能实现
  别忘了npm install mockjs
  这里好多示例!
    // 声明变量
  var Mock = require('mockjs')
  
  //生成10个星
  Mock.mock({
  "string|1-10": "★"
  })
  // => { "string": "★★★★★★★★"}
 
  //随机生成邮件地址
  var Random = Mock.Random
  Random.email()
  // => "n.clark@miller.io"
  Mock.mock('@email')
  // => "y.lee@lewis.org"
  Mock.mock( { email: '@email' } )
  // => { email: "v.lewis@hall.gov" }
 
  //生成随机数字
  Mock.mock({
  "number|+1": 202
  })
  // =>{"number": 201}
  
  // 生成uid
  Random.guid()
  Mock.mock('@guid')
  Mock.mock('@guid()')
 
  // =>{"number": 201}"6CeEb1D9-a54F-b90b-EA28-fD271E6eAe01"
  // =>{"number": 201}"7f11ef3A-d270-BafE-289f-f6B4DFCbD94f"
  // =>{"number": 201}"eCfD7F48-d8dE-9caB-cE77-bE5c6b16Fbd4"
  挡板实战
  上面也算是详细介绍了mountebank、mockjs的用法,那么接下来介绍下我的挡板实现(结合上面的第三张图)。
  本次讲解的的挡板demo目录为baffle,他的目录结构如下:
   |路径 |类型 |备注 |
  |-- |-- |-- |
  |baffle/logs    |目录 |日志文件目录,存放业务日志和定时任务日志   |
  |baffle/quartz  |目录 |存放定时任务,为了实现挡板的回调功能包含log.js(quartz专用)、testquartz.js |
  |baffle/test    |目录 |存放挡板服务实现   |
  |baffle/utils   |目录 |常用工具类,数据查询、日志输出等,包含db.js、log.js、utils.js   |
  |baffle/imposters.ejs   |文件 |启动参数   文件|
  |baffle/start.sh    |文件 |linux启动脚本,方便服务启动   |
  |baffle/stop.sh |文件 |linux停止脚本,方便服务停止   |
  |baffle/startMac.sh |文件|mac启动脚本,里面包含停止脚本|
  挡板回调
  利用定时任务实现挡板回调 log.js代码如下:
  /**
  * 日志打印工具类
  */
  var log4js = require('log4js');
  log4js.configure({
  appenders: {
  file: {
  type: "dateFile",
  filename: './logs/quartz.log', //您要写入日志文件的路径
  alwaysIncludePattern: true, //(默认为false) - 将模式包含在当前日志文件的名称以及备份中
  //compress: true,//(默认为false) - 在滚动期间压缩备份文件(备份文件将具有.gz扩展名)
  pattern: "-yyyy-MM-dd.log", //(可选,默认为.yyyy-MM-dd) - 用于确定何时滚动日志的模式。格式:.yyyy-MM-dd-hh:mm:ss.log
  encoding: 'utf-8', //default "utf-8",文件的编码
  maxLogSize: 10 //文件最大存储空间,当文件内容超过文件存储空间会自动生成一个文件xxx.log.1的序列自增长的文件
  }
  },
  categories: {
  default: {
  appenders: ['file'],
  level: 'info'
  }
  }
  });
  
  function info(obj) {
  logger.info(obj);
  }
  var logger = log4js.getLogger('log_file');
  module.exports = {
  info
  }
   testquartz.js文件如下:
   var logger = require('./log');
  const cron = require('cron');
  var db = require('../utils/db');
  var utils = require('../utils/utils');
  /**
  * 定时任务
  */
  // https://www.npmjs.com/package/cron
  const CronJob = cron.CronJob;
  
  // CREATE TABLE `prduct` (
  //   `id` int(255) NOT NULL AUTO_INCREMENT,
  //   `context` varchar(1000) NOT NULL,
  //   `flag` smallint(255) NOT NULL DEFAULT '0',
  //   PRIMARY KEY (`id`)
  // ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
  //
  const prductParam = {
  // cronTime: '*/10 * * * * *',
  //
  cronTime: '0 */5 * * * *',
  onTick: function() {
  db.query('SELECT * from prduct where flag=0', [], function(ret){
  if(ret.length>0){
  for(var row in ret){
  var formData=ret[row].context;
  var parm=[];
  parm.push(ret[row].id);
  var callRet = utils.call(utils.testUrl.prod.url, formData, function(results) {
  logger.info("callRet=" + JSON.stringify(results));
  // console.log(results);
  db.query("update prduct set flag='1' where flag=0 and id=?", parm, function(err, retu){
  logger.info("产品状态变更完成:"+parm);
  console.log(err);
  });
  });
  }
  }else{
  logger.info("没有对应的产品");
  console.log("没有对应的产品");
  }
  });
  }
  };
  // 产品定时推送接口
  const prductJob = new CronJob(prductParam);
  prductJob.start();
  utils工具类
  db.js代码如下:
  var logger = require('./log');
  var mysql = require('mysql');

  /**
  * @param {Object} sql
  * @param {Object} arr
  * @param {Object} callback
  * 执行sql
  */
  exports.query = function(sql, arr, callback) {
  var connection=getConnection();
  //查
  connection.query(sql,arr, function(err, result) {
  if (err) {
  logger.info('[SELECT ERROR] - ', err.message);
  return;
  }
  
  callback && callback(result);
  });
  connection.end();
  };
  /**
  * 获取连接
  */
  function getConnection(){
  var connection = mysql.createConnection({
  host: '127.0.0.1',
  user: 'root',
  password: '123456',
  database: 'test_mock'
  });
  connection.connect();
  return connection;
  }
  log.js代码如下:
   /**
  * 日志打印工具类
  */
  var log4js = require('log4js');
  log4js.configure({
  appenders: {
  file: {
  type: "dateFile",
  filename: './logs/mock.log', //您要写入日志文件的路径
  alwaysIncludePattern: true, //(默认为false) - 将模式包含在当前日志文件的名称以及备份中
  //compress: true,//(默认为false) - 在滚动期间压缩备份文件(备份文件将具有.gz扩展名)
  pattern: "-yyyy-MM-dd.log", //(可选,默认为.yyyy-MM-dd) - 用于确定何时滚动日志的模式。格式:.yyyy-MM-dd-hh:mm:ss.log
  encoding: 'utf-8', //default "utf-8",文件的编码
  maxLogSize: 10 //文件最大存储空间,当文件内容超过文件存储空间会自动生成一个文件xxx.log.1的序列自增长的文件
  }
  },
  categories: {
  default: {
  appenders: ['file'],
  level: 'info'
  }
  }
  });
  
  function info(obj) {
  logger.info(obj);
  }
  var logger = log4js.getLogger('log_file');
  module.exports = {
  info
  }
  utils.js代码如下:
  var logger = require('./log');
  var moment = require('moment');
  moment.locale('zh-cn');
  

  function exc(process) {
  var request = JSON.parse(process.argv[2]),
  response = JSON.parse(process.argv[3]);
  var method = request.path.slice(1).replace(new RegExp("/","gm"), "_");
  var excutor = method + '(request,response)';
  logger.info(excutor);
  return excutor;
  }
  
  function httpPost(url, formData,callback) {
  var superagent = require('superagent');
  superagent
  .post(url)
  .send(formData)
  .set('header_key', 'header_value')
  .set('Content-Type', 'application/json')
  .end(function(err, res) {
  if (err) {
  logger.info(err);
  callback && callback("");
  // return "";
  } else {
  var retData = JSON.parse(res.text)
  callback && callback(retData);
  // return retData
  }
  })
  }
  
  /**
  * 默认的函数
  * @param {Object} request
  * @param {Object} response
  */
  function defaults(request, response) {
  console.log(JSON.stringify(response));
  var reqData = request.body;
  var reqJson = JSON.parse(reqData);
  logger.info(reqJson);
  }
  /**
  * 回调地址
  */
  var testUrl = {
  prod: {
  url: '/openapi/prodList',
  description: '产品列表'
  }
  };
  
  function getUuid() {
  // 声明变量
  var Mock = require('mockjs');
  return Mock.mock('@guid');
  }
  
  function getDateYYYYMMDD() {
  return moment().format('YYYY-MM-DD'); /*现在的时间*/
  }
  
  function getDateYYYYMMDDHHMMSS() {
  return moment().format('YYYY-MM-DD HH:mm:ss'); /*格式化时间*/
  }
  
  function formatDate(val) {
  return moment(val).format('YYYY-MM-DD HH:mm:ss'); /*格式化时间*/
  }
  
  function subDay(val) {
  var _today = moment();
  return _today.subtract(val, 'days').format('YYYY-MM-DD'); /*前一天的时间*/
  }
  
  function addDay(val) {
  var _today = moment();
  return _today.add(val, 'days').format('YYYY-MM-DD'); /*明天天的时间*/
  }
  
  /**
  * @param {Object} url
  * @param {Object} formdata
  * 调用其他项目的接口
  */
  function call(url, formdata, callback) {
  logger.info(url);
  logger.info(formdata);
  var postUrl = "http://localhost:8082" + url;
  logger.info("postUrl=" + postUrl);
  httpPost(postUrl, formdata, callback);
  }
  
  module.exports = {
  exc,
  httpPost,
  defaults,
  testUrl,
  getUuid,
  getDateYYYYMMDD,
  getDateYYYYMMDDHHMMSS,
  formatDate,
  subDay,
  addDay,
  call
  }
   挡板实现
  存放挡板服务实现,请参照上面讲的案例或到github拉取。
  imposters.ejs配置文件
   {
  "imposters": [
  <% include ./test/predicates_inject.json %>,
  <% include ./test/predicates.json %>,
  <% include ./test/proxy.json %>,
  <% include ./test/mockjs.json %>,
  <% include ./test/responses_inject.json %>,
  <% include ./test/_behaviors.json %>,
  <% include ./test/shellTransform.json %>
  ]
  }
  start.sh linux启动脚本
  利用node的npm start也是可以的,这里自己写的目的是把定时任务也包含在里面。
   #停止服务
  mb stop
  #---------------------------定时任务 start-----------------------------
  #先停止进程
  ps -ef|grep testquartz |grep -v grep|cut -c 9-15|xargs kill -9
  #再启动进程
  node quartz/testquartz.js &
  #---------------------------定时任务 end-----------------------------
  #启动服务
  mb start --configfile imposters.ejs  --allowInjection >test.out 2>&1 &
   stop.sh linux停止脚本
   mb stop
  #---------------------------定时任务 start-----------------------------
  ps -ef|grep testquartz |grep -v grep|cut -c 9-15|xargs kill -9
  #---------------------------定时任务 end-----------------------------
  netstat -antp|grep 2525 |grep -v grep|cut -c 80-85|xargs kill -9
  DEMO代码
  完整的代码demo已经传到Git,在这里:https://github.com/dcs678/baffle
  总结
  本文主要是先从当前的微服务架构说起,调用外围系统给开发、测试、演示的痛点,给出挡板的架构规划,然后利用mountebank的强大功能,通过脚本+实战的方式一步步详细说明。

     上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
22/2<12
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号