65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Jasmine 进行 ExtJS 应用程序单元测试的分步指南

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (3投票s)

2013年10月3日

CPOL

5分钟阅读

viewsIcon

74152

downloadIcon

2177

本文是一个关于如何使用 Jasmine 实现 ExtJS 应用程序单元测试的分步指南。

引言

本文是关于如何使用 Jasmine 框架对 ExtJS 应用程序进行单元测试的分步指南。ExtJS 的 MVC 方法结合 Jasmine 框架的“行为驱动测试”方法,能够独立测试 ExtJS 的模型、存储和控制器。

背景

ExtJS 是一个基于模型-视图-控制器(MVC)的 JavaScript 框架,用于开发富互联网应用程序。ExtJS 的架构强制将模型/视图/控制器保存在不同的文件中,以开发类似桌面应用程序。ExtJS 架构需要创建模型、视图和控制器,最好是作为独立的文件。根据 MVC 范例:

  • 模型代表实体的状态。
  • 视图处理实体的表示逻辑,并且
  • 控制器通过处理模型/视图上发生的事件来充当模型和视图之间的中介,并响应事件更新模型/视图。
如上所述的 MVC 架构,便于为控制器和存储编写单元测试。这种方法不需要从视图触发事件,因为视图上发生的所有事件都绑定到控制器中的 JavaScript 函数作为事件处理程序,因此它们可以独立调用。同样,ExtJS 存储也独立于视图,可以独立进行测试。

Jasmine 框架

Jasmine 是一个 JavaScript 框架,可以实现 JavaScript 代码的行为驱动测试。Jasmine 是一个独立的框架,不依赖任何其他 JavaScript 库。它允许在不与 DOM 交互的情况下测试 JavaScript 代码。Jasmine 库可用于测试任何 JavaScript 代码的功能。

Jasmine 可以轻松集成以测试 ExtJS 应用程序的行为,因为 ExtJS 的控制器/存储/模型是功能代码片段,对 UI 的引用最少。ExtJS 中的控制器会绑定到不同的 UI 事件,如单击、选择等,并将它们委托给控制器中定义的相应事件处理函数。我们可以利用 Jasmine 框架来测试这些事件委托,而无需实际从 UI 生成事件。

在本文中,我们将测试一个简单的 QuestionBank 应用程序,该应用程序允许我们在数据库中添加/删除/列出多项选择题和答案。

文档包含两个 zip 文件,一个包含不带测试用例的 QuestionBank 应用程序(questionbank.zip)。此 zip 文件可用作编写测试用例的骨架。另一个 zip 文件(questionbank-tests.zip)包含完整的代码库以及测试用例,方便快速参考。

为简单起见,示例应用程序代码库包含一个示例 servlet,该 servlet使用数组列表来返回/保存问题。

让我们开始探索为 ExtJS 应用程序创建单元测试用例的方法。

应用程序组件

将附加的 questionbank.zip 中的 WebContent 目录的内容提取到名为 PRJ_DIR 的 Web 项目中。应用程序的不同组件如下:

总之,在集成 Jasmine 框架与 ExtJS 以创建基于浏览器的 HTML 测试用例时,有六个步骤:

  1. 创建测试文件夹结构
  2. 下载并设置 Jasmine 框架
  3. 创建 test-app.js
  4. 在 Jasmine 规范中创建测试用例
  5. 创建测试 HTML 文件
  6. 执行测试用例

因此,让我们一步一步地探讨这些步骤。

步骤 1:创建测试文件夹结构

为了配置测试用例,我们需要为 Jasmine 框架、测试用例和配置文件创建文件夹。在 PRJ_DIR\WebContent 目录下创建一个名为 js 的文件夹,并创建以下文件夹:

  • js
  • js/jasmine
  • js/test
  • js/spec

下图显示了最终所需的目录结构。用红色矩形标出的文件夹是需要创建的新文件夹,用于集成 Jasmine 框架。

步骤 2:下载并设置 Jasmine 框架

在此步骤中,我们将学习如何设置 Jasmine 框架以实现单元测试。

  1. https://github.com/pivotal/jasmine/downloads 下载 Jasmine 框架包 "jasmine-standalone-1.3.1.zip"
  2. 将 jasmine-standalone-1.3.1.zip 解压缩到您选择的任何目录。
  3. 将以下文件复制到您的 PRJ_DIR\WebContent\js\jasmine 目录:
    1. lib/jasmine-1.3.1/jasmine.css 到 PRJ_DIR/WebContent/js/jasmine/jasmine.css
    2. lib/jasmine-1.3.1/jasmine.js 到 PRJ_DIR/WebContent/js/jasmine/jasmine.js
    3. lib/jasmine-1.3.1/jasmine-html.js 到 PRJ_DIR/WebContent/js/jasmine/jasmine-html.js

步骤 3:创建 test-app.js

我们需要创建一个单独的 app.js 文件来实现测试。此文件将创建在 PROJECT_DIR\WebContent\js\test 目录中。

让我们看看如何做到这一点:

  1. 创建现有 app.js 的副本,使其看起来像这样:
  2. Ext.Loader.setConfig ({enabled: true});
    
    // Loading different components like controller, model, view..
    Ext.application ({
    	 controllers:[ 'QuestionController' ],
    	 models: [ 'Question' ],
    	 stores: [ 'QuestionStore' ],
    	 views: [ 'MainPanel' ],
    	 autoCreateViewport: true,
    	 name: 'QAApp'
    });
  3. autoCreateViewPort 设置为 false,以便 ExtJS 不会渲染任何 HTML。这是必需的,因为我们将测试控制器/存储而不初始化视图。
  4. Ext.Loader.setConfig ({enabled: true});
    
    Ext.application ({
         .............
         .............
      // Setting the autoCreateViewPort to initialize the Application without   
      // View being rendered
      autoCreateViewport: false,
      
      name: 'QAApp'
    });
  5. 添加 launch 函数以初始化和执行 Jasmine 测试用例。
  6. Ext.Loader.setConfig ({enabled: true});
    
    Ext.application ({
         .............
         .............
         autoCreateViewport: false,
         name: 'QAApp',
        
        // using the Launch method of Application object to execute the Jasmine
        //Test Cases
         launch: function () {
    	var jasmineEnv = jasmine.getEnv ();
    	jasmineEnv.updateInterval = 1000;
    	var htmlReporter = new jasmine.HtmlReporter ();
    	jasmineEnv.addReporter (htmlReporter);
    	jasmineEnv.execute ();
         }
    
    });

步骤 4:在 Jasmine 规范中创建测试用例

创建一个空的 JavaScript 文件 PROJECT_DIR\WebContent\js\spec\QuizControllerSpec.js。这将作为所有单元测试用例的占位符。

开始编写 Jasmine 测试用例,例如:

describe ("ExtJS Question App Test Suite", function () {
var mainPanel = null;
var questionStore = null;
var questionStore = null;
var storeLength = -1;
var controller = null;
  /* Setup method to be called before each Test case.*/
  beforeEach (function () {
        // Initializing the mainPanel.
       mainPanel = Ext.create ('QAApp.view.MainPanel');
       questionStore = Ext.StoreManager.lookup ('QuestionStore');
       controller = Ext.create ('QAApp.controller.QuestionController');
       storeLength = questionStore.data.items.length;
  }); // before each

  /* Test if View is created Successfully.*/
  it ('Main View is loaded', function () {
        expect (mainPanel != null).toBeTruthy ();
  });

 /* Test if store is loaded successfully.*/ 
  it ('Store shouldn’t be null', function () {
        expect (questionStore != null).toBeTruthy();
   });

  /* Test controller is initialized successfully.*/ 
  it ('Controller shouldn’t be null', function () {
        expect (controller != null).toBeTruthy();
   });

/* Test if Grid in MainPanel is loaded successfully.*/   
  it ('Grid should be loaded', function () {
        expect (Ext.getCmp ("questionGrid") != null).toBeTruthy ();
  });

 /* Test if Grid in MainPanel is loaded successfully.*/   
  it ('Store has items', function () {
  
       expect (questionStore.data.items.length).toBe (storeLength);
  });

 /* Test if new item is added to store.*/   
 it ('New item should be added to store', function () {
        var record = Ext.create ("QAApp.model.Question");
        record.id = 1;
        record.question = 'Questions 3';
        questionStore.add (record);
        expect (questionStore.data.items.length).toBe (storeLength + 1);
        questionStore.removeAt (storeLength);
 });

/* Item should be removed from store via controller.*/   
 it ('Item should be removed from store', function () {
        var record = Ext.create ("QAApp.model.Question");
        record.id = 1;
        record.question = 'Questions 3';
        questionStore.add (record);

        /* Removing item from controller API.*/   
        controller.deleteQuestionFromStore(record);
        questionStore.removeAt (storeLength);
        expect (questionStore.data.items.length).toBe (storeLength);
 });

});

步骤 5:创建测试 HTML 文件

这是主要文件,它将集成 Jasmine 单元测试用例和 ExtJS 应用程序的代码。它将包含 Jasmine 框架、Jasmine 测试用例、ExtJS test-app.js 文件。此文件将实际执行测试用例。

请参考以下步骤了解更多详情:

  1. 创建一个空的 html 文件 SpecRunner.html,其中包含以下内容:
  2. <html>
       <head>
          <title>Test Quiz Application</title>
       </head>
       <body>
       </body>
    </html>
  3. 在测试 HTML 中添加 Jasmine 框架的 CSS 和 JS 文件。这是为了让 SpecRunner.html 使用 Jasmine 框架来实现单元测试。
  4. SpecRunner.html 中添加 ExtJS 库/CSS。这是加载 ExtJS 代码库到测试文件所必需的。
  5. 包含 Jasmine 测试用例文件。这将允许加载测试用例,以便 Jasmine 框架可以执行它们。
  6. 现在添加 app-test.js 文件以在测试模式下加载 ExtJS 应用程序。
  7. 执行完以上所有步骤后,最终的 SpecRunner.html 文件将如下所示:
    <html>
       <head>
             <title>Test Quiz Application</title>	
            
             // including the Jasmine Files
            <link rel="stylesheet" type="text/css" href="js/jasmine/jasmine.css">
            <script type="text/javascript" src="js/jasmine/jasmine.js"></script>
            <script type="text/javascript" src="js/jasmine/jasmine-html.js"></script>
    
            //including the ExtJS Files
            <script type="text/javascript" src="extjs-4.1.1/ext-all.js"></script>
            <link rel="stylesheet" type="text/css" href="extjs-4.1.1/resources/css/ext-all.css">
     
            // including the Jasmine Test Case file
           <script type="text/javascript" src="js/spec/QuizControllerSpec.js"></script>
    
            // including the test-app.js file to load test cases
           <script type="text/javascript" src="js/test/test-app.js"></script>
       </head>
      <body>
      </body>
    </html>

步骤 6:执行测试用例

执行测试用例非常容易。只需将应用程序部署到任何应用程序服务器,如 Tomcat。

应用程序部署后,打开 URL http://:/ExtJSQuizApp/SpecRunner.html 将会触发测试用例。

结果如下所示:

摘要

正如我们所见,利用 Jasmine 框架来测试 ExtJS 应用程序非常简单。同样,我们可以使用 Jasmine 来测试任何 JavaScript 代码库。

源代码

附带了带骨架和测试用例的应用程序源代码。

参考文献

  1. http://www.sencha.com/products/extjs 
  2. http://pivotal.github.io/jasmine/
© . All rights reserved.