)
原文zh.annas-archive.org/md5/81C516831B5BF457C3508E2F3CF1895F译者飞龙协议CC BY-NC-SA 4.0第十二章测试和调试测试对于构建可投入生产的应用程序至关重要。在单元测试中我们独立于外部来源测试组件以确保其按预期工作。Angular 2 自带了测试功能。在本章中我们将查看对以下元素执行单元测试组件服务管道指令Angular 2 测试工具在 Angular 2 中用于辅助测试的一些工具如下茉莉花KarmaProtractorAngular 2 测试平台让我们更深入地了解每一个。茉莉花Jasmine 是一个开源的测试框架。它使用行为驱动的符号带来了改进的测试。茉莉花的主要概念在我们深入实际测试案例之前这里有一些您应该了解的 Jasmine 概念套件这些是由describe块编写和描述的。它们以函数的形式出现。规范定义itstringfunction函数。此函数的主体包含实际的断言。期望这些是评估为布尔值的断言。这用于查看输入是否等于预期值。匹配器这些是常见断言的辅助工具例如toBeexpectedtoEqualexpected。KarmaKarma 是由 Angular 团队创建的 JavaScript 测试运行器。Karma 可以成为项目的持续集成过程的一部分也可以成为其开发的一部分。ProtractorProtractor 是用于 Angular 应用的端到端测试框架。使用 Protractor您可以设置期望并根据我们的假设进行测试。顾名思义端到端测试不仅确保系统自身正常工作还验证其与外部系统的功能。它们探索应用程序的最终用户体验。Angular 测试平台Angular 测试平台用于测试类与 Angular 和 DOM 的交互。Angular 测试平台使我们能够检查类的实例而不依赖于 Angular 或注入的值。在本章中我们将专注于 Jasmine 和 Karma 进行测试。当使用 Angular-CLI 创建新项目时将创建包含使用 Protractor 的端到端测试的e2e文件夹以及karma.conf.js和protractor.conf.js文件这些是 Karma 和 Protractor 测试的配置文件。使用 Karma与 Jasmine 一起使用 Karma您可以在运行应用程序时测试您的代码因为 Karma 为测试创建了一个浏览器环境。除了您的浏览器您还可以在其他设备上测试您的代码比如手机和平板电脑。Jasmine 是用于测试 JavaScript 代码的行为驱动开发框架。Jasmine 无需依赖不需要 DOM并经常与 Karma 一起使用。我们现在将继续创建一个新项目并测试其元素。创建一个新项目我们将使用以下命令创建一个名为Angular-test的新项目ngnewAngular-test安装 Karma CLI要安装 Karma CLI请输入以下命令npm install-g karma-cli我们的测试将在.spec.ts文件中执行。在./app/文件夹中创建一个新的测试文件sampletest.spec.ts并复制以下内容// ./app/sampletest.spec.tsdescribe(Sample Test,(){it(true is true,()expect(true).toBe(true));});import{AppComponent}from./app.component;describe(AppComponent,(){beforeEach(function(){this.appnewAppComponent();});it(should have hello property,function(){expect(this.app.hello).toBe(Hello, World!);});});在上述代码中我们首先编写一个示例测试来展示 Jamine 中的主要概念。我们创建一个测试套件定义我们的测试套件并编写期望。在示例测试中我们进行了一个简单的检查看看true是否与true相同。我们还为AppComponent编写了一个简单的测试。我们期望该组件具有一个hello属性其值等于Hello, World。让我们通过更新app.component.ts来确保测试通过privatehello:stringHello, World!;通过创建带有.spec.ts扩展名的文件我们满足了 Karma 的配置要求。您也可以测试多个组件。例如当您通过 Angular CLI 创建新组件时它会自动为组件创建测试文件.spec.ts这些文件只是测试组件是否与其他组件正确配合工作。对于 Angular 来说惯例是为每个.ts文件都有一个.spec.ts文件。当您使用ng test命令时这些文件将通过 Karma 任务运行器使用 Jasmine JavaScript 测试框架运行。配置 Karma为了配置我们的 Karma我们需要更新karma.conf.js文件。默认文件的内容如下// ./karma.conf.js.module.exportsfunction(config){config.set({basePath:,frameworks:[jasmine,angular-cli],plugins:[require(karma-jasmine),require(karma-chrome-launcher),require(karma-remap-istanbul),require(angular-cli/plugins/karma)],files:[{pattern:./src/test.ts,watched:false}],preprocessors:{./src/test.ts:[angular-cli]},remapIstanbulReporter:{reports:{html:coverage,lcovonly:./coverage/coverage.lcov}},angularCli:{config:./angular-cli.json,environment:dev},reporters:[progress,karma-remap-istanbul],port:9876,colors:true,logLevel:config.LOG_INFO,autoWatch:true,browsers:[PhantomJS],singleRun:false});};在这里我们展示了将使用 PhantomJS 浏览器将使用 Jasmine 测试框架和 Webpack 进行文件捆绑。测试组件组件是 Angular 的核心。它们是整个框架构建的核心。我们将探讨组件是什么为什么它很重要以及如何测试它。我们的测试策略围绕验证组件的属性和方法的正确性展开。在为组件编写单元测试时我们手动初始化组件并注入任何依赖项而不是启动应用程序。TestBed函数将用于测试组件这是所有 Angular 测试接口的主要入口。它将使我们能够创建我们的组件以用于运行单元测试。TestBed是为 Angular 应用程序和库编写单元测试的主要 API。创建一个名为sample的新组件ng generate component sample这将自动生成.ts和.spec.ts文件。我们还将在生成的.spec.ts文件中添加一些测试以了解测试的工作原理//sample.component.tsimport{Component,OnInit}fromangular/core;Component({selector:app-sample,templateUrl:./sample.component.html,styleUrls:[./sample.component.css]})exportclassSampleComponentimplementsOnInit{titleTest Sample Component;constructor(){}ngOnInit(){}}这是更新后的测试规范//sample.component.spec.tsimport{ComponentFixture,TestBed}fromangular/core/testing;import{By}fromangular/platform-browser;import{DebugElement}fromangular/core;import{SampleComponent}from./sample.component;describe(SampleComponent (inline template),(){letcomponent:SampleComponent;letfixture:ComponentFixtureSampleComponent;// For Debugging HTML Elementsletdebug:DebugElement;lethtmlElem:HTMLElement;beforeEach((){TestBed.configureTestingModule({declarations:[SampleComponent],// Our Test sample component});// Get the ComponentFixturefixtureTestBed.createComponent(SampleComponent);componentfixture.componentInstance;// SampleComponent test instance// CSS Element selectordebugfixture.debugElement.query(By.css(h1));htmlElemdebug.nativeElement;});it(dont showanytitle onDOMuntil we calldetectChanges,(){expect(htmlElem.textContent).toEqual();});it(should display original title,(){fixture.detectChanges();expect(htmlElem.textContent).toContain(component.title);});it(should display a different test title,(){component.titleDifferent Test Title;fixture.detectChanges();expect(htmlElem.textContent).toContain(Different Test Title);});});TestBed中的createComponent方法创建组件的实例。这些测试告诉 Angular 何时通过fixture.detectChanges()我们从createComponent中获得执行变更检测。默认情况下TestBed.createComponent不会触发变更检测。这就是为什么我们测试中的特定部分不会在 DOM 上显示更改。利用angular/core/testing中的ComponentFixtureAutoDetect可以全局应用自动检测TestBed.configureTestingModule({declarations:[SampleComponent],providers:[{provide:ComponentFixtureAutoDetect,useValue:true}]})测试服务让我们创建一个示例服务。我们的服务只有一个方法返回应用程序可用用户的数组//a simple serviceexportclassUsersService{get(){return[Ken,Mark,Chris];}}我们使用beforeEach方法实例化服务。即使我们只有一个规范这也是一个好习惯。我们正在检查每个单独的用户和总数describe(Service: UsersService,(){letservice;beforeEach(()TestBed.configureTestingModule({providers:[UsersService]}));beforeEach(inject([UsersService],s{services;}));it(should return available users,(){letusersservice.get();expect(users).toContain(en);expect(users).toContain(es);expect(users).toContain(fr);expect(users.length).toEqual(3);});});使用 HTTP 进行测试让我们首先创建一个users.serviceHttp.ts文件// users.serviceHttp.tsexportclassUsersServiceHttp{constructor(privatehttp:Http){}get(){returnthis.http.get(api/users.json).map(responseresponse.json());}}在这种情况下它使用http.get()从 JSON 文件中获取数据。然后我们使用Observable.map()使用json()将响应转换为最终结果。这个测试与之前的测试之间的区别在于使用了异步测试//users.serviceHttp.spec.tsdescribe(Service: UsersServiceHttp,(){letservice;//setupbeforeEach(()TestBed.configureTestingModule({imports:[HttpModule],providers:[UsersServiceHttp]}));beforeEach(inject([UsersServiceHttp],s{services;}));//specsit(should return available users,async((){service.get().subscribe(x{expect(x).toContain(en);expect(x).toContain(es);expect(x).toContain(fr);expect(x.length).toEqual(3);});}));})使用 MockBackend 进行测试一个更明智的方法是用 MockBackend 替换 HTTP 调用。为此我们可以使用beforeEach()方法。这将允许我们模拟我们的响应并避免访问真实的后端从而提高我们的测试//users.serviceHttp.spec.tsdescribe(MockBackend: UsersServiceHttp,(){letmockbackend,service;//setupbeforeEach((){TestBed.configureTestingModule({imports:[HttpModule],providers:[UsersServiceHttp,{provide:XHRBackend,useClass:MockBackend}]})});beforeEach(inject([UsersServiceHttp,XHRBackend],(_service,_mockbackend){service_service;mockbackend_mockbackend;}));//specsit(should return mocked response (sync),(){letresponse[ru,es];mockbackend.connections.subscribe(connection{connection.mockRespond(newResponse(newResponseOptions({body:JSON.stringify(response)})));service.get().subscribe(users{expect(users).toContain(ru);expect(users).toContain(es);expect(users.length).toBe(2);});});});我们制作了模拟响应。因此当我们最终调用我们的服务时它会得到预期的结果。测试一个指令在 Angular 中指令装饰器用于装饰一个负责根据定义的方法和逻辑扩展 DOM 中组件的类。以更改背景的指令为例import{Directive,HostBinding,HostListener}fromangular/core;Directive({selector:[appBackgroundChanger]})exportclassBackgroundChangerDirective{HostBinding(style.background-color)backgroundColor:string;HostListener(mouseover)onHover(){this.backgroundColorred;}HostListener(mouseout)onLeave(){this.backgroundColorinherit;}}我们将使用一个属性指令logClicks它记录宿主元素上的点击次数。让我们创建一个container组件。这将是我们的宿主重现我们指令发出的事件Component({selector:container,template:div log-clicks (changes)changed($event)/div,directives:[logClicks]})exportclassContainer{Output()changesnewEventEmitter();changed(value){this.changes.emit(value);}}以下是测试规范describe(Directive: logClicks,(){letfixture;letcontainer;letelement;//setupbeforeEach((){TestBed.configureTestingModule({declarations:[Container,logClicks]});fixtureTestBed.createComponent(Container);containerfixture.componentInstance;// to access properties and methodselementfixture.nativeElement;// to access DOM element});//specsit(should increment counter,fakeAsync((){letdivelement.querySelector(div);//set up subscribercontainer.changes.subscribe(x{expect(x).toBe(1);});//trigger click on containerdiv.click();//execute all pending asynchronous callstick();}));})beforeEach方法用于将创建组件的逻辑与测试分开。建议使用 DOM API 来触发容器上的点击。测试管道Angular 中的管道是可以将输入数据转换为用户可读格式的函数。以下是我们src文件夹中一个名为capitalise的自定义管道的示例使用了标准的String.toUpperCase()。这只是一个例子Angular 已经有了自己的管道来进行大写转换//capitalise.pipe.tsimport{Pipe,PipeTransform}fromangular/core;Pipe({name:capitalise})exportclassCapitalisePipeimplementsPipeTransform{transform(value:string):string{if(typeofvalue!string){thrownewError(Requires a String as input);}returnvalue.toUpperCase();}}capitalise.pipe.spec.ts文件将如下所示describe(Pipe: CapitalisePipe,(){letpipe;//setupbeforeEach(()TestBed.configureTestingModule({providers:[CapitalisePipe]}));beforeEach(inject([CapitalisePipe],p{pipep;}));//specsit(should work with empty string,(){expect(pipe.transform()).toEqual();});it(should capitalise,(){expect(pipe.transform(wow)).toEqual(WOW);});it(should throw with invalid values,(){//must use arrow function for expect to capture exceptionexpect(()pipe.transform(undefined)).toThrow();expect(()pipe.transform()).toThrow();expect(()pipe.transform()).toThrowError(Requires a String as input);});})调试Augury 是用于调试 Angular 应用程序的 Chrome 扩展就像 Batarang 用于调试 Angular 1 应用程序一样。安装后该扩展被视为具有测试 Angular 应用程序行为功能的开发工具插件。AuguryAugury 检查和可视化一个或多个组件的不同属性的组件树。从 Augury Chrome 扩展页面安装 Augury 工具(chrome.google.com/webstore/detail/augury/elgalmkoelokbchhkhacckoklkejnhcd)然后单击“添加到 Chrome”按钮。安装完成后需要按照以下步骤才能使用 Augury使用CtrlShiftI打开 Chrome 开发者工具窗口。单击 Augury 打开工具。它显示菜单选项如组件树、路由器树和 NgModules。一旦安装完成您可以在浏览器的右上角看到 Augury 图标。打开后您将看到当前加载的组件列表按其层次结构排序。您还可以看到它们在 DOM 中的位置。对组件所做的任何更改也将显示出来。有了这个开发人员可以更容易地了解他们的应用程序的性能以及问题和错误可能来自哪里https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/ts2-ng-dev/img/17bb4831-72e8-4616-a777-c4bb6fc873da.pngAugury 功能让我们详细看一些 Augury 功能。组件树这是可见的第一个视图显示了属于应用程序的加载组件https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/ts2-ng-dev/img/e58b5b39-947e-488f-ae74-58e24c697d4c.png组件树显示了组件之间的分层关系。通过选择每个组件还可以显示有关组件的更多信息https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/ts2-ng-dev/img/1ad9bd8c-856a-4eff-83d2-b40511d3c783.png路由器树路由器树以分层顺序显示应用程序树中每个组件的路由信息https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/ts2-ng-dev/img/b88681df-420b-4e8e-ab85-bb7422aea80a.png源映射值得注意的是TypeScript 代码将显示源映射文件是否存在。在生产环境中如果找不到源映射将仅显示编译后的 JavaScript 代码这可能也是经过缩小处理的难以阅读。单击“注入图形”将显示组件和服务的依赖关系https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/ts2-ng-dev/img/f9aaa886-20ea-4461-9cba-d00607c8b449.png值得注意的是要使 Augury 调试工作应用程序必须设置为开发模式。总结进行单元测试很重要因为它们运行更快我们将能够更快地获得反馈。测试的一个很大优势是它有助于防止回归破坏现有代码的更改。调试帮助我们识别和从代码中删除错误。使用 Augury开发人员可以通过组件树和可视化调试工具看到应用程序的可视化效果。这使得调试更容易。