18、使用QUnit和PhantomJS进行单元测试

使用QUnit和PhantomJS进行单元测试

在软件开发中,单元测试是确保代码质量和稳定性的重要手段。QUnit是一个流行的JavaScript单元测试框架,而PhantomJS是一个无头浏览器,可用于自动化测试。下面将详细介绍如何使用QUnit和PhantomJS进行单元测试。

QUnit测试结果构成

QUnit的测试结果包含以下几个元素:
- 测试名称标题 :显示测试的名称,与HTML文档的标题标签相同。
- 选项 :可用于隐藏通过的测试(仅显示失败的测试)、检查全局变量或禁用QUnit的try - catch功能。
- 当前浏览器的用户代理字符串 :显示当前使用的浏览器信息。
- 执行所有测试的时间 :显示测试执行所花费的总时间。
- 断言总数、通过数和失败数 :展示测试中各项断言的执行情况。
- 每个执行测试的名称 :列出所有执行过的测试的名称。

QUnit存在的问题

虽然QUnit为单元测试提供了便捷的方式,但存在一些关键问题:
- 依赖浏览器执行测试繁琐 :在为应用程序设置和运行持续集成(CI)环境时,依赖浏览器执行测试会带来很多不便。
- 缺乏命令行执行方式 :没有内置的从命令行执行测试的方法。
- 反馈提取困难 :测试反馈是通过更新浏览器的文档对象模型(DOM)给出的,难以自动提取。

为了解决这些问题,我们引入PhantomJS。

PhantomJS的优势及功能

PhantomJS是一个无头的WebKit安装程序,可通过JavaScript API进行高度脚本化。它在命令行环境下表现出色,也能在CI服务器中良好运行。使用PhantomJS可以实现以下功能:
- 命令行执行测试并获取反馈 :可以从命令行执行测试并获得测试结果反馈。
- 针对真实WebKit安装进行测试 :确保测试环境与实际运行环境相似。
- 捕获屏幕截图 :在测试用例执行前、执行期间或执行后捕获网页的屏幕截图。
- 链式执行测试脚本和场景 :使用第三方工具将多个测试脚本和场景链接在一起。
- 构建测试管道 :构建可用于不同测试目的(单元测试、集成测试和性能测试)的测试管道。

许多知名项目,如Ember.js、Bootstrap、Modernizr和CodeMirror等,都广泛使用PhantomJS进行测试。

示例:使用PhantomJS进行简单测试

下面是一个使用PhantomJS进行测试的示例,该测试会导航到http://emberjs.com ,验证页面标题是否符合预期,如果符合则截取网页屏幕截图并退出测试。

var page = require('webpage').create();         
var before = Date.now();
page.open('http://emberjs.com/', function () { 
    var title = page.evaluate(function () {                             
        return document.title;
    });
    if (title === "Ember.js - About") {                                
        console.log('Title as expected. Rendering screenshot!');
        page.render('emberjs.png');             
    } else {
        console.log("Title not as expected!")
    }

    console.log("Test took: " + (Date.now() - before) + " ms.");        
    phantom.exit();                              
});

在运行此脚本前,需要在计算机上安装PhantomJS,其二进制文件适用于常见的操作系统,如Windows、Mac OS X和Linux。安装完成后,使用以下命令执行脚本:

phantomjs testEmberHomepage.js
PhantomJS模块说明

在使用PhantomJS时,需要在使用前引入所需的模块,常见的模块有:
- webpage :使测试能够与单个网页进行交互。
- system :向测试暴露系统级功能。
- fs :暴露文件系统功能,使测试能够访问文件和目录。
- webserver :一个实验性模块,使用嵌入式Web服务器,PhantomJS脚本可以启动该服务器。

结合QUnit和PhantomJS

单独使用QUnit编写可由CI服务器在每次构建应用程序时执行的单元测试存在两个主要问题:
- 需要特定HTML文件 :QUnit需要自己的HTML文件来设置测试所需的一切。
- 输出难以解析 :QUnit将输出直接打印到DOM中,CI服务器难以判断测试是否失败。

通过将QUnit与PhantomJS结合,可以解决这些问题。下面介绍如何集成QUnit和PhantomJS。

集成脚本的引导

以下是 run - qunit.js 脚本的引导部分,用于声明脚本期望的输入参数并加载测试HTML文件。

var interval = null;       
var start = null;                
var args = phantom.args;           
if (args.length != 1) {
    console.log("Usage: " + phantom.scriptName + " <URL>");
    phantom.exit(1);                               
}
var page = require('webpage').create();
page.open(args[0], function(status) {          
    if (status !== 'success') {                   
        console.error("Unable to access network");
        phantom.exit(1);
    } else {
        page.evaluate(logQUnit);                                     
        start = Date.now();                   
        interval = setInterval(qunitTimeout, 500);    
    }
});

该部分脚本的执行流程如下:
1. 定义一个变量 interval ,用于存储JavaScript间隔对象,用于在测试完成或超时后退出PhantomJS。
2. 获取传递给脚本的参数,如果参数数量不等于1,则打印正确的使用方法并退出。
3. 打开通过第一个参数传递的URL,如果无法加载该URL,则打印错误信息并退出;否则,通过 page.evaluate() 函数调用 logQUnit() 函数,并注册一个间隔,每500毫秒执行一次 qunitTimeout() 函数。

超时函数 qunitTimeout()
function qunitTimeout() {
    var timeout = 60000;                                                
    if (Date.now() > start + timeout) {      
        console.error("Tests timed out");
        phantom.exit(124);
    } else {
        var qunitDone = page.evaluate(function() {
            return window.qunitDone;             
        });
        if (qunitDone) {
            clearInterval(interval);                                 
            if (qunitDone.failed > 0) {
                phantom.exit(1);           
            } else {
                phantom.exit();    
            }
        }
    }
}

qunitTimeout() 函数每500毫秒执行一次,其执行逻辑如下:
1. 定义所有单元测试允许的最大总执行时间为60000毫秒(即60秒)。
2. 定期检查是否超过该时间阈值,如果超过则立即退出PhantomJS。
3. 在允许的执行时间内,检查QUnit是否完成。如果完成,则清除执行超时检查的间隔。如果QUnit报告有测试失败,则以错误状态退出;否则,正常退出。

日志函数 logQUnit()
function logQUnit() {
    var moduleErrors = [];                                              
    var testErrors = [];                                               
    var assertionErrors = [];                
    QUnit.moduleDone(function(context) {          
        //Log any failures to the moduleErrors Array
        //Print Module status to the console
        ...
    });
    QUnit.testDone(function(context) {           
        //Log any failures to the testError and 
        //asertionErrors Arrays
        ...
    });
    QUnit.log(function(context) {                  
        //Print Assertion error messages to the console 
        ...
    });
    QUnit.done(function(context) {                                     
        //Print out any moduleErrors and testErrors
        //Print Stats that show the total, successful and failed
        //tests
        ...
        window.qunitDone = context;                                     
    });
}

logQUnit() 函数向QUnit注册了四个回调函数,用于在QUnit执行测试时获取所需的数据并打印测试结果。它会跟踪模块内测试的结果、单个测试中的断言结果以及单个断言的结果,并在每个模块执行完后报告这些结果,所有测试执行完后给出总结。

编写简单的Ember.js单元测试

编写JavaScript应用程序的单元测试与使用其他静态类型语言有所不同。由于没有标准的方法来设置JavaScript应用程序的运行时环境,需要在QUnit设置中提供完整的应用程序。

待测试函数

以下是一个将JavaScript日期对象转换为人类可读日期字符串的函数:

Montric.ApplicationController = Ember.Controller.extend({
    dateFormat: 'dd mmmm yyyy HH:MM',                                  
    generateChartString: function (date) {         
        var fmt = this.get('dateFormat') || 'dd.mm.yy';                
        var dateString = date ? dateFormat(date, fmt) : "";     
        return dateString;
    }
});

Montric.ApplicationController 指定了一个默认的日期格式 dateFormat ,用于在图表上显示日期。 generateChartString() 函数接受一个日期参数,根据日期格式返回相应的日期字符串。如果 dateFormat 未定义或为null,则使用默认的 dd.mm.yyyy 格式;如果日期变量为null或未定义,则返回空字符串。

单元测试的引导设置
var appController;                       
var inputDate = new Date(2013,2,27,11,15,00);                           
module("Montric.AppController", {                    
    setup: function() {             

        Ember.run(function() {
            appController = 
Montric.__container__.lookup("controller:application");

        }); 
    },

    teardown: function() {      

    }
});

在这个设置中,定义了两个变量: appController 用于保存Ember.js实例化的 Montric.ApplicationController inputDate 用于保存单元测试中使用的日期对象。 module() 函数用于执行所有测试所需的通用设置和清理功能。在 setup() 函数中,通过 Montric.__container__.lookup() 函数获取 Montric.ApplicationController 并赋值给 appController 。需要注意的是,这个私有函数只能在单元测试中使用,不能在生产代码中使用。

创建单元测试

为了验证 generateChartString() 函数的功能,创建了以下五个单元测试:
1. 验证是否成功获取到 Montric.ApplicationController 实例。
2. 验证默认日期格式是否按类初始化中指定的默认规范格式化日期。
3. 验证提供自定义日期格式时,是否按给定的日期格式模式格式化日期。
4. 验证当日期格式为null时,是否按 dd.mm.yyyy 格式格式化日期。
5. 验证当日期为null时,是否返回空字符串。

test("Verify appController", function() {      
    Montric.reset();                                                   
    ok(appController, "Expecting non-null appController"); 
});
test("Testing the default dateFormat", function() {
    Montric.reset();                                                    
    strictEqual("27 March 2013 11:15", 
appController.generateChartString(inputDate), "Default Chart String 
Generation OK");                                          
});
test("Testing custom dateFormat", function() {    
    Montric.reset();                                                    
    appController.set('dateFormat', 'dd.mm.yyyy');
    strictEqual("27.03.2013", appController.generateChartString(inputDate), 
"Custom Chart String Generation OK");        
});
test("Testing null dateFormat", function() {   
    Montric.reset();                                                    
    appController.set('dateFormat', null);
    strictEqual("27.03.13",
    ➥ appController.generateChartString(inputDate),
    ➥ "Null Chart String Generation OK");                                   
});
test("Testing null date", function() {                                  
    Montric.reset();                                  
    strictEqual("", appController.generateChartString(null),
         "Null Date OK");                                   
});

每个测试开始时都会调用 Montric.reset() ,确保Ember.js将应用程序重置为刚加载的状态,方便进行单元测试。在第一个测试中,使用 ok 断言验证是否成功获取到有效的 Montric.ApplicationController 实例;其他测试使用 strictEqual 断言验证 generateChartString() 函数在不同情况下是否返回正确的字符串表示。

strictEquals()与equals()的区别

strictEquals() 函数使用严格相等运算符 === 来验证前两个参数是否相等;而 equals 断言使用相等运算符 == 来检查非严格相等。

通过以上步骤,我们可以使用QUnit和PhantomJS对Ember.js应用程序进行有效的单元测试,确保代码的质量和稳定性。

下面是一个简单的mermaid流程图,展示了使用PhantomJS和QUnit进行单元测试的主要流程:

graph LR
    A[开始] --> B[安装PhantomJS]
    B --> C[编写PhantomJS测试脚本]
    C --> D[执行PhantomJS脚本]
    D --> E{测试是否通过}
    E -- 是 --> F[完成测试]
    E -- 否 --> G[调试并修复问题]
    G --> C
    C --> H[集成QUnit和PhantomJS]
    H --> I[编写QUnit单元测试]
    I --> J[执行QUnit单元测试]
    J --> K{单元测试是否通过}
    K -- 是 --> L[完成单元测试]
    K -- 否 --> M[调试并修复单元测试问题]
    M --> I

通过这个流程图,可以清晰地看到使用PhantomJS和QUnit进行单元测试的整体步骤和循环过程。

此外,为了更清晰地对比不同测试场景下的预期结果和实际结果,我们可以使用表格进行展示:
| 测试场景 | 预期结果 | 实际结果验证方式 |
| — | — | — |
| 验证appController实例 | 非空的Montric.ApplicationController实例 | 使用ok断言 |
| 测试默认日期格式 | “27 March 2013 11:15” | 使用strictEqual断言 |
| 测试自定义日期格式 | “27.03.2013” | 使用strictEqual断言 |
| 测试null日期格式 | “27.03.13” | 使用strictEqual断言 |
| 测试null日期 | 空字符串 | 使用strictEqual断言 |

这样的表格可以帮助我们更直观地了解每个测试场景的具体要求和验证方法。

使用QUnit和PhantomJS进行单元测试

总结与回顾

在前面的内容中,我们详细介绍了使用QUnit和PhantomJS进行单元测试的相关知识。首先了解了QUnit测试结果的构成以及其存在的问题,如依赖浏览器执行繁琐、缺乏命令行执行方式和反馈提取困难等。接着引入了PhantomJS,它具有命令行执行测试、针对真实WebKit安装进行测试、捕获屏幕截图等优势。我们还给出了使用PhantomJS进行简单测试的示例,以及PhantomJS常见模块的说明。

之后,阐述了如何将QUnit与PhantomJS结合,包括集成脚本的引导、超时函数 qunitTimeout() 和日志函数 logQUnit() 的实现。最后,以 Montric.ApplicationController 中的 generateChartString() 函数为例,介绍了如何编写简单的Ember.js单元测试,包括待测试函数的分析、单元测试的引导设置和具体的单元测试创建。

测试流程的优化建议

为了进一步提高测试效率和质量,我们可以对测试流程进行优化。以下是一些建议:
1. 自动化测试脚本管理 :将测试脚本进行分类管理,例如按照功能模块、测试类型等进行划分。可以使用版本控制系统(如Git)来管理测试脚本的版本,方便团队协作和代码回溯。
2. 测试数据管理 :对于不同的测试场景,可能需要不同的测试数据。可以创建测试数据生成工具,根据测试需求自动生成合适的测试数据。同时,对测试数据进行备份和管理,确保测试的可重复性。
3. 测试报告生成 :在测试完成后,生成详细的测试报告。测试报告应包含测试结果的统计信息、失败测试的详细信息以及测试执行时间等。可以使用第三方工具(如Allure)来生成美观、易读的测试报告。

常见问题及解决方法

在使用QUnit和PhantomJS进行单元测试的过程中,可能会遇到一些常见问题,以下是一些问题及解决方法:
| 问题 | 症状 | 解决方法 |
| — | — | — |
| 网络问题 | 无法访问测试URL,PhantomJS提示“Unable to access network” | 检查网络连接是否正常,确保测试URL可以正常访问。可以尝试使用 ping 命令测试网络连通性。 |
| 测试超时 | 测试执行时间过长,最终超时退出 | 检查测试代码中是否存在死循环或耗时过长的操作。可以适当调整 qunitTimeout() 函数中的超时时间,但不建议设置过长。 |
| 断言失败 | 单元测试中某些断言失败 | 检查测试代码和待测试代码,确保断言的条件和预期结果正确。可以使用调试工具(如浏览器的开发工具)来调试代码。 |

拓展应用

除了上述介绍的基本单元测试,QUnit和PhantomJS还可以应用于其他场景:
1. 集成测试 :将多个模块组合在一起进行测试,验证模块之间的交互是否正常。可以使用PhantomJS模拟用户在网页上的操作,触发不同模块之间的交互。
2. 性能测试 :使用PhantomJS的性能分析功能,对应用程序的性能进行测试。例如,可以测量页面加载时间、脚本执行时间等。
3. 跨浏览器测试 :虽然PhantomJS是一个无头浏览器,但可以结合其他浏览器自动化工具(如Selenium),对不同浏览器进行测试,确保应用程序在各种浏览器上都能正常运行。

总结

通过使用QUnit和PhantomJS,我们可以方便地进行单元测试,提高代码的质量和稳定性。在实际应用中,需要根据具体的项目需求和场景,灵活运用这些工具和方法。同时,不断优化测试流程,解决遇到的问题,拓展测试的应用场景,以更好地保障软件的质量。

下面是一个mermaid流程图,展示了优化后的测试流程:

graph LR
    A[开始] --> B[自动化测试脚本管理]
    B --> C[测试数据管理]
    C --> D[编写测试脚本]
    D --> E[执行测试]
    E --> F{测试是否通过}
    F -- 是 --> G[生成测试报告]
    F -- 否 --> H[调试并修复问题]
    H --> D
    G --> I[分析测试报告]
    I --> J{是否需要优化测试流程}
    J -- 是 --> K[优化测试流程]
    K --> B
    J -- 否 --> L[结束]

这个流程图展示了一个更加完善的测试流程,包括测试脚本管理、测试数据管理、测试执行、测试报告生成和分析以及测试流程的优化等环节。

总之,掌握QUnit和PhantomJS的使用方法,并结合实际情况进行灵活运用和优化,将有助于我们更好地进行软件测试工作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值