使用 Karma 和 Jasmine 进行 AngularJS 单元测试

⚡ 智能摘要

使用 Karma 和 Jasmine 进行 AngularJS 单元测试,可以隔离地验证控制器、指令和服务。Karma 在真实的浏览器中运行测试用例,Jasmine 提供断言语法,而 angular-mocks 则无需运行服务器即可注入依赖项。

  • 🧱 工具拆分: Karma 是测试运行器,Jasmine 是规范框架,angular-mocks 提供依赖注入助手。
  • ⚙️ 安装路径: 通过 npm 安装 karma、karma-chrome-launcher、karma-jasmine 和 karma-cli,然后生成 karma.conf.js。
  • 📁 配置规则: files 数组必须按顺序列出 angular.js、angular-mocks.js、应用程序源代码和测试文件夹。
  • 🎛️ 控制器测试: 注入 $controller 服务,传递一个模拟的 $scope 对象,然后断言控制器分配的值。
  • 🧩 指令测试: 使用 $rootScope 编译 $compile,调用 $digest,并检查指令返回的渲染标记。
  • 🌐 端到端层: 专业版tractor 驱动一个正在运行的应用程序,并计算 ng-repeat 生成的重复行数。
  • 📌 支持状态: AngularJS 已于 2021 年 12 月 31 日停止维护,因此请将此工作流程视为遗留维护知识。

使用 Karma 和 Jasmine 进行 AngularJS 单元测试

AngularJS最出色的特性之一是 测试与验证 方面。当开发人员在 Google 在开发 AngularJS 的过程中,他们始终牢记测试的重要性,并确保整个 AngularJS 框架都是可测试的。

在 AngularJS 中,测试通常使用 Karma(框架)进行。虽然不用 Karma 也可以进行 AngularJS 测试,但 Karma 框架在测试 AngularJS 代码方面功能强大,因此使用它更为合理。

  • 在 AngularJS 中,我们可以执行 单元测试 分别为控制器和指令。
  • 我们还可以执行 端到端测试 AngularJS 是从用户角度进行测试的。

⚠️ 版本说明: AngularJS(1.x 版本)已于 2021 年 12 月 31 日正式停止维护,ProtracTor 已于 2023 年 8 月停止维护。本页所示的工作流程仍然适用于维护现有的 AngularJS 1.x 应用程序。新项目应使用最新版本的 Angular 以及当前受支持的运行器。

Karma框架介绍与安装

业力是 测试自动化工具 由 AngularJS 团队创建 Google使用 Karma 的第一步是安装 Karma。Karma 可以通过 npm 安装(npm 是一个包管理器,用于在本地计算机上轻松安装模块)。

安装 Karma

通过 npm 安装 Karma 分为两个步骤。

步骤1) 在命令行中执行下面一行

npm install karma karma-chrome-launcher karma-jasmine --save-dev

其中,

  1. npm 是 Node 包管理器的命令行实用程序,用于在任何机器上安装自定义模块。它随附于…… Node.js.
  2. install 参数指示 npm 命令行实用程序需要安装。
  3. 命令行中指定了 3 个与 karma 协同工作所需的库。
    • karma 是用于测试目的的核心库。
    • karma-chrome-launcher 是一个单独的库,可以使 karma 命令被 chrome 浏览器识别。
    • karma-jasmine – 这将安装 jasmine,它是 Karma 的依赖框架。

步骤2) 下一步是安装 karma 命令行实用程序。这是执行 karma 行命令所必需的。karma 行实用程序将用于初始化 karma 环境以进行测试。

要安装命令行实用程序,请在命令行中执行以下命令

npm install -g karma-cli
  • karma-cli 用于安装 Karma 的命令行界面,用户可以通过该界面在命令行中输入 Karma 命令。-g 标志会将其全局安装,以便用户可以从任何文件夹访问 karma 命令。

Karma 框架的配置

下一步是配置 karma,可以通过以下命令完成

karma init

执行完上述步骤后,karma 将创建一个 karma.conf.js 文件。该文件可能看起来像下面的代码片段

files: [
  'node_modules/angular/angular.js',
  'node_modules/angular-mocks/angular-mocks.js',
  'lib/app.js',
  'tests/*.js'
]

上述配置文件告诉 karma 运行时引擎以下内容

  1. 角.js – 这告诉 Karma,你的应用程序依赖于 AngularJS 中的核心模块。
  2. angular-mocks.js – 这告诉 Karma 使用 angular-mocks.js 文件中的 AngularJS 单元测试功能。
  3. 所有主要应用程序或业务逻辑文件都存在于应用程序的 lib 文件夹中。
  4. 测试文件夹将包含所有单元测试。

💡提示: 文件数组中的顺序很重要。angular.js 必须在 angular-mocks.js 之前加载,并且两者都必须在应用程序代码之前加载,否则在运行规范时注入器将不可用。

要检查 karma 是否正常运行,请创建一个名为 Sample.js 的文件,输入以下代码并将其放在测试目录中。

describe('Sample test', function() {
  it('Condition is true', function() {
    expect('AngularJS').toBe('AngularJS');
  });
});

上述代码有以下几个方面

  1. describe 函数用于对测试进行描述。在我们的例子中,我们为测试提供了“示例测试”的描述。
  2. “it”函数用于为测试命名。在我们的例子中,我们将测试命名为“条件为真”。测试的名称需要有意义。
  3. 'expect' 和 'toBe' 关键字的组合表明测试结果的预期值和实际值。如果实际值和预期值相同,则测试将通过,否则将失败。

在命令提示符下执行以下行时,它将执行上述测试文件

karma start

以下输出取自 IDE Webstorm 其中执行了上述步骤。

Karma 框架的配置

  1. 输出结果在 Karma 浏览器中 Webstorm. 该窗口显示在 karma 框架中定义的所有测试的执行情况。
  2. 这里您可以看到执行的测试的描述是“示例测试”。
  3. 接下来,您可以看到名为“条件为真”的测试本身被执行。
  4. 请注意,所有测试旁边都有绿色的“Ok”图标,表示所有测试都已通过。

AngularJS 单元测试中的 Jasmine 是什么?

Karma 决定测试的运行位置,而 Jasmine 决定测试的编写方式。Jasmine 是一个行为驱动的规范框架,它提供了本页所有示例中使用的词汇表,因此 karma-jasmine 适配器会与运行器一起安装。

茉莉花贡献了四个组成部分:

  • 描述(): 将相关规范分组到一个套件中,并接受被测单元的易读描述。
  • 它(): 声明一个单独的规范。名称应说明要验证的行为,而不是要调用的方法。
  • expect() 带有匹配器: 将实际值与预期值进行比较。常用的匹配器包括 toBe、toEqual、toContain 和 toBeDefined。
  • beforeEach() 和 afterEach(): 在每个规范执行之前和之后运行设置和拆卸程序,这样可以保证每个测试的独立性。

Jasmine 还提供了间谍(spy)功能。间谍会将实际函数替换为录制的替代函数,这样就可以在不调用实时 HTTP 端点的情况下测试控制器。由于 Jasmine 不需要 DOM 和自身的断言库,因此 Karma 和 Jasmine 的组合仍然是运行 AngularJS 最轻量级的方式。 Java脚本单元测试.

测试 AngularJS 控制器

karma 测试框架还具有端到端测试控制器的功能。这包括测试控制器中使用的 $scope 对象。

让我们看一个例子来了解如何实现这一点。

在我们的例子中,

我们首先需要定义一个控制器。该控制器将执行以下步骤

  1. 创建一个 ID 变量并为其赋值 5。
  2. 将 ID 变量分配给 $scope 对象。

我们的测试将测试该控制器的存在,并测试 $scope 对象的 ID 变量是否设置为 5。

首先,我们需要确保满足以下先决条件

通过 npm 安装 angular-mocks 库。这可以通过在命令提示符中执行以下命令来完成。

npm install angular-mocks --save-dev

接下来是修改 karma.conf.js 文件,以确保测试中包含正确的文件。下面的部分仅显示了需要修改的 karma.conf.js 文件部分

files: ['lib/angular.js', 'lib/angular-mocks.js', 'lib/index.js', 'test/*.js']
  • “文件”参数基本上告诉 Karma 运行测试所需的所有文件。
  • 运行 AngularJS 单元测试需要 angular.js 和 angular-mocks.js 文件。
  • index.js 文件将包含控制器的代码
  • 测试文件夹将包含我们所有的 AngularJS 测试

以下是我们的 AngularJS 代码,它将以 Index.js 文件的形式存储在我们应用程序的测试文件夹中。

下面的代码只做了以下事情

  1. 创建 AngularJS 模块 名为sampleApp
  2. 创建一个名为 AngularJSController 的控制器
  3. 创建一个名为 ID 的变量,赋予其值 5,并将其分配给 $scope 对象
var sampleApp = angular.module('sampleApp', []);
sampleApp.controller('AngularJSController', function($scope) {
  $scope.ID = 5;
});

一旦上述代码成功执行,下一步就是创建一个 测试用例 以确保代码已正确编写和执行。

我们的测试代码如下所示。

代码将放在一个名为 ControllerTest.js 的单独文件中,该文件将放在测试文件夹中。以下代码仅执行以下关键操作

  1. beforeEach 函数——此函数用于在测试运行之前加载名为“sampleApp”的 AngularJS 模块。请注意,这是 index.js 文件中的模块名称。
  2. `$controller` 对象是为 `AngularJSController` 控制器创建的模拟对象,该控制器定义在 `index.js` 文件中。在任何类型的单元测试中,模拟对象都代表一个用于测试的虚拟对象。这个模拟对象将模拟控制器的行为。
  3. beforeEach(inject(function(_$controller_) – 这用于在我们的测试中注入模拟对象,以便它表现得像实际的控制器。
  4. var $scope = {}; 这是为 $scope 对象创建的模拟对象。
  5. `var controller = $controller('AngularJSController', { $scope: $scope });` – 这里我们检查是否存在名为“AngularJSController”的控制器。同时,我们将 Index.js 文件中控制器的 `$scope` 对象中的所有变量赋值给测试文件中的 `$scope` 对象。
  6. 最后,我们将 $scope.ID 与 5 进行比较
describe('AngularJSController', function() {
  beforeEach(module('sampleApp'));

  var $controller;

  beforeEach(inject(function(_$controller_){
    $controller = _$controller_;
  }));

  describe('$scope.ID', function() {
    it('Check the scope object', function() {
      var $scope = {};
      var controller = $controller('AngularJSController', { $scope: $scope });
      expect($scope.ID).toEqual(5);
    });
  });
});

上述测试将在 karma 浏览器中运行,并给出与上一主题中显示的相同的通过结果。

测试 AngularJS 指令

Karma 测试框架还具备测试自定义指令的功能。这包括模板。URL在自定义指令中使用的。

让我们看一个例子来了解如何实现这一点。

在我们的示例中,我们将首先定义一个自定义指令,该指令执行以下操作

  1. 创建一个名为 sampleApp 的 AngularJS 模块
  2. 创建一个名为 guru99 的自定义指令
  3. 创建一个函数,返回一个带有标题标签的模板,该标签显示文本“这是 AngularJS 测试”。
var sampleApp = angular.module('sampleApp', []);
sampleApp.directive('guru99', function () {
  return {
    restrict: 'E',
    replace: true,
    template: '<h1>This is AngularJS Testing</h1>'
  };
});

一旦上述代码成功执行,下一步就是创建一个测试用例,以确保代码已正确编写和执行。我们的测试代码如下所示

代码将位于名为 DirectiveTest.js 的单独文件中,该文件将放置在测试文件夹中。以下代码仅执行以下关键操作

  1. beforeEach 函数 – 此函数用于在测试运行之前加载名为“sampleApp”的 AngularJS 模块。
  2. `$compile` 服务用于编译指令。此服务是必需的,必须声明,以便 AngularJS 可以使用它来编译我们的自定义指令。
  3. `$rootScope` 是任何 AngularJS 应用程序的主要作用域。我们在前面的章节中已经见过控制器的 `$scope` 对象。实际上,`$scope` 对象是 `$rootScope` 对象的子对象。之所以在这里声明它,是因为我们通过自定义指令修改了 DOM 中的一个实际 HTML 标签。因此,我们需要使用 `$rootScope` 服务,它会监听并检测 HTML 文档中发生的任何更改。
  4. var element = $compile(“ ”($rootScope) – 这用于检查我们的指令是否按预期注入。该指令注册的名称为驼峰式命名法的 guru99,因此 AngularJS 会匹配小写元素。在标记中。因此,此语句用于进行该检查。
  5. expect(element.html()).toContain(“This is AngularJS Testing”) – 这用于指示 expect 函数应该找到包含 innerHTML 文本“This is AngularJS Testing”的元素(在我们的例子中是 h1 标签)。
describe('Unit testing directives', function() {
  var $compile,
      $rootScope;
  beforeEach(module('sampleApp'));

  beforeEach(inject(function(_$compile_, _$rootScope_){
    $compile = _$compile_;
    $rootScope = _$rootScope_;
  }));

  it('Check the directive', function() {
    // Compile a piece of HTML containing the directive
    var element = $compile("<guru99></guru99>")($rootScope);
    $rootScope.$digest();
    expect(element.html()).toContain("This is AngularJS Testing");
  });
});

上述测试将在 karma 浏览器中运行,并给出与上一主题中显示的相同的通过结果。

对 AngularJS 应用进行端到端测试

业力测试框架以及一个名为 Pro 的框架。tracTor 具备对 Web 应用程序进行端到端测试的功能。

因此,它不仅是指令和控制器的测试,也是对 HTML 页面上可能出现的任何其他内容的测试。

让我们看一个例子来了解如何实现这一点。

在下面的示例中,我们将有一个使用 ng-repeat 指令创建数据表的 AngularJS 应用程序。

  1. 我们首先创建一个名为“tutorial”的变量,并一次性为其分配一些键值对。显示表格时,每个键值对都将用作数据。然后将 tutorial 变量分配给范围对象,以便可以从我们的视图中访问它。
  2. 对于表中的每一行数据,我们都使用 ng-repeat 指令。该指令使用变量 ptutor 遍历教程范围对象中的每个键值对。
  3. 最后,我们使用标签以及键值对(ptutor.Name 和 ptutor.Description)来显示表格数据。
<table>
  <tr ng-repeat="ptutor in tutorial">
    <td>{{ ptutor.Name }}</td>
    <td>{{ ptutor.Description }}</td>
  </tr>
</table>
</div>
<script type="text/javascript">
var app = angular.module('DemoApp', []);
app.controller('DemoController', function($scope) {
  $scope.tutorial = [
    {Name: "Controllers", Description: "Controllers in action"},
    {Name: "Models", Description: "Models and binding data"},
    {Name: "Directives", Description: "Flexibility of Directives"}
  ];
});

一旦上述代码成功执行,下一步就是创建一个测试用例,以确保代码已正确编写和执行。我们的测试代码如下所示

我们的测试实际上要测试 ng-repeat 指令并确保它包含如上例所示的 3 行数据。

首先,我们需要确保满足以下先决条件

安装专业版trac通过 npm 安装 tor 库。这可以通过在命令提示符中执行以下命令来完成。

npm install -g protractor

我们的测试代码如下所示。

代码将放在一个名为 CompleteTest.js 的单独文件中,该文件将放在测试文件夹中。下面的代码只执行以下关键操作

  1. 浏览器功能由专业人士提供。tractor 库,并假设我们的 AngularJS 应用程序(包含上述代码)正在我们的网站上运行。 URL – http://localhost:8080/Guru99 /
  2. var list = element.all(by.repeater('ptutor in tutorial')); - 这行代码实际上是在获取由代码 'ptutor in tutorial' 填充的 ng-repeat 指令。element 和 by.repeater 是 pro 提供的特殊关键字。tractor 库,允许我们获取 ng-repeat 指令的详细信息。
  3. expect(list.count()).toEqual(3); – 最后,我们使用 expect 函数来查看我们确实由于 ng-repeat 指令而获得了表中填充的 3 个项目。
describe('Unit testing end to end', function() {
  beforeEach(function() {
    browser.get('http://localhost:8080/Guru99/');
  });

  it('Check the ng directive', function() {
    var list = element.all(by.repeater('ptutor in tutorial'));
    expect(list.count()).toEqual(3);
  });
});

上述测试将在 karma 浏览器中运行,并给出与上一主题中显示的相同的通过结果。

业力 vs 茉莉 vs 职业tractor:主要区别

本页使用的三个工具经常被混淆,因为它们出现在同一个命令行中。如下表所示,每个工具都位于测试堆栈的不同层级。

参数 因果报应 Jasmine 专业版trac器
类别 测试运行员 规范框架 端到端自动化框架
主要工作 启动浏览器并报告结果 提供描述、说明和预期 在真实浏览器中运行实时应用程序
测试级别 单位和 积分 单位积分 端到端
需要一台正在运行的服务器 没有 没有
配置文件 业力配置文件 没有,它在运行器内部运行。 为tractor.conf.js
当前状态 已弃用,仅供维护使用 积极维护 2023年8月退休

简而言之,Jasmine 编写测试,Karma 执行测试,而 Pro 则负责处理测试结果。tractor 将 Jasmine 的语法扩展到了完全渲染的页面。

AngularJS 单元测试最佳实践

随着应用程序的增长,养成一些简单的习惯就能使 AngularJS 套件保持快速可靠。

  1. 一次只测试一个单元。 在 beforeEach 中只加载被测模块,并模拟每个协作者,以便故障指向单个文件。
  2. 永远不要调用真正的后端。 使用 angular-mocks 中的 $httpBackend 服务返回预设响应。测试随后离线运行,并在每台机器上产生相同的结果。
  3. 保持规范名称具有行为性。 在报告中,“控制器初始化时返回 5”这样的名称比“test1”更易读。
  4. 务必彻底冲洗消化循环系统。 更改作用域数据后调用 $rootScope.$digest(),否则绑定和监视器永远不会更新,断言会因错误的原因而失败。
  5. 衡量覆盖范围,但要谨慎判断。 添加 karma-coverage 可以查看未测试的分支。即使覆盖率很高,但如果断言不够充分,仍然会隐藏缺陷。
  6. 在持续集成模式下运行该套件。 使用无头 Chrome 启动器,以便每次提交时都执行相同的规范,而不仅仅是在开发人员的机器上执行。

应用这些规则可以将上述示例从孤立的演示转化为对遗留 AngularJS 代码库的回归安全网。如需了解更全面的信息,请参阅…… AngularJS教程 系列丛书中。

常见问题

包含该提供程序的模块未加载。请将其添加到 `beforeEach(module('yourModule'))` 中,并确认 `angular-mocks.js` 已在 `karma.conf.js` 中列在 `angular.js` 之后,否则注入器将无法解析依赖项。

安装 karma-chrome-launcher 并将 karma.conf.js 文件中的浏览器选项设置为 ChromeHeadless。这样,即使没有显示服务器,该套件也能在持续集成管道和容器中运行。

是的。AI编码助手可以读取控制器,并生成带有模拟作用域和匹配器的Jasmine规范。 Rev仔细查看输出结果,因为生成的断言通常会重述实现过程,而不是验证预期的行为。

是的。AI 工具会将 Jasmine 规范转换为 Jest 或 Vitest 语法,并标记出没有直接对应项的 angular-mocks 调用。由于依赖注入模式很少能完美转换,因此人工验证仍然至关重要。

大多数团队的目标是控制器和服务端的语句覆盖率达到 70% 到 80%。追求更高的覆盖率通常意味着要测试模板和第三方库,这会增加维护成本,却无法发现真正的缺陷。

总结一下这篇文章: