系统测试利器之挡板实战(七)

挡板实战

上面也算是详细介绍了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,在这里。

总结

本文主要是先从当前的微服务架构说起,调用外围系统给开发、测试、演示的痛点,给出挡板的架构规划,然后利用mountebank的强大功能,通过脚本+实战的方式一步步详细说明。

不知读到最后的您有没收获? 用mountebank来做挡板其实很简单,但必须多实践才能更好的掌握其精髓,更好的进行适合自己业务的扩展。

系统测试利器之挡板实战(一)
系统测试利器之挡板实战(二)
系统测试利器之挡板实战(三)
系统测试利器之挡板实战(四)
系统测试利器之挡板实战(五)
系统测试利器之挡板实战(六)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容

  • feisky云计算、虚拟化与Linux技术笔记posts - 1014, comments - 298, trac...
    不排版阅读 3,841评论 0 5
  • 第 2 章 SHELL 基础知识2.1 shell脚本我们在上面简单介绍了一下什么是shell脚本,现在我们来进一...
    LiWei_9e4b阅读 1,566评论 0 0
  • mean to add the formatted="false" attribute?.[ 46% 47325/...
    ProZoom阅读 2,694评论 0 3
  • 很多人都只不过是打着热爱智慧、追求精神世界的旗号合理化自己没有什么赚钱能力的事实 所以为什么文艺青年、艺术家与金钱...
    无限游戏指引阅读 347评论 0 0
  • 生下孩子以后,你会发现自己一天到晚忙个不停、睡觉也不安稳,甚至会因为过度疲劳导致“智商下线”,做出各种令自己尴尬的...
    铃铛儿童时尚阅读 197评论 0 0