JavaScript单元测试ABC

前言
  当前,在软件开发中单元测试越来越受到开发者的重视,它能提高软件的开发效率,而且能保障开发的质量。以往,单元测试往往多见于服务端的开发中,但随着Web编程领域的分工逐渐明细,在前端Javascript开发领域中,也可以进行相关的单元测试,以保障前端开发的质量。
  在服务器端的单元测试中,都有各种各样的测试框架,在JavaScript中现在也有一些很优秀的框架,但在本文中,我们将自己动手一步步来实现一个简单的单元测试框架。
  JS单元测试有很多方面,比较多的是对方法功能检查,对浏览器兼容性检查,本文主要谈第一种。

本文检查的JS代码是我以前写的一个JS日期格式化的方法,原文在这里(javascript日期格式化函数,跟C#中的使用方法类似),代码如下:


代码如下:

Date.prototype.toString=function(format){
var time={};
time.Year=this.getFullYear();
time.TYear=(""+time.Year).substr(2);
time.Month=this.getMonth()+1;
time.TMonth=time.Month<10?"0"+time.Month:time.Month;
time.Day=this.getDate();
time.TDay=time.Day<10?"0"+time.Day:time.Day;
time.Hour=this.getHours();
time.THour=time.Hour<10?"0"+time.Hour:time.Hour;
time.hour=time.Hour<13?time.Hour:time.Hour-12;
time.Thour=time.hour<10?"0"+time.hour:time.hour;
time.Minute=this.getMinutes();
time.TMinute=time.Minute<10?"0"+time.Minute:time.Minute;
time.Second=this.getSeconds();
time.TSecond=time.Second<10?"0"+time.Second:time.Second;
time.Millisecond=this.getMilliseconds();
var oNumber=time.Millisecond/1000;
if(format!=undefined && format.replace(/\s/g,"").length>0){
format=format
.replace(/yyyy/ig,time.Year)
.replace(/yyy/ig,time.Year)
.replace(/yy/ig,time.TYear)
.replace(/y/ig,time.TYear)
.replace(/MM/g,time.TMonth)
.replace(/M/g,time.Month)
.replace(/dd/ig,time.TDay)
.replace(/d/ig,time.Day)
.replace(/HH/g,time.THour)
.replace(/H/g,time.Hour)
.replace(/hh/g,time.Thour)
.replace(/h/g,time.hour)
.replace(/mm/g,time.TMinute)
.replace(/m/g,time.Minute)
.replace(/ss/ig,time.TSecond)
.replace(/s/ig,time.Second)
.replace(/fff/ig,time.Millisecond)
.replace(/ff/ig,oNumber.toFixed(2)*100)
.replace(/f/ig,oNumber.toFixed(1)*10);
}
else{
format=time.Year+"-"+time.Month+"-"+time.Day+" "+time.Hour+":"+time.Minute+":"+time.Second;
}
return format;
}

这段代码目前没有发现比较严重的bug,本文为了测试,我们把 .replace(/MM/g,time.TMonth) 改为 .replace(/MM/g,time.Month),这个错误是当月份小于10时,没有用两位数表示月份。
  现在有这么一句话,好的设计都是重构出来的,在本文中也一样,我们从最简单的开始。
第一版:用最原始的alert
  作为第一版,我们很偷懒的直接用alert来检查,完整代码如下:


代码如下:

<!DOCTYPE html>
<html>
<head>
<title>Demo</title>
<meta charset="utf-8"/>
</head>
<body>
<script type="text/javascript">
Date.prototype.toString=function(format){
var time={};
time.Year=this.getFullYear();
time.TYear=(""+time.Year).substr(2);
time.Month=this.getMonth()+1;
time.TMonth=time.Month<10?"0"+time.Month:time.Month;
time.Day=this.getDate();
time.TDay=time.Day<10?"0"+time.Day:time.Day;
time.Hour=this.getHours();
time.THour=time.Hour<10?"0"+time.Hour:time.Hour;
time.hour=time.Hour<13?time.Hour:time.Hour-12;
time.Thour=time.hour<10?"0"+time.hour:time.hour;
time.Minute=this.getMinutes();
time.TMinute=time.Minute<10?"0"+time.Minute:time.Minute;
time.Second=this.getSeconds();
time.TSecond=time.Second<10?"0"+time.Second:time.Second;
time.Millisecond=this.getMilliseconds();
var oNumber=time.Millisecond/1000;
if(format!=undefined && format.replace(/\s/g,"").length>0){
format=format
.replace(/yyyy/ig,time.Year)
.replace(/yyy/ig,time.Year)
.replace(/yy/ig,time.TYear)
.replace(/y/ig,time.TYear)
.replace(/MM/g,time.Month)
.replace(/M/g,time.Month)
.replace(/dd/ig,time.TDay)
.replace(/d/ig,time.Day)
.replace(/HH/g,time.THour)
.replace(/H/g,time.Hour)
.replace(/hh/g,time.Thour)
.replace(/h/g,time.hour)
.replace(/mm/g,time.TMinute)
.replace(/m/g,time.Minute)
.replace(/ss/ig,time.TSecond)
.replace(/s/ig,time.Second)
.replace(/fff/ig,time.Millisecond)
.replace(/ff/ig,oNumber.toFixed(2)*100)
.replace(/f/ig,oNumber.toFixed(1)*10);
}
else{
format=time.Year+"-"+time.Month+"-"+time.Day+" "+time.Hour+":"+time.Minute+":"+time.Second;
}
return format;
}
var date=new Date(2012,3,9);
alert(date.toString("yyyy"));
alert(date.toString("MM"));
</script>
</body>
</html>

运行后会弹出 2012 和 4 ,观察结果我们知道 date.toString("MM")方法是有问题的。
  这种方式很不方便,最大的问题是它只弹出了结果,并没有给出正确或错误的信息,除非对代码非常熟悉,否则很难知道弹出的结果是正是误,下面,我们写一个断言(assert)方法来进行测试,明确给出是正是误的信息。
第二版:用assert进行检查
  断言是表达程序设计人员对于系统应该达到状态的一种预期,比如有一个方法用于把两个数字加起来,对于3+2,我们预期这个方法返回的结果是5,如果确实返回5那么就通过,否则给出错误提示。
  断言是单元测试的核心,在各种单元测试的框架中都提供了断言功能,这里我们写一个简单的断言(assert)方法:


代码如下:

function assert(message,result){
if(!result){
throw new Error(message);
}
return true;
}

这个方法接受两个参数,第一个是错误后的提示信息,第二个是断言结果
  用断言测试代码如下:


代码如下:

var date=new Date(2012,3,9);
try{
assert("yyyy should return full year",date.toString("yyyy")==="2012");
}catch(e){
alert("Test failed:"+e.message);
}
try{
assert("MM should return full month",date.toString("MM")==="04");
}
catch(e){
alert("Test failed:"+e.message);
}

  运行后会弹出如下窗口:

第三版:进行批量测试

  在第二版中,assert方法可以给出明确的结果,但如果想进行一系列的测试,每个测试都要进行异常捕获,还是不够方便。另外,在一般的测试框架中都可以给出成功的个数,失败的个数,及失败的错误信息。

  为了可以方便在看到测试结果,这里我们把结果用有颜色的文字显示的页面上,所以这里要写一个小的输出方法PrintMessage:


代码如下:

function PrintMessage(text,color){
var div=document.createElement("div");
div.innerHTML=text;
div.style.color=color;
document.body.appendChild(div);
delete div;
}

  下面,我们就写一个类似jsTestDriver中的TestCase方法,来进行批量测试:


代码如下:

function testCase(name,tests){
var successCount=0;
var testCount=0;
for(var test in tests){
testCount++;
try{
tests[test]();
PrintMessage(test+" success","#080");
successCount++;
}
catch(e){
PrintMessage(test+" failed:"+e.message,"#800");
}
}
PrintMessage("Test result: "+testCount+" tests,"+successCount+" success, "+ (testCount-successCount)+" failures","#800");
}

  测试代码:


代码如下:

var date=new Date(2012,3,9);
testCase("date toString test",{
yyyy:function(){
assert("yyyy should return 2012",date.toString("yyyy")==="2012");
},
MM:function(){
assert("MM should return 04",date.toString("MM")==="04");
},
dd:function(){
assert("dd should return 09",date.toString("dd")==="09");
}
});

  结果为:

这样我们一眼就可以看出哪个出错了。但这样是否就完美了呢,我们可以看到最后那个测试中 var date=new Date(2012,3,9)是放在testCase外面定义的,并且整个testCase的测试代码中共用了date,这里因为各个方法中没有对date的值进行修改,所以没出问题,如果某个测试方法中对date的值修改了呢,测试的结果就是不准确的,所以在很多测试框架中都提供了setUp和tearDown方法,用来对统一提供和销毁测试数据,下面我们就在我们的testCase中加上setUp和tearDown方法。
第四版:统一提供测试数据的批量测试

  首先我们添加setUp和tearDown方法:


代码如下:

testCase("date toString",{
setUp:function(){
this.date=new Date(2012,3,9);
},
tearDown:function(){
delete this.date;
},
yyyy:function(){
assert("yyyy should return 2012",this.date.toString("yyyy")==="2012");
},
MM:function(){
assert("MM should return 04",this.date.toString("MM")==="04");
},
dd:function(){
assert("dd should return 09",this.date.toString("dd")==="09");
}
});

  由于setUp和tearDown方法不参与测试,所以我们要修改testCase代码:


代码如下:

function testCase(name,tests){
var successCount=0;
var testCount=0;
var hasSetUp=typeof tests.setUp == "function";
var hasTearDown=typeof tests.tearDown == "function";
for(var test in tests){
if(test==="setUp"||test==="tearDown"){
continue;
}
testCount++;
try{
if(hasSetUp){
tests.setUp();
}
tests[test]();
PrintMessage(test+" success","#080");

if(hasTearDown){
tests.tearDown();
}

successCount++;
}
catch(e){
PrintMessage(test+" failed:"+e.message,"#800");
}
}
PrintMessage("Test result: "+testCount+" tests,"+successCount+" success, "+ (testCount-successCount)+" failures","#800");
}

  运行后的结果跟第三版相同。
小结及参考文章

  上面说了,好的设计是不断重构的结果,上面的第四版是不是就完美了呢,远远没有达到,这里只是一个示例。如果大家需要这方面的知识,我后面可以再写写各个测试框架的使用。

  本文只是JS单元测试入门级的示例,让初学者对JS的单元测试有个初步概念,属于抛砖引玉,欢迎各位高人拍砖补充。

  本文参考了《测试驱动的JavaScript开发》(个人觉得还不错,推荐下)一书第一章,书中的测试用例也是一个时间函数,不过写的比较复杂,初学者不太容易看懂。
作者:Artwl

(0)

相关推荐

  • JavaScript单元测试ABC

    前言 当前,在软件开发中单元测试越来越受到开发者的重视,它能提高软件的开发效率,而且能保障开发的质量.以往,单元测试往往多见于服务端的开发中,但随着Web编程领域的分工逐渐明细,在前端Javascript开发领域中,也可以进行相关的单元测试,以保障前端开发的质量. 在服务器端的单元测试中,都有各种各样的测试框架,在JavaScript中现在也有一些很优秀的框架,但在本文中,我们将自己动手一步步来实现一个简单的单元测试框架. JS单元测试有很多方面,比较多的是对方法功能检查,对浏览器兼容性检查,本

  • Javascript单元测试框架QUnitjs详细介绍

    一.什么是 QUnit QUnit(http://qunitjs.com/) 是一个非常强大的javascript单元测试框架,可以帮你调试代码.它是由 jQuery 团队的成员写的,而且是 jQuery 的官方测试套装.但QUnit一般是足以测试任何常规 javascript 代码,它甚至可能通过一些 javascript 引擎比如 Rhino 或 V8 来测试服务器端 JavaScript.如果你不熟悉"单元测试"的概念,请不要担心.这不是很难理解的: 复制代码 代码如下: 在计算

  • JQuery团队打造的javascript单元测试工具QUnit介绍

    什么是单元测试? 单元测试又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作.单元测试主要是用来检验程式的内部逻辑,也称为个体测试.结构测试或逻辑驱动测试.通常由撰写程式码的程式设计师负责进行. 通常来说,程式設計師每修改一次程式就會進行最少一次單元測試,在編寫程式的過程中前後很可能要進行多次單元測試,以證實程式達到軟件規格書(en:Specification)要求的工作目標,沒有臭蟲:雖然单元测试不是什么必须的,但也不坏,這牽涉到專案管理的政策決定. -- 维基百科

  • Riot.js 快速的JavaScript单元测试框架

    http://github.com/alexyoung/riotjs示例: Ruby代码 复制代码 代码如下: context "a new user" do setup { User.new } asserts("that it is not yet created") { topic.new_record? } end context "a new user" do setup { User.new } asserts("that

  • kmock javascript 单元测试代码

    复制代码 代码如下: (function () { var KMock = window.KMock = function () { } KMock.prototype.setup = function (methodName) { var instance = this; instance[methodName] = { returnAs: function (fn) { instance["_" + methodName] = function () { instance[meth

  • 详解如何用JavaScript编写一个单元测试

    目录 为什么要进行单元测试? 范围界定和编写单元测试 保持单元测试简短而简单 考虑正面和负面的测试用例 分解长而复杂的函数 避免网络和数据库连接 如何编写单元测试 创建一个新项目 实现一个类 配置和添加我们的第一个单元测试 添加更多单元测试 修复错误 最后 测试代码是确保代码稳定的第一步.能做到这一点的最佳方法之一就是使用单元测试,确保应用程序中的每个较小的功能都按应有的方式运行——尤其是当应用程序接收到极端或无效输入,甚至可能有害的输入时. 为什么要进行单元测试? 进行单元测试有许多不同的方法

  • 7个去伪存真的JavaScript面试题

    下面这7个JavaScript面试问题是你应该在面试前先问的.否则,很有可能会浪费你的时间. 1.创建JavaScript对象的两种方法是什么? 这是一个非常简单的问题,如果你用过JavaScript的话.你至少得知道一种方法.但是,尽管如此,根据我的经验,也有很多自称是JavaScript程序员的人说不知道如何回答这个问题. 使用"new"关键字来调用函数. open/close花括号. var o = {}; 你也可以继续提问,"使用new关键字,什么情况下创建对象?&q

  • JavaScript测试工具之Karma-Jasmine的安装和使用详解

    1.Karma介绍 Karma是Testacular的新名字,在2012年google开源了Testacular,2013年Testacular改名为Karma.Karma是一个让人感到非常神秘的名字,表示佛教中的缘分,因果报应,比Cassandra这种名字更让人猜不透! Karma是一个基于Node.js的JavaScript测试执行过程管理工具(Test Runner).该工具可用于测试所有主流Web浏览器,也可集成到CI(Continuous integration)工具,也可和其他代码编

  • javascript定义变量时带var与不带var的区别分析

    本文实例分析了javascript定义变量时带var与不带var的区别.分享给大家供大家参考.具体分析如下: 直接看实例里说明: 复制代码 代码如下: <script language="javascript" type="text/javascript"> var abc=89;//带var,表示全局变量 function test(){  var abc=80;//在函数内部,如果不带var,表示使用函数外全局变量:带上var,表示新定义一个全局变量

  • 几个比较实用的JavaScript 测试及效验工具

    尽管JavaScript的语法非常简单,但对于写程序而言仍然是困难重重,就是因为它的运行环境:基于Web浏览器. JSLint JSLint是基于Web的验证JavaScript错误代码的工具.它拥有的功能及特定的设置来使用您的需求,自定义你的验证算法. JsUnit JsUnit是一款在客户端(在浏览时)的单元测试JavaScript框架.对JavaScript而言,JUnit就像是它的一个端口.当然它也可以在多个浏览器.多个机器的不同操作系统中自动运行.它的发展始于2001年1月. J3Un

随机推荐