作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Raoni Boaventura的头像

Raoni Boaventura

Raoni的BCS和十年的web开发经验见证了他领导和贡献了大量使用RoR的项目, JS, and PHP, among others.

Years of Experience

16

Share

Introduction

有许多可用的工具来帮助开发 AngularJS applications, 很多人的印象是它是一个极其复杂的框架, 事实并非如此. 这就是我开始本教程系列的主要原因之一.

In part one 我们介绍了AngularJS框架的基础知识,并开始编写我们的第一个应用程序. 这篇文章是为初学者设计的. If you’re a more 经验丰富的AngularJS开发人员,你可能会更感兴趣 demystifying directives or a story of AngularJS在一个成长中的初创公司中使用.

In this tutorial, 我们将把应用逻辑层放在一边,学习如何进行正确的AngularJS项目设置, including scaffolding, dependency management, 并为测试做准备(单元和端到端). 我们将使用以下AngularJS工具:Yeoman、Grunt和Bower. 然后,我们将回顾使用Karma编写和运行Jasmine测试的过程.

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

有很多开发工具可以帮助搭建AngularJS, 测试AngularJS并正确构建应用.

如果你使用JavaScript, 您很可能已经知道至少其中一些工具, even if you’re new to Angular. 但是为了帮助确保一个共同的基线,我将避免做任何假设. 让我们简要回顾一下这些技术以及它们的用途:

  • Karma (以前称为Testacular)是Google的JavaScript测试运行器,也是测试AngularJS的自然选择. 除了允许您在真实浏览器(包括手机/平板电脑浏览器)上运行测试之外, it is also test framework agnostic; which means that you can use it in conjunction with any 您选择的测试框架(例如Jasmine, Mocha, or QUnit, among others).

  • Jasmine 它会是我们的测试框架选择吗,至少在本文中是这样. 的语法非常相似 RSpec,如果你用过的话. (If you haven’t, don’t worry; we’ll check it out in greater detail later in this tutorial.)

  • Grunt 任务运行器是否有助于自动执行一些重复性任务,例如 minification编译(或构建)、测试和设置你的AngularJS应用的预览.

  • Bower 包管理器是否可以帮助您查找和安装所有应用程序依赖项, such as CSS frameworks, JavaScript libraries, and so on. 它在git上运行,就像 Rails bundler,避免了手动下载和更新依赖项的需要.

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

So, where do we start?

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

If you don’t have git, node.js, and npm 已经安装,继续安装它们.

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

NPM install -g yo grunt-cli bower

Oh, and don’t forget, 我们将使用AngularJS生成器,所以你也需要安装它:

NPM install -g generator-angular

OK, now we’re ready to…

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

Last time中手动借用了样板代码 angular-seed project. 这一次,我们将让你(与generator-angular一起)为我们做这件事.

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

yo angular

我们将看到一些选项,例如是否包含 Bootstrap and Compass. For now, let’s say no to Compass and yes to Bootstrap. Then, 当提示包含哪些模块时(资源, cookies, sanitize and route), we’ll select only angular-route.js.

我们的项目脚手架现在应该被创建了(这可能需要一分钟), 集成了Karma和所有预配置.

Note: 请记住,我们将这里的模块限制为我们在构建的应用程序中使用的模块 part one of this tutorial. 当你在做自己的项目时, 这将由您决定需要包括哪些模块.

现在,因为我们要用Jasmine,让我们加入 karma-jasmine adapter to our project:

NPM安装karma-jasmine——save-dev

如果我们想让我们的测试在Chrome实例上执行,让我们也添加 karma-chrome-launcher:

NPM安装karma-chrome-launcher——save-dev

好了,如果我们做对了,我们的项目文件树现在应该是这样的:

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

我们的静态应用程序代码进入 app/ directory and the test/ 目录将包含(是的,您猜对了!) our tests. 我们在根目录下看到的文件是我们的项目配置文件. 他们每个人都有很多值得学习的地方, 但是现在我们将坚持使用默认配置. 因此,让我们第一次运行我们的应用程序,我们可以简单地使用以下命令:

grunt serve

And voila! 我们的应用程序现在应该弹出在我们面前!

