标签: jdists 模拟接口 单元测试
应用的功能模块之间,离不开接口的设计和实现,接口通常涉及:
本文介绍一种模拟接口的简单方案
就是伪造接口的功能,忽略实现细节,模拟调用过程和结果。
项目中两个需对接的功能模块,开发周期和复杂度并不会一样。有的模块开发几小时就完了,而一些模块要写好几周。不同的功能模块需要的运行环境也有差异,一些需要数据库,一些需要运行在微信环境,一些需要硬件支持。
模拟接口对于开发期的收益显而易见:
异步开发接口开发过程不用等到另一端开发完毕
用例覆盖可以预设输出结果,覆盖各种极端用例
降低测试成本减少手动操作步骤、减少搭建环境的时间和物资
通常需要借助测试框架和模拟环境(如: jest 、 phantomjs )
来源:tutorial-jquery.html
业务代码 fetchCurrentUser.js
function fetchCurrentUser(callback) { return $.ajax({ type: 'GET', url: 'http://example.com/currentUser', done: user => callback(parseJSON(user)), }); }
测试代码 displayUser-test.js
jest .dontMock('../displayUser.js') .dontMock('jquery'); describe('displayUser', function() { it('displays a user after a click', function() { // ... var $ = require('jquery'); var fetchCurrentUser = require('../fetchCurrentUser'); // Tell the fetchCurrentUser mock function to automatically invoke // its callback with some data fetchCurrentUser.mockImplementation(function(cb) { cb({ loggedIn: true, fullName: 'Johnny Cash' }); }); // ... }); });
这个用例中 fetchCurrentUser.mockImplementation()
执行后,原函数就被劫持,执行回调结果:
{ loggedIn: true, fullName: 'Johnny Cash' }
当然这样就不用等待 http://example.com/currentUser
接口实现
这是成本相对较高的方法,将对方的接口做一次简单实现,返回固定的模拟数据。如果对接方能提供是最理想的。
无论采用什么方案,都得写一段模拟接口的代码,为何不写在距离接口调用最近的地方?
业务代码调用接口的地方就是最近的地方。
这就是本文提出的方案:在业务代码中注入模拟接口代码。
比如 jest
中 jQuery 的用例,修改成这样:
开发期
function fetchCurrentUser(callback) { ////////////// callback({ loggedIn: true, fullName: 'Johnny Cash' }); return; ////////////// return $.ajax({ type: 'GET', url: 'http://example.com/currentUser', done: user => callback(parseJSON(user)), }); }
生产环境
function fetchCurrentUser(callback) { return $.ajax({ type: 'GET', url: 'http://example.com/currentUser', done: user => callback(parseJSON(user)), }); }
在上线的时候把这段模拟接口的代码移除。
这种简单的模拟接口方式为何不常见?
我想可能是在构建工具没有普及的时代,手动增删不够方便。
利用构建工具这个过程就可以变成自动的。
只需要做一下标记即可。
/*<remove>*/ ... 业务代码 ... /*</remove>*/
我推荐的是 jdists
使用的方法:「多行注释 + XML」。
现在构建工具已经很普及 Gulp、Grunt、FIS, jdists
提供这些构建工具的插件,可以方便的引入。
当然也可以简单的用 replace
写个正则,替换一下。
var user = $('.userid').val(); $.getJSON('/user/info/' + user, function (reply) { if (reply.status == 'ok') { $('.nickname').text(reply.data.nickname); } });
var user = $('.userid').val(); /*<remove>*/ $.oldGetJSON = $.getJSON; $.getJSON = function (url, callback) { // 接管接口功能 console.log('url: %s', url); callback({ status: 'ok', data: { id: 4455, nickname: 'zswang' } }); }); /*</remove>*/ $.getJSON('/user/info/' + user, function (reply) { if (reply.status == 'ok') { $('.nickname').text(reply.data.nickname); } }); /*<remove>*/ $.getJSON = $.oldGetJSON; // 恢复接口功能 /*</remove>*/
/*<remove>*/ JavaScriptBridge.oldInvoke = JavaScriptBridge.invoke; JavaScriptBridge.invoke = function (action, query, callback) { console.log('invoke() action: %s, query: %s', action, JSON.stringify(query)); callback({ status: 'ok' }); }; /*</remove>*/ try { JavaScriptBridge.invoke("wechat_share", { title: document.title, icon: $('img').attr('src') }, function(reply) { if (reply.status === 'ok') { alert('分享成功'); } } ); } catch(ex) {} /*<remove>*/ JavaScriptBridge.invoke = JavaScriptBridge.oldInvoke; /*</remove>*/
/*<remove>*/ wx.ready = function(callback) { callback(); }; /*</remove>*/ wx.ready(function() { /*<remove>*/ wx.onMenuShareTimeline = function (argv) { console.log(JSON.stringify(argv)); if (Math.random() < 0.5) { argv.success(); } else { argv.cancel(); } }; /*</remove>*/ // 获取“分享到朋友圈”按钮点击状态及自定义分享内容接口 wx.onMenuShareTimeline({ title: document.title, // 分享标题 link: document.location, // 分享链接 imgUrl: $('.icon').attr('src'), // 分享图标 success: function () { // 用户确认分享后执行的回调函数 $('.info').text('分享成功'); }, cancel: function () { // 用户取消分享后执行的回调函数 $('.info').text('分享识别'); } }); });
jdists
提供了触发器,可以选择哪些情况需要移除。
<?php /*<remove trigger="local">*/ $db = new mysqli( Config::mysql_database_host, Config::mysql_database_user, Config::mysql_database_pass, Config::mysql_database_db, Config::mysql_database_port ); $owner_id = 4455; $res = $db->query(" SELECT `id`, `nickname`, `city` FROM `visits` WHERE `owner_id` = $owner_id ORDER BY `id` DESC "); $db->close(); $visits = array(); if ($res) { while ($row = $res->fetch_object()) { $visits[] = $row; } } /*</remove>*/ /*<remove trigger="@trigger != 'local'">*/ $visits = json_decode('[ {"id": 4451, "nickname": "zswang", "city": "beijing"}, {"id": 4452, "nickname": "techird", "city": "guangzhou"} ]'); /*</remove>*/ var_dump($visits); ?>