前端项目改善-泛谈

首先需要申明的是,本人对javascript了解的不多,所以贻笑大方之处请见谅

我目前所负责的一个web项目其中前端部分所占比例较大,项目搭建于jsonp通讯(博客园中有一些介绍jsonp的相关帖子)基础之上,其特点很明显:

前端界面全部用js动态生成,且结构较复杂; 界面的生成需要服务器参与(异步回调中执行相关js函数以更新界面)

因此直接导致的结果便是开发后台服务器的同事需要跟前台同事纠结在一起,联调效率异常低下,而且每次版本升级之后都需要将以前的过程重新来过一次

痛定思痛,在对项目目前的处境做过评估和考量之后,我认为过程可以这样改进:

在前端代码中引入单元测试机制,将所有代码纳入自动化单元测试框架之中

如何重构

随之而来的问题很明显:

  1. 代码如何合理的组织才能方便进行单元测试?
  2. 界面如何单元测试?

首先,代码如何组织其实很简单,MVC结构很好的解决了这个问题,与界面无关的代码提取到M和C中即可,M和C是可以方便进行单元测试的

说到界面如何测试,可能有的人会想到Selenium这类界面流程录制工具,这类工具不能说不牛叉,但对于单元测试来说有个最大的缺憾:无法自动化,自动集成部署完全无法做

对于界面的自动化测试,我是这样考虑的:

所有web界面的更新,无非是在指定的区域插入或删除指定的html结构片段

基于这点考虑,我定义了一个衔接于MC和V的UI解析层次,并制定了几个基本的规则,例如要在一个div中插入新的innerHtml,可以这样定义:

{‘action’: ‘insert’, ‘target’: ‘div_id’, ‘src’: ‘…’}

在M和C做完代码处理之后,如果需要更新界面,将动态生成的html片段(可能以模板技术动态生成)以这种格式返回即可

那么,有了这个UI解析层的加入,针对界面的单元测试便可轻松搞定了,对于具体的需求,只需要比较对应的界面定义返回值是否满足期望即可,完全无需真正的web界面参与

如何单元测试

在js的单元测试框架的选型上,起先我选择了QUnit(http://docs.jquery.com/QUnit),其用法很简单,界面很赞,而且也基本上没有任何侵入性,但有2点比较伤不起:

  1. 无法自动化,测试结果只能在web页面中查看
  2. 无法输出相应报表 //js本身安全限制

因此我考虑是否有其他语言能解释运行js,并且自身有成熟的单元测试框架,当然这种要求是完全可以满足的:py spidermonkey(http://code.google.com/p/python-spidermonkey/),这是一个针对python的开源js解释运行库,和当前最新版的ff4内核完全一致,单元测试有了python的参与,自然如虎添翼,以下代码便是一个示例:

utbase.py

import unittest
import spidermonkey
from os import path
import json
__author__ = ‘Michael’

class ClientUTBase(unittest.TestCase):
    def initialize(self):
        self.__cx = spidermonkey.Runtime().new_context()
        self.__cx.add_global('loadfile', self.__loadfile)
        
        
        
    def __loadfile(self, fn):
        with open(fn, 'r') as f:
            return f.read()
        
            
        
    def readyJsFile(self, jsfilepath):
        '''
        jsfilepath: javascript filepath
        '''
        if not jsfilepath:
            raise IOError('jsfilepath is invalid')
        if not path.isfile(jsfilepath):
            raise IOError('jsfilepath %s not existed' % jsfilepath)
        param = 'eval(loadfile("%s"))' % jsfilepath
        self.__cx.execute(param)
        
        
        
    def runJs(self, jsFuncName, *params):
        encode = "%s(%s)" % (jsFuncName, ', '.join(map(lambda p: json.dumps(p), params)))
        return self.__cx.execute(encode)
        
    

demo_test.py

#coding:utf-8

from utbase import ClientUTBase
import unittest

__author__ = 'Michael'

class XXXTestCase(ClientUTBase):
    '''
    XXX: 对应业务的名称
    '''
    def setUp(self):
        self.initialize() #must initialize ClientUTBase object at first
        jsfn = '1.js'
        self.readyJsFile(jsfn) #load js file
        

    def tearDown(self):
        pass

    
    def test_getnull(self):
        jsFuncName = 'getNULL'
        rt = self.runJs(jsFuncName) #invoke js function, get return value
        print rt #rt == None here
        self.assertTrue(not rt)
        
        
    def test_getName(self):
        jsFuncName = 'getName'
        rt = self.runJs(jsFuncName, 'hello')
        self.assertNotEqual(rt, 'world')
        
        
    def test_nofuncDef(self):
        jsFuncName = 'getXXX' #no such function in js file
        self.assertTrue(self.runJs(jsFuncName))
        
        
    def test_getComplexObject(self):
        obj = {'name' : 'mic'}
        jsFuncName = 'getComplexObject'
        rt = self.runJs(jsFuncName, obj)
        self.assertEqual(rt, obj) #oh yeah
       
    
    

if __name__ == "__main__":
    suite = unittest.TestLoader().loadTestsFromTestCase(XXXTestCase)
    unittest.TextTestRunner(verbosity=2).run(suite)

1.js

function err() {
	throw new Error('hello world');
}

function getNULL() {
    return null;
}

function getName(myname) {
    return myname;
}

function getComplexObject(obj) { //obj: {'name' : 'mic'}
    return obj;
}

但是,py spidermonkey有个限制:目前还没有windows版本,只能在linux和Mac执行,不过这也不是神马大问题Winking smile

 

你可能感兴趣的:(前端)