简单介绍一下面向AngularJS的鲍尔

在进入真正重要的部分(1)之前.e.(测试),让我们花一点时间来了解更多 Bower. 如前所述,鲍尔是我们的包管理器. 将库或插件添加到项目中可以简单地使用 bower install command. For example, to include modernizr,我们所需要做的就是(当然,在我们的项目目录中):

bower install modernizr

请注意,虽然这确实使 modernizr 我们项目的一部分(它将位于 app/bower_components directory), 我们仍然有责任在我们的应用程序中包含它 (或管理何时应该包含它),就像我们需要对任何手动添加的库做的那样. 这样做的一种方法是简单地添加以下内容

或者,我们可以使用 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"
  }
}

语法是不言自明的,但可以获得更多信息 here.

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

bower install

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

好了,现在是时候从我们离开的地方开始了 part one 并为我们的AngularJS应用编写一些测试.

但首先,我们需要解决一个小问题:尽管 generator-angular 基于他们的项目模板 angular-seed 项目(这是官方的Angular样板), 出于某种我不太明白的原因, 他们决定改变 app 文件夹命名约定(更改) css to styles, js to scripts, and so on).

As a result, 我们最初编写的应用程序现在的路径与我们刚刚生成的scaffold不一致. 为了解决这个问题,让我们从 here 并且从这一点开始使用那个版本(它基本上是我们最初编写的相同应用程序), 但是,随着路径更新以匹配generator-angular命名).

下载应用程序后,导航到 tests/spec/controllers 文件夹并创建一个名为 drivers.js 载有下列内容:

description ('Controller: driversController', function () {

  //首先,我们加载应用的模块
  beforeEach(模块(' F1FeederApp '));

  //然后我们创建一些将要使用的变量
  变量驱动控制器,范围;

  beforeEach(注入函数($controller, $rootScope, $httpBackend) {

    //在这里,我们创建一个模拟作用域变量,以取代实际的$scope变量
    //控制器将作为参数
    scope = $rootScope.$new();

    //然后我们创建一个$httpBackend实例. 我将在下面讨论它.
    httpMock = $httpBackend;

    //这里,我们将httpBackend标准响应设置为控制器所在的URL
    //应该从API检索
    httpMock.expectJSONP(
	  "http://ergast.com/api/f1/2013/driverStandings.json?回调= JSON_CALLBACK”).respond(
      {"MRData": {"StandingsTable": {" standingslist ":[{"DriverStandings":[
        {
          "Driver": {
              “givenName”:“塞巴斯蒂安”,
              "familyName": 'Vettel'
          },
          "points": "397",
          “国籍”:“德国”,
          "Constructors": [
              {"name": "Red Bull"}
          ]
        },
        {
          "Driver": {
              "givenName": 'Fernando',
              "familyName": 'Alonso'
          },
          "points": "242",
          “国籍”:“西班牙语”,
          "Constructors": [
              {"name": "Ferrari"}
          ]
        },
        {
          "Driver": {
              "givenName": 'Mark',
              "familyName": 'Webber'
          },
          "points": "199",
          “国籍”:“澳大利亚”,
          "Constructors": [
              {"name": "Red Bull"}
          ]
        }
      ]}]}}}
    );

    //这里,我们实际初始化我们的控制器,传递我们的新模拟范围作为参数
    drivercontroller = $controller(' drivercontroller ', {
      $scope: scope
    });

    //然后刷新httpBackend来解析虚假的http调用
    httpMock.flush();

  }));


  //现在,对于实际的测试,让我们检查driversList是否真的在检索
  //模拟驱动数组
  它('应该返回一个包含三个驱动程序的列表'),function () {
    expect(scope.driversList.length).toBe(3);
  });

  //让我们再做一个测试,检查驱动程序属性是否匹配
  // the expected values
  It('应该检索司机的姓氏',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. 它可能看起来像很多代码,但实际上大部分只是模拟数据声明. 让我们快速浏览一下真正重要的元素:

  • The describe() 方法定义了我们的测试套件.
  • Each it() is a proper test spec.
  • Every beforeEach() 函数在每个测试之前执行.

这里最重要(也可能令人困惑)的元素是 $httpBackend 实例化的服务 httpMock variable. 该服务充当伪后端,并在测试运行时响应我们的API调用, 就像我们实际的服务器在生产环境中所做的那样. In this case, using the expectJSONP() function, 我们将其设置为拦截对给定URL(与我们用于从服务器获取信息的URL相同)的任何JSONP请求, 返回一个包含三个驱动程序的静态列表, 模拟真实服务器响应. 这使我们能够确切地知道应该从控制器返回什么. 因此,我们可以将结果与预期的结果进行比较 expect() function. 如果匹配,测试就会通过.

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

grunt test

驱动细节控制器的测试套件(drivercontroller)应该和我们刚才看到的很相似. 我建议你试着自己找出答案,作为练习(或者你可以看一看) here(如果你做不到的话).

端到端AngularJS测试呢?

Angular团队最近为端到端测试引入了一个新的运行器 Protractor. It uses webdriver 与浏览器中运行的应用程序进行交互,默认情况下它还使用Jasmine测试框架, 因此,语法将与我们的单元测试高度一致.

由于Protractor是一个相当新的工具,因此它与Yeoman堆栈和 generator-angular 仍然需要大量的配置工作. With that in mind, 我的目的是使本教程尽可能简单, 我的计划是在未来专门写一篇文章来深入介绍AngularJS的端到端测试.

Conclusion

在本系列教程中,我们已经学习了如何用 yo,管理它的依赖 bower编写/运行一些测试 karma and protractor. Bear in mind, though, that this tutorial is meant only as an introduction to these AngularJS tools and practices; we didn’t analyze any of them here in great depth.

我们的目标只是帮助你开始走上这条道路. From here, 这取决于您是否继续学习这个令人惊叹的框架和工具套件.

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

看完这篇教程,有些人可能会问, “Wait. 你不应该在真正开始编写应用程序之前完成所有这些事情吗? 这不应该是本教程的第一部分吗?”

我的简短回答是 no. 正如我们在第一部分中看到的,在编写你的第一个Angular应用时,你实际上并不需要知道所有这些东西. Rather, 我们在这篇文章中讨论的大多数工具都是为了帮助您优化开发工作流程和实践测试驱动开发(TDD)而设计的。.

And speaking of TDD, the most basic concept of TDD is certainly a sound one; namely, 在编写代码之前先编写测试. 然而,有些人把这个概念看得太过分了. TDD是一种开发实践, not a learning method. Accordingly, writing 在编写代码之前进行测试确实很有意义,然而 learning how 在学习如何编码之前编写测试是不可取的.

我个人认为这就是为什么官方的Angular教程会让人觉得很复杂,对于没有前端MVC/TDD经验的人来说几乎不可能理解的主要原因. 这就是我开始本教程系列的主要原因之一.

对于那些学习如何驾驭AngularJS世界的人,我的个人建议是:不要对自己太苛刻. 你不需要一次学习所有的东西(尽管人们告诉你不是这样)!). 这取决于您之前使用其他前端/测试框架的经验, 一开始,AngularJS很难理解. 因此,学习所有你需要学习的东西,直到你能够编写自己的简单应用程序, 一旦您熟悉了框架的基础知识, 您可以选择并应用最适合您的长期开发实践.

Of course, 这是我的拙见,并不是每个人都同意这种方法(一旦我发表了这篇文章,Angular开发团队可能会派一个雇佣杀手来找我)。, 但这就是我的想法,我很确定有很多人会同意我的观点.

就这一主题咨询作者或专家.
Schedule a call
Raoni Boaventura的头像
Raoni Boaventura

Located in 圣保罗州-巴西圣保罗州

Member since September 20, 2012

About the author

Raoni的BCS和十年的web开发经验见证了他领导和贡献了大量使用RoR的项目, JS, and PHP, among others.

Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

Years of Experience

16

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

Toptal Developers

Join the Toptal® community.

\n\n\n

或者,我们可以使用 bower.json 文件来管理我们的依赖项. 直到现在,在仔细遵循了每一步之后 bower.json 文件应该是这样的:

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

语法是不言自明的,但可以获得更多信息 here.

\n\n

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

\n\n
bower install\n
\n\n

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

\n\n

好了,现在是时候从我们离开的地方开始了 part one 并为我们的AngularJS应用编写一些测试.

\n\n

但首先,我们需要解决一个小问题:尽管 generator-angular 基于他们的项目模板 angular-seed 项目(这是官方的Angular样板), 出于某种我不太明白的原因, 他们决定改变 app 文件夹命名约定(更改) css to styles, js to scripts, and so on).

\n\n

As a result, 我们最初编写的应用程序现在的路径与我们刚刚生成的scaffold不一致. 为了解决这个问题,让我们从 here 并且从这一点开始使用那个版本(它基本上是我们最初编写的相同应用程序), 但是,随着路径更新以匹配generator-angular命名).

\n\n

下载应用程序后,导航到 tests/spec/controllers 文件夹并创建一个名为 drivers.js 载有下列内容:

\n\n
description ('Controller: driversController', function () {\n\n  //首先,我们加载应用的模块\n  beforeEach(模块(' F1FeederApp '));\n\n  //然后我们创建一些将要使用的变量\n  变量驱动控制器,范围;\n\n  beforeEach(注入函数($controller, $rootScope, $httpBackend) {\n\n    //在这里,我们创建一个模拟作用域变量,以取代实际的$scope变量\n    //控制器将作为参数\n    scope = $rootScope.$new();\n\n    //然后我们创建一个$httpBackend实例. 我将在下面讨论它.\n    httpMock = $httpBackend;\n\n    //这里,我们将httpBackend标准响应设置为控制器所在的URL\n    //应该从API检索\n    httpMock.expectJSONP(\n\t  \"http://ergast.com/api/f1/2013/driverStandings.json?callback=JSON_CALLBACK\").respond(\n      {\"MRData\": {\"StandingsTable\": {\"StandingsLists\" : [{\"DriverStandings\":[\n        {\n          \"Driver\": {\n              \"givenName\": 'Sebastian',\n              \"familyName\": 'Vettel'\n          },\n          \"points\": \"397\",\n          \"nationality\": \"German\",\n          \"Constructors\": [\n              {\"name\": \"Red Bull\"}\n          ]\n        },\n        {\n          \"Driver\": {\n              \"givenName\": 'Fernando',\n              \"familyName\": 'Alonso'\n          },\n          \"points\": \"242\",\n          \"nationality\": \"Spanish\",\n          \"Constructors\": [\n              {\"name\": \"Ferrari\"}\n          ]\n        },\n        {\n          \"Driver\": {\n              \"givenName\": 'Mark',\n              \"familyName\": 'Webber'\n          },\n          \"points\": \"199\",\n          \"nationality\": \"Australian\",\n          \"Constructors\": [\n              {\"name\": \"Red Bull\"}\n          ]\n        }\n      ]}]}}}\n    );\n\n    //这里,我们实际初始化我们的控制器,传递我们的新模拟范围作为参数\n    drivercontroller = $controller(' drivercontroller ', {\n      $scope: scope\n    });\n\n    //然后刷新httpBackend来解析虚假的http调用\n    httpMock.flush();\n\n  }));\n\n\n  //现在,对于实际的测试,让我们检查driversList是否真的在检索\n  //模拟驱动数组\n  它('应该返回一个包含三个驱动程序的列表'),function () {\n    expect(scope.driversList.length).toBe(3);\n  });\n\n  //让我们再做一个测试,检查驱动程序属性是否匹配\n  // the expected values\n  It('应该检索司机的姓氏',function () {\n    expect(scope.driversList[0].Driver.familyName).toBe(\"Vettel\");\n    expect(scope.driversList[1].Driver.familyName).toBe(\"Alonso\");\n    expect(scope.driversList[2].Driver.familyName).toBe(\"Webber\");\n  });\n\n});\n
\n\n

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

\n\n\n\n

这里最重要(也可能令人困惑)的元素是 $httpBackend 实例化的服务 httpMock variable. 该服务充当伪后端,并在测试运行时响应我们的API调用, 就像我们实际的服务器在生产环境中所做的那样. In this case, using the expectJSONP() function, 我们将其设置为拦截对给定URL(与我们用于从服务器获取信息的URL相同)的任何JSONP请求, 返回一个包含三个驱动程序的静态列表, 模拟真实服务器响应. 这使我们能够确切地知道应该从控制器返回什么. 因此,我们可以将结果与预期的结果进行比较 expect() function. 如果匹配,测试就会通过.

\n\n

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

\n\n
grunt test\n
\n\n

驱动细节控制器的测试套件(drivercontroller)应该和我们刚才看到的很相似. 我建议你试着自己找出答案,作为练习(或者你可以看一看) here(如果你做不到的话).

\n\n

端到端AngularJS测试呢?

\n\n

Angular团队最近为端到端测试引入了一个新的运行器 Protractor. It uses webdriver 与浏览器中运行的应用程序进行交互,默认情况下它还使用Jasmine测试框架, 因此,语法将与我们的单元测试高度一致.

\n\n

由于Protractor是一个相当新的工具,因此它与Yeoman堆栈和 generator-angular 仍然需要大量的配置工作. With that in mind, 我的目的是使本教程尽可能简单, 我的计划是在未来专门写一篇文章来深入介绍AngularJS的端到端测试.

\n\n

Conclusion

\n\n

在本系列教程中,我们已经学习了如何用 yo,管理它的依赖 bower编写/运行一些测试 karma and protractor. Bear in mind, though, that this tutorial is meant only as an introduction to these AngularJS tools and practices; we didn’t analyze any of them here in great depth.

\n\n

我们的目标只是帮助你开始走上这条道路. From here, 这取决于您是否继续学习这个令人惊叹的框架和工具套件.

\n\n

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

\n\n

看完这篇教程,有些人可能会问, “Wait. 你不应该在真正开始编写应用程序之前完成所有这些事情吗? 这不应该是本教程的第一部分吗?”

\n\n

我的简短回答是 no. 正如我们在第一部分中看到的,在编写你的第一个Angular应用时,你实际上并不需要知道所有这些东西. Rather, 我们在这篇文章中讨论的大多数工具都是为了帮助您优化开发工作流程和实践测试驱动开发(TDD)而设计的。.

\n\n

And speaking of TDD, the most basic concept of TDD is certainly a sound one; namely, 在编写代码之前先编写测试. 然而,有些人把这个概念看得太过分了. TDD是一种开发实践, not a learning method. Accordingly, writing 在编写代码之前进行测试确实很有意义,然而 learning how 在学习如何编码之前编写测试是不可取的.

\n\n

我个人认为这就是为什么官方的Angular教程会让人觉得很复杂,对于没有前端MVC/TDD经验的人来说几乎不可能理解的主要原因. 这就是我开始本教程系列的主要原因之一.

\n\n

对于那些学习如何驾驭AngularJS世界的人,我的个人建议是:不要对自己太苛刻. 你不需要一次学习所有的东西(尽管人们告诉你不是这样)!). 这取决于您之前使用其他前端/测试框架的经验, 一开始,AngularJS很难理解. 因此,学习所有你需要学习的东西,直到你能够编写自己的简单应用程序, 一旦您熟悉了框架的基础知识, 您可以选择并应用最适合您的长期开发实践.

\n\n

Of course, 这是我的拙见,并不是每个人都同意这种方法(一旦我发表了这篇文章,Angular开发团队可能会派一个雇佣杀手来找我)。, 但这就是我的想法,我很确定有很多人会同意我的观点.

\n","as":"div","isContentFit":true,"sharingWidget":{"url":"http://1imq.1e1v.com/angular-js/your-first-angularjs-app-part-2-scaffolding-building-and-testing","title":"AngularJS工具:搭建、测试教程 & More","text":null,"providers":["linkedin","twitter","facebook"],"gaCategory":null,"domain":{"name":"developers","title":"Engineering","vertical":{"name":"developers","title":"Developers","publicUrl":"http://1imq.1e1v.com/developers"},"publicUrl":"http://1imq.1e1v.com/developers/blog"},"hashtags":"JavaScript,AngularJS,FrontEnd,Karma,Jasmine,Grunt,Bower,Yeoman"}}