您的第一个 AngularJS 应用教程第 2 部分:脚手架、构建和测试工具

已发表: 2022-03-11

介绍

由于有许多工具可用于帮助开发 AngularJS 应用程序,许多人认为它是一个极其复杂的框架,但事实并非如此。 这是我开始本教程系列的主要原因之一。

在第一部分中,我们介绍了 AngularJS 框架的基础知识,并从编写我们的第一个应用程序开始。 这篇文章是为初学者设计的。 如果您是一位更有经验的 AngularJS 开发人员,您可能对揭秘指令或 AngularJS 在成长中的初创公司中使用的故事更感兴趣。

在本教程中,我们将抛开应用程序逻辑层,学习如何进行正确的 AngularJS 项目设置,包括脚手架、依赖管理以及为测试做准备(包括单元测试和端到端测试)。 我们将使用以下 AngularJS 工具来完成这项工作:Yeoman、Grunt 和 Bower。 然后,我们将回顾使用 Karma 编写和运行 Jasmine 测试的过程。

Karma、Jasmine、Grunt、Bower、Yeoman……这些工具是什么?

有很多开发工具可以帮助构建 AngularJS、测试 AngularJS 和正确构建应用程序。

如果您使用 JavaScript,那么即使您是 Angular 新手,您也很可能至少已经知道其中的一些工具。 但是为了帮助确保一个共同的基线,我将避免做出任何假设。 让我们简要回顾一下这些技术及其用途:

  • Karma(以前称为 Testacular)是 Google 的 JavaScript 测试运行器,也是测试 AngularJS 的自然选择。 除了允许您在真实浏览器(包括手机/平板浏览器)上运行测试之外,它还与测试框架无关; 这意味着您可以将它与您选择的任何测试框架(例如 Jasmine、Mocha 或 QUnit 等)结合使用。

  • Jasmine 将是我们选择的测试框架,至少在这篇文章中是这样。 如果您曾经使用过它,它的语法与 RSpec 的语法非常相似。 (如果你还没有,别担心;我们将在本教程后面更详细地检查它。)

  • Grunt 是一个任务运行器,可帮助自动执行多项重复性任务,例如缩小、编译(或构建)、测试和设置 AngularJS 应用程序的预览。

  • Bower 是一个包管理器,可帮助您查找和安装所有应用程序依赖项,例如 CSS 框架、JavaScript 库等。 它在 git 上运行,很像 Rails 捆绑器,并且避免了手动下载和更新依赖项的需要。

  • Yeoman 是一个包含 3 个核心组件的工具集:Grunt、Bower 和脚手架工具 Yo。 Yo 在生成器(只是脚手架模板)的帮助下生成样板代码,并为您的项目自动配置 Grunt 和 Bower。 您可以找到几乎所有 JavaScript 框架(Angular、Backbone、Ember 等)的生成器,但由于我们在这里关注 Angular,因此我们将使用生成器 Angular 项目。

那么,我们从哪里开始呢?

嗯,我们需要做的第一件事就是安装我们需要的工具。

如果您还没有安装 git、node.js 和 npm,请继续安装它们。

然后我们将进入命令行并运行以下命令来安装 Yeoman 的工具:

 npm install -g yo grunt-cli bower

哦,别忘了,我们将使用 AngularJS 生成器,所以你也需要安装它:

 npm install -g generator-angular

好的,现在我们准备...

脚手架/生成我们的 AngularJS 应用程序

上次,我们手动从 angular-seed 项目中借用了样板代码。 这一次,我们将让 yo(与 generator-angular 一起)为我们做这件事。

我们需要做的就是创建我们的新项目文件夹,导航到它并运行:

 yo angular

我们将看到一些选项,例如是否包含 Bootstrap 和 Compass。 现在,让我们对 Compass说不Bootstrap 说是。 然后,当提示要包含哪些模块(资源、cookie、清理和路由)时,我们将只选择angular-route.js

现在应该创建我们的项目脚手架(可能需要一分钟),与 Karma 集成并进行所有预配置。

注意:请记住,我们将此处的模块限制为我们在本教程第一部分中构建的应用程序中使用的模块。 当您为自己的项目执行此操作时,由您决定需要包含哪些模块。

现在,由于我们将使用 Jasmine,让我们将karma-jasmine适配器添加到我们的项目中:

 npm install karma-jasmine --save-dev

如果我们希望我们的测试在 Chrome 实例上执行,我们还要添加karma-chrome-launcher

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

好的,如果我们做的一切正确,我们的项目文件树现在应该如下所示:

使用这些 AngularJS 工具的示例项目文件树将如下所示。

我们的静态应用程序代码进入app/目录,而test/目录将包含(是的,你猜对了!)我们的测试。 我们在根目录上看到的文件就是我们的项目配置文件。 关于它们中的每一个都有很多要学习的东西,但现在我们将坚持使用默认配置。 因此,让我们第一次运行我们的应用程序,我们只需使用以下命令即可:

 grunt serve

瞧! 我们的应用程序现在应该会出现在我们面前!

关于 AngularJS 的 Bower

在进入真正重要的部分(即测试)之前,让我们花一点时间了解更多关于 Bower 的信息。 如前所述,Bower 是我们的包管理器。 可以使用bower install命令简单地向我们的项目添加库或插件。 例如,要包含modernizr ,我们需要做的就是以下(当然是在我们的项目目录中):

 bower install modernizr

但是请注意,虽然这确实使modernizr成为我们项目的一部分(它将位于app/bower_components目录中),但我们仍然负责根据需要将其包含在我们的应用程序中(或管理何时应该包含它)与任何手动添加的库有关。 一种方法是简单地将以下<script>标签添加到我们的index.html

 <script src="bower_components/modernizr/modernizr.js"></script>

或者,我们可以使用bower.json文件来管理我们的依赖项。 直到现在仔细执行每一步之后, bower.json文件应该如下所示:

 { "name": "F1FeederApp", "version": "0.0.0", "dependencies": { "angular": "1.2.15", "json3": "~3.2.6", "es5-shim": "~2.1.0", "jquery": "~1.11.0", "bootstrap": "~3.0.3", "angular-route": "1.2.15" }, "devDependencies": { "angular-mocks": "1.2.15", "angular-scenario": "1.2.15" } }

语法是不言自明的,但可以在此处获得更多信息。

然后我们可以添加我们想要的任何其他新依赖项,然后我们只需要以下命令来安装它们:

 bower install

现在让我们编写一些测试!

好的,现在是时候从我们在第一部分中断的地方开始,为我们的 AngularJS 应用程序编写一些测试了。

但首先,我们需要解决一个小问题:虽然 generator-angular 的开发人员基于 angular-seed 项目(这是官方 Angular 样板)的项目模板,但出于某种我不太明白的原因,他们决定更改app文件夹命名约定(将css更改为styles ,将js更改为scripts ,等等)。

结果,我们最初编写的应用程序现在的路径与我们刚刚生成的脚手架不一致。 为了解决这个问题,让我们从这里下载应用程序代码并从现在开始使用该版本(它与我们最初编写的应用程序几乎完全相同,但更新了路径以匹配生成器角度命名)。

下载应用程序后,导航到tests/spec/controllers文件夹并创建一个名为drivers.js的文件,其中包含以下内容:

 describe('Controller: driversController', function () { // First, we load the app's module beforeEach(module('F1FeederApp')); // Then we create some variables we're going to use var driversController, scope; beforeEach(inject(function ($controller, $rootScope, $httpBackend) { // Here, we create a mock scope variable, to replace the actual $scope variable // the controller would take as parameter scope = $rootScope.$new(); // Then we create an $httpBackend instance. I'll talk about it below. httpMock = $httpBackend; // Here, we set the httpBackend standard reponse to the URL the controller is // supposed to retrieve from the API httpMock.expectJSONP( "http://ergast.com/api/f1/2013/driverStandings.json?callback=JSON_CALLBACK").respond( {"MRData": {"StandingsTable": {"StandingsLists" : [{"DriverStandings":[ { "Driver": { "givenName": 'Sebastian', "familyName": 'Vettel' }, "points": "397", "nationality": "German", "Constructors": [ {"name": "Red Bull"} ] }, { "Driver": { "givenName": 'Fernando', "familyName": 'Alonso' }, "points": "242", "nationality": "Spanish", "Constructors": [ {"name": "Ferrari"} ] }, { "Driver": { "givenName": 'Mark', "familyName": 'Webber' }, "points": "199", "nationality": "Australian", "Constructors": [ {"name": "Red Bull"} ] } ]}]}}} ); // Here, we actually initialize our controller, passing our new mock scope as parameter driversController = $controller('driversController', { $scope: scope }); // Then we flush the httpBackend to resolve the fake http call httpMock.flush(); })); // Now, for the actual test, let's check if the driversList is actually retrieving // the mock driver array it('should return a list with three drivers', function () { expect(scope.driversList.length).toBe(3); }); // Let's also make a second test checking if the drivers attributes match against // the expected values it('should retrieve the family names of the drivers', function () { expect(scope.driversList[0].Driver.familyName).toBe("Vettel"); expect(scope.driversList[1].Driver.familyName).toBe("Alonso"); expect(scope.driversList[2].Driver.familyName).toBe("Webber"); }); });

这是我们的driverscontroller的测试套件。 它可能看起来很多代码,但实际上大部分只是模拟数据声明。 让我们快速浏览一下真正重要的元素:

  • describe()方法定义了我们的测试套件。
  • 每个it()都是正确的测试规范。
  • 每个beforeEach()函数都在每个测试之前执行。

这里最重要(并且可能令人困惑)的元素是我们在httpMock变量上实例化的$httpBackend服务。 该服务充当假后端,并在测试运行时响应我们的 API 调用,就像我们的实际服务器在生产中所做的那样。 在这种情况下,使用expectJSONP()函数,我们将其设置为拦截对给定 URL 的任何 JSONP 请求(与我们用来从服务器获取信息相同的 URL),而是返回一个包含三个驱动程序的静态列表,模仿真实的服务器响应。 这使我们能够确定应该从控制器返回什么。 因此,我们可以使用expect()函数将结果与预期结果进行比较。 如果它们匹配,则测试将通过。

只需使用以下命令即可运行测试:

 grunt test

驱动程序详细信息控制器 ( drivercontroller ) 的测试套件应该与我们刚刚看到的非常相似。 我建议您尝试自己将其作为练习来解决(或者,如果您不适应,也可以在这里查看)。

那么端到端 AngularJS 测试呢?

Angular 团队最近推出了一种名为 Protractor 的用于端到端测试的新运行程序。 它使用 webdriver 与运行在浏览器中的应用程序进行交互,并且默认使用 Jasmine 测试框架,因此语法将与我们的单元测试高度一致。

然而,由于 Protractor 是一个相当新的工具,它与 Yeoman 堆栈和generator-angular的集成仍然需要大量的配置工作。 考虑到这一点,并且我打算使本教程尽可能简单,我的计划是在未来专门发表一篇文章,专门介绍 AngularJS 中的端到端测试。

结论

在本教程系列的这一点上,我们已经学习了如何使用yo搭建我们的 Angular 应用程序,使用bower管理它的依赖项,以及使用karmaprotractor编写/运行一些测试。 但请记住,本教程仅作为对这些 AngularJS 工具和实践的介绍; 我们没有在这里深入分析它们中的任何一个。

我们的目标只是帮助您开始这条道路。 从这里开始,您可以继续学习这个惊人的框架和工具套件。

附录:作者的一些(重要)注释

读完本教程后,有些人可能会问: “等等。 在真正开始编写应用程序之前,您不应该做所有这些事情吗? 这不应该是本教程的一部分吗?”

我对此的简短回答是否定的。 正如我们在第一部分中看到的那样,您实际上不需要知道所有这些东西来编写您的第一个 Angular 应用程序。 相反,我们在本文中讨论的大多数工具旨在帮助您优化开发工作流程和实践测试驱动开发 (TDD)。

而说到TDD,TDD最基本的概念当然是一个健全的概念; 即,在编写代码之前编写测试。 不过,有些人认为这个概念太过分了。 TDD 是一种开发实践,而不是一种学习方法。 因此,在编写代码之前编写测试确实很有意义,而在学习如何编码之前学习如何编写测试则没有。

我个人认为这就是为什么官方 Angular 教程可能感觉如此复杂,并且对于以前没有前端 MVC/TDD 经验的人来说几乎不可能遵循的主要原因。 这是我开始本教程系列的主要原因之一。

我对那些学习驾驭 AngularJS 世界的人的个人建议是:不要对自己太苛刻。 您不需要一次全部学习所有内容(尽管人们告诉您否则!)。 根据您之前使用其他前端/测试框架的经验,AngularJS 一开始可能很难理解。 所以学习所有你需要学习的东西,直到你能够编写自己的简单应用程序,然后,一旦你对框架的基础知识感到满意,你就可以考虑选择和应用最适合的长期开发实践你。

当然,这是我的拙见,并不是每个人都会同意这种方法(一旦我发布了这个,Angular 开发团队可能会派一个雇佣杀手来跟踪我),但这是我的愿景,我很确定有很多人外面谁会同意我的看法。

相关: Angular 6 教程:具有新功能的新功能