STF初步定制测试平台

本帖已被设为精华帖!,

一个月前看了monkey大神的stf修改帖子后心生想法, 能不能根据STF定制一个符合我司测试需求的平台, 在上面跑一些自动化测试以及查看结果.

怀着这个想法, 啃了一个月的源码. 从最初的摸不着头脑到现在的略有领悟, 终于改造了一个简单的测试平台.

把经验和看法分享给大家. 第一次发帖, 有不足的地方还望大家指正,能多多交流


准备工作

关于STF环境搭建和运行我就不多说了, 可参考帖子:

https://testerhome.com/topics/2988

在这简单介绍下stf的结构,因为我刚开始接触就很摸不着头脑, 希望能对刚接触STF的人一些帮助吧

STF代码主要有两个目录,lib和res. lib里放的是后端的代码, res里放的是前端的代码.

STF启动运行命令是 stf local, 实际上是 node lib/cli.js local.
也就是说lib下cli.js是stf后台的入口,local是传入的参数. cli.js中启动了一些后端的服务

以下是启动app服务代码:

// app
, procutil.fork(__filename, [
'app'
, '--port', options.appPort
, '--secret', options.authSecret
, '--auth-url', options.authUrl || util.format(
'http://%s:%d/auth/%s/'

启动方式就是调用了procutil的fork(),fork里是启动了一个child_process,在这就不细说了.


界面改造

首先我想在页面最上方增加一个test的标签, 在res/app/menu/menu.jade里添加:

a(ng-href='/#!/devices', accesskey='1')
span.fa.fa-sitemap
span(ng-if='!$root.basicMode', translate) Devices
a(ng-href='/#!/apptest')
span.fa.fa-bug
span(ng-if='!$root.basicMode', translate) Test

增加了一个和Devices一样的标签, 修改了其中的图标为fa-bug, 其实STF大多数的icon都是借用的font awesome里的icon, 传送门:

http://fontawesome.io/

在res/app目录下新建apptest文件夹,
STF初步定制测试平台

在index中配置route:

.config(function($routeProvider) {
$routeProvider.when('/apptest', {
template: require('./app-test.jade')
})
})

标签添加完成,在test标签下, 我想做两个子页面, 一个用于配置测试的, 一个展示测试状态列表,在app-test.jade中添加

div(fa-pane,pane-id='test-tabs')
.widget-container.fluid-height
nice-tabs(key='ControlBottomTabs', tabs='appTestTabs', filter='')
div(fa-pane, pane-anchor='south', pane-size='50%', pane-handle='4').pane-bottom-p
.widget-container.fluid-height
status-Directive(tests='tests',columns='columns')

为配置测试页增加crawl和smoke两个tab:

$scope.appTestTabs = [
{
title: gettext('Crawl'),
icon: 'fa-refresh fa-fw',
templateUrl: 'apptest/crawl/crawl.jade'
},
{
title: gettext('Smoke'),
icon: 'fa-rss fa-fw',
}
]

效果如下图:
STF初步定制测试平台

statusDirective是状态列表的directive, 之所以放在app-test.jade里,是想切换tab时,状态列表始终展示.

在directive中可以进行适时的数据更新,该处是仿照device列表写的,

function createRow(test) {
var id = test.id
var tr = document.createElement('tr')
var td

tr.id = id

for (var i = 0, l = columns.length; i < l; ++i) {
td = scope.testCell[columns[i].name].build()
scope.testCell[columns[i].name].update(td, test)
tr.appendChild(td)
}
return tr
}

function updateRow(test)
{
var tr = tbody.children[test.id]
if (tr) {
for (var i = 0, l = columns.length; i < l; ++i) {
scope.testCell[columns[i].name].update(tr.cells[i], test)
}
return tr
}

效果如下图:

STF初步定制测试平台


数据存储

STF使用的数据库是rethinkdb, rethinkdb是以json方式存储数据的. 先在lib/db/tables.js中新建表tests 主键为id:

tests: {
primaryKey: 'id'
}

在api.js中定义操作数据库的方法

dbapi.saveTests = function(tests)
{
var data = {
testType: tests.testType
, startTime: tests.startTime
, endTime: tests.endTime
, owner: tests.owner
, serial: tests.serial
, status: tests.status
, result: tests.result
, device: tests.device
}
return db.run(r.table('tests').get(tests.id).update(data))
.then(function(stats) {
if (stats.skipped) {
data.id = tests.id
data.createdAt = r.now()
return db.run(r.table('tests').insert(data))
}
return stats
})
}

dbapi.loadTest = function(id)
{
return db.run(r.table('tests').get(id))
}

dbapi.loadTests = function()
{
return db.run(r.table('tests'))
}


前后端通信

有了界面, 要在后端执行我的遍历测试并返回结果,需要前后端通信.首先前端触发开始测试操作:

$scope.runTest = function()
{
var mydevices = $scope.devices
for (var i = 0; i < mydevices.length; i++) {
var mydevice = mydevices[i]
console.log(mydevice.model);
if(mydevice.selected)
{
mydevice.testing = true;
mydevice.tester = user;
$scope.control = ControlService.create(mydevice, mydevice.channel)
GroupService.invite(mydevice)
$scope.control.runCrawler(mydevice)
}
};
}

在这我给device新加了两个字段:测试状态testing和测试人员tester, 因为在测试中的设备,我不想让它展示出来. tester则在列表中展示

触发测试指令是用websocket来传达, 在control-service中添加

this.runCrawler = function(device)
{
return sendOneWay('runCrawler',{
device: device
})
}
this.stopTest = function(device,test)
{
return sendOneWay('stopTest',{
device: device
})
}

sendOneWay里是向所有client发送action,然后我们在后台中监听这个action 在lib/units/websocket/index.js中添加:

.on('runCrawler', function(channel, data) {
controller.runTest(io,data.device);
})
.on('stopTest', function(channel, data) {
controller.stopTest(socket,data.serial);
})

controller中是我们具体执行测试的代码:

this.runTest = function(socket,device){
var myDevice = {};

var test = createTest(device);

crawler = spawn('java', ['-jar','monkeytest/monkeytest.jar','-d','android','-p',
'com.android.text','-a','MainActivity','-s',device.serial,'-i',test.id]);
console.log(device.serial);

crawler.serial = device.serial
crawler.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});

crawler.stderr.on('data', (data) => {
console.log(`stderr: ${data}`);
});

crawler.on('close', (code) => {
console.log(`child process exited with code ${code}`);
if(code == 0)
{
test.endTime = getNowFormatDate();
test.status = 'Finish';
dbapi.saveTests(test);
dbapi.setDeviceTestStatus(crawler.serial,test.status)
triggerTestComplete(socket,test);
}
else
{
test.endTime = getNowFormatDate();
test.status = 'Stop';
dbapi.saveTests(test);
dbapi.setDeviceTestStatus(crawler.serial,test.status)
triggerStopTest(socket,test);
}
});
var serial = device.serial;
deviceList[serial] = crawler;
dbapi.saveTests(test);
dbapi.setDeviceTestStatus(device.serial,test.status)
triggerStartTest(socket,test);
}
this.stopTest = function(socket,serial)
{
deviceList[serial].kill();
}

由于执行的是我本地上的遍历工具, 所以用了child_process, 在close时对exitcode进行判断,来分辨测试是正常执行完或中断. 然后对数据库操作后trigger前端更新状态

function triggerStartTest(socket,test)
{
console.log('triggerStartTest');
socket.emit('test.start',test);
}

在这也是用的socket, 就不细述了.

效果图:

STF初步定制测试平台


总结一下吧 STF二次开发还是有些难度的, 像我这种只会java的刚开始看想死的心都有了.

踏遍了无数的坑, 辛酸只有自己知道. 不过风雨之后见彩虹, 看到有些成果心里也满足了.

希望有改造STF想法的同学也不要气馁,努力必有收获!

* 注:本文来自网络投稿,不代表本站立场,如若侵犯版权,请及时知会删除