ng-japan: Testing Angular app

ng-japan: Testing Angular app

Slides for ng-japan 2017

893f54413c2bd9ba41d11d753aacaf2c?s=128

Yosuke Kurami

June 17, 2017
Tweet

Transcript

  1. 3.

    About me w גࣜձࣾ8"$6-ॴଐͷϑϩϯτΤϯδχΞ w $PNNVOJUZ w OHKBQBO TUB⒎ 

    w (SBQI2-5PLZP PSHBOJ[FS  w ॻ੶ w ʮ&MFDUSPOͰ͸͡ΊΔΞϓϦ։ൃʯ w झຯ w 7JN5ZQF4DSJQUQMVHJOͷ։ൃϝϯς
  2. 16.

    Compilation and Templates? w Ͱ΋)5.-ςϯϓϨʔτ͸୞ͷจࣈྻ w ԿΛॻ͍ͯ΋$PNQJMF௨ͬͪΌ͏͡ΌΜʜ import { Component

    } from '@angular/core'; @Component({ selector: 'app-root', template: ` <h1> {{title}} </h1> `, styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'app works!'; } It’s just string literal
  3. 17.

    AoT Compile w "P5 "IFBEPG5JNF $PNQJMF w ੩తʹ"OHVMBS$PNQPOFOUΛղੳͯ͠
 %0.ૢ࡞ίʔυΛੜ੒͢Δ͜ͱ w

    +J5 +VTUJO5JNF $PNQJMF w ࣮ߦ࣌ʹ$PNQPOFOU͔Β$PNQJMFͯ͠
 %0.ૢ࡞ؔ਺Λ࡞Γग़͢͜ͱ
  4. 19.

    How to use AoT ? w !BOHVMBSDMJ w !BOHVMBSDPNQJMFSDMJ $

    ng build --prod $ ngc -p tsconfig.json
  5. 21.

    import * as i0 from './app.component.css.shim.ngstyle'; import * as i1

    from '@angular/core'; import * as i2 from './app.component'; const styles_AppComponent:any[] = [i0.styles]; export const RenderType_AppComponent:i1.RendererType2 = i1.ɵcrt({encapsulation: 0,styles:styles_AppComponent, data:{}}); export function View_AppComponent_0(_l:any):i1.ɵViewDefinition { return i1.ɵvid(0,[(_l()(),i1.ɵeld(0,(null as any),(null as any),1,'h1',([] as any[]), (null as any),(null as any),(null as any),(null as any),(null as any))),(_l()(), i1.ɵted((null as any),['\n ','\n'])),(_l()(),i1.ɵted((null as any),['\n']))], (null as any),(_ck,_v) => { var _co:i2.AppComponent = _v.component; const currVal_0:any = _co.title; _ck(_v,1,0,currVal_0); }); } export function View_AppComponent_Host_0(_l:any):i1.ɵViewDefinition { return i1.ɵvid(0,[(_l()(),i1.ɵeld(0,(null as any),(null as any),1,'app-root',([] as any[]), (null as any),(null as any),(null as any),View_AppComponent_0,RenderType_AppComponent)), i1.ɵdid(49152,(null as any),0,i2.AppComponent,([] as any[]),(null as any),(null as any))], (null as any),(null as any)); } export const AppComponentNgFactory:i1.ComponentFactory<i2.AppComponent> = i1.ɵccf('app-root', i2.AppComponent,View_AppComponent_Host_0,{},{},([] as any[]));
  6. 23.

    AoT CompileͷԸܙ w ࣮ߦ࣌ύϑΥʔϚϯε ಛʹॳճඳը࣌ؒ ͷվળ w 5FNQMBUFͷ੩తίʔυνΣοΫ 㾎 "OHVMBS&YQSFTTJPOͷจ๏Τϥʔ

    㾎 ଘࡏ͠ͳ͍ϓϩύςΟͷࢀর 㾎 ଘࡏ͠ͳ͍$PNQPOFOU%JSFDUJWF1JQFͷࢀর 㾎 FUD
  7. 25.

    Strict null check example export interface Hoge { foo?: {

    bar: string; } } function main(hoge: Hoge) { // TS2532: Object is possibly 'undefined'. console.log(hoge.foo.bar); } hoge.foo͸OVMMVOEFpOFE͔΋ΑʁɹͱౖΒΕΔ
  8. 26.

    Null check example export interface Hoge { foo?: { bar:

    string; } } function main(hoge: Hoge) { if (hoge.foo) { console.log(hoge.foo.bar); } } ͪΌΜͱOVMMDIFDL͢ΔͱΤϥʔ͸ফ͑Δ
  9. 27.

    With Angular templates export interface Hoge { foo?: { bar:

    string; } } @Component({ selector: 'app-hoge', template: `<div>{{hoge.foo.bar}}</div>`, }) export class HogeComponent { @Input() hoge: Hoge; } ͜Ε͸Ͳ͏ͩΖ͏ʁ
  10. 28.

    Great! $ ng build --prod Hash: 797e0c8cdb3322d2d094 Time: 9762ms chunk

    {0} polyfills.c1dd16e57fa8d11134c9.bundle.js (polyfills) 158 kB {4} [initial] [rendered] chunk {1} main.a49bd4f47eb88687420a.bundle.js (main) 11.3 kB {3} [initial] [rendered] chunk {2} styles.d41d8cd98f00b204e980.bundle.css (styles) 69 bytes {4} [initial] [rendered] chunk {3} vendor.5b15b8462e82e6e78e7f.bundle.js (vendor) 1.14 MB [initial] [rendered] chunk {4} inline.f43fbe997f9c1fcc2775.bundle.js (inline) 0 bytes [entry] [rendered] ERROR in src/$$_gendir/app/hoge/ hoge.component.ngfactory.ts (19,27):
 Object is possibly 'undefined'. "P5͔͍͜͠ʂ
  11. 29.

    With Angular templates export interface Hoge { foo?: { bar:

    string; } } @Component({ selector: 'app-hoge', template: `<div *ngIf=“hoge.foo”>{{hoge.foo.bar}}</div>`, }) export class HogeComponent { @Input() hoge: Hoge; } ͪΌΜͱOVMMDIFDL͢ΔͱΤϥʔ͸ফ͑Δ
  12. 34.
  13. 35.
  14. 40.

    II. Jasmine w 5FTUJOH'SBNFXPSLGPS+BWB4DSJQU w ࠷ॳ͔Β৭ʑೖͬͯΔ w %FTDSJCFTQFDT #%% 

    w "TTFSUJPO  w 4QZJOH  w FUD w /PEFKTPS8FC#SPXTFSͷͲͪΒͰ΋ಈ࡞
  15. 41.

    Simple Jasmine example: describe('FizzBuzz', () => { it('should say Fizz

    when input can be divided 3', () => { expect(fizzBuzz(3)).toBe('Fizz'); }); it('should say Buzz when input can be divided 5', () => { expect(fizzBuzz(5)).toBe('Buzz'); }); });
  16. 42.

    III. Angular testing utilities w !BOHVMBSDPSFUFTUJOH w ςετ༻"OHVMBSNPEVMFͷηοτΞοϓ  w

    ඇಉظ੍ޚؔ਺  w FUD w ଞʹ΋!BOHVMBSIUUQUFTUJOH΍!BOHVMBSSPVUFS UFTUJOHͳͲɺςετΛαϙʔτ͢Δػೳ܈͕ඪ४Ͱ෇͍ ͯ͘Δ
  17. 43.
  18. 45.

    Run test with @angular/cli $ ng new my-angualr-app $ cd

    my-angualr-app $ ng test —single-run Executed 3 of 3 SUCCESS (0.192 secs / 0.169 secs)
  19. 47.

    3 ways to Component Testing w "OHVMBS$PNQPOFOUͷςετύλʔϯ͸େ͖͘௨Γ * *TPMBUFE5FTUJOH **

    4IBMMPX5FTUJOH *** *OUFHSBUJPO5FTUJOH https://vsavkin.com/three-ways-to-test-angular-2-components-dcea8e90bd8d
  20. 49.

    I. Isolated Testing @Component({ selector: 'app-root', template: ` <h1>{{title}}</h1> <h2>My

    Heroes</h2> <ul class="heroes"> <li *ngFor="let hero of heroes" [class.selected]="hero === selectedHero" (click)="onSelect(hero)"> <span class="badge">{{hero.id}}</span> {{hero.name}} </li> </ul> <hero-detail [hero]="selectedHero"></hero-detail> `, }) export class AppComponent { title = 'Tour of Heroes'; heroes: Hero[] = [ { id: 10, name: 'Windstorm' } ]; selectedHero: Hero; onSelect(hero: Hero) { this.selectedHero = hero; } }
  21. 50.

    I. Isolated Testing @Component({ selector: 'app-root', template: ` <h1>{{title}}</h1> <h2>My

    Heroes</h2> <ul class="heroes"> <li *ngFor="let hero of heroes" [class.selected]="hero === selectedHero" (click)="onSelect(hero)"> <span class="badge">{{hero.id}}</span> {{hero.name}} </li> </ul> <hero-detail [hero]="selectedHero"></hero-detail> `, }) export class AppComponent { title = 'Tour of Heroes'; heroes: Hero[] = [ { id: 10, name: 'Windstorm' } ]; selectedHero: Hero; onSelect(hero: Hero) { this.selectedHero = hero; } }
  22. 51.

    Example: describe('AppComponent(Isolated testing)', () => { it('should be set selectedHero

    when onSelect called', () => { const comp = new AppComponent(); const hero = { id: 1, name: 'a hero' }; comp.onSelect(hero); expect(comp.selectedHero).toBe(hero); }); });
  23. 54.

    II. Shallow Testing @Component({ selector: 'app-root', template: ` <h1>{{title}}</h1> <h2>My

    Heroes</h2> <ul class="heroes"> <li *ngFor="let hero of heroes" [class.selected]="hero === selectedHero" (click)="onSelect(hero)"> <span class="badge">{{hero.id}}</span> {{hero.name}} </li> </ul> <hero-detail [hero]="selectedHero"></hero-detail> `, }) export class AppComponent { title = 'Tour of Heroes'; heroes: Hero[] = [ { id: 10, name: 'Windstorm' } ]; selectedHero: Hero; onSelect(hero: Hero) { this.selectedHero = hero; } }
  24. 55.

    Example: describe('AppComponent(Shallow testing)', () => { it('should render hero-detail element

    when selectedHero is set', () => { const fixture = TestBed.configureTestingModule({ declarations: [AppComponent], schemas: [NO_ERRORS_SCHEMA], }).createComponent(AppComponent); fixture.componentInstance.selectedHero = { id: 10, name: 'Windstorm' }; fixture.detectChanges(); expect(fixture.debugElement.query(By.css('hero-detail'))).toBeTruthy(); }); });
  25. 56.

    II. Shallow Testing w /0@&33034@4$)&."ʹΑΓԼ૚$PNQPOFOUͷ $PNQJMF͕ແࢹ͞ΕΔ ௨ৗ͸Τϥʔʹͳͬͯ͠·͏  w $PNQJMF

    ඳըίετ͸ࣗ਎ͷ$PNQPOFOU෼ͷΈ
 ·͊·͊ߴ଎ w ෦෼తʹ%0.ͷݕূ͕Մೳ w )5.-ཁૉͷඳը %0.Πϕϯτϋϯυϥͷݕূ౳ w 5FNQMBUF಺ͷ"OHVMBSFYQSFTTJPO֬ೝʹ޲͘
  26. 58.

    III. Integration Testing @Component({ selector: 'app-root', template: ` <h1>{{title}}</h1> <h2>My

    Heroes</h2> <ul class="heroes"> <li *ngFor="let hero of heroes" [class.selected]="hero === selectedHero" (click)="onSelect(hero)"> <span class="badge">{{hero.id}}</span> {{hero.name}} </li> </ul> <hero-detail [hero]="selectedHero"></hero-detail> `, }) export class AppComponent { title = 'Tour of Heroes'; heroes: Hero[] = [ { id: 1, name: 'Windstorm' } ]; selectedHero: Hero; onSelect(hero: Hero) { this.selectedHero = hero; } }
  27. 59.

    Example: describe('AppComponent(Integration Testing)', () => { it('should pass selectedHero object

    to heroDetailComponent', async(() => { const fixture = TestBed.configureTestingModule({ imports: [AppModule, FormsModule], }).createComponent(AppComponent); fixture.detectChanges(); fixture.debugElement .query(By.css('li:first-child')).triggerEventHandler('click', {}); fixture.detectChanges(); expect( fixture.debugElement.query(By.directive(HeroDetailComponent)) .componentInstance.hero ).toBe(fixture.componentInstance.selectedHero); })); });
  28. 68.

    One more thing w ൃ୺͸+FTU 3FBDUͷ4OBQTIPU5FTUJOH΁ͷಌΕ import React from 'react';

    import Link from '../Link.react'; import renderer from 'react-test-renderer'; it('renders correctly', () => { const tree = renderer .create(<Link page="http://www.facebook.com">Facebook</Link>) .toJSON(); expect(tree).toMatchSnapshot(); });
  29. 69.

    Snapshot Testing w ςετ݁Ռ 4OBQTIPU Λʮਖ਼͍݁͠Ռʯͱͯ͠อଘ w ࣍ճςετ࣌ʹલճ݁Ռͱͷࠩ෼Λݕࠪ͢Δ ƭ The

    latest snapshot (DOM String) ƭ The actual snapshot (DOM String) Update if snapshot is accepted Assertion
  30. 70.

    Visual Snapshot Testing w ୯ମςετͰඳըΛ࣮ߦ͍ͯ͠Δ 4IBMMPX*OUFHSBUJPO  w ඳը݁Ռಉ࢜ͷൺֱͰ4OBQTIPU5FTUΠέΔ͔΋ʁ Ʃ

    The latest snapshot (PNG Image) Ʃ The actual snapshot (PNG Image) Update if snapshot is accepted Assertion
  31. 76.

    III. Compare w ը૾ͷࠩ෼ݕ஌࣮ߦ JNBHFNBHJDL  w "DUVBMࠓճͷςετ݁Ռը૾܈ w &YQFDUFEEPXOMPBEͨ͠ը૾܈

    w 4΁ࠓճͷςετը૾ΛΞοϓ w 4΁ൺֱϨϙʔτΛΞοϓ w (JU)VC΁݁ՌΛ௨஌
  32. 78.

    Tools for Visual Regression test w OQNQBDLBHFT TQFDJBMUIBOLTGPS!CPLVXFC  w

    ΩϟϓνϟऔಘLBSNBOJHIUNBSF w ը૾ࠩ෼ݕग़݁ՌϨϙʔτͷग़ྗSFHDMJ w "844(JU)VC"1*DBMM͸TIFMMTDSJQUʹϕλॻ͖ w ݱঢ়͸4FUVQ͕େม
 Ώ͘Ώ͘͸TVJUతͳUPPMʹ͍͖͍ͯͨ͠
  33. 79.

    Visual TestingͷԸܙ w $44TUZMF΍QJQF݁ՌͷݕূΛ໌ࣔతʹॻ͔ͣͱ΋ɺ
 ҙਤͤ͵ഁյʹؾ෇͚Δ w ॳճίϛοτ࣌ͷϨϏϡʔΛ͔ͬ͠Γ͓͚ͯ͠͹Α͍ w ςετ݁Ռ͕ը૾Խ͞Ε͍ͯΔɿ 㾎

    ϨϏϡʔ࣌ʹը૾Λ֬ೝͰ͖ΔˠϨϏϡΞෛՙ΋ܰݮ 㾎 ͜ΜͳQOHͰ͖ͨΑʂײ͕ग़Δˠ
 ςετίʔυΛॻ͘ϞνϕʔγϣϯVQʹܨ͕Δ
  34. 87.

    (࠶ܝ) AoT CompileͷԸܙ w ࣮ߦ࣌ύϑΥʔϚϯε ಛʹॳճඳը࣌ؒ ͷվળ w 5FNQMBUFͷ੩తίʔυνΣοΫ 㾎

    "OHVMBS&YQSFTTJPOͷจ๏Τϥʔ 㾎 ଘࡏ͠ͳ͍ϓϩύςΟͷࢀর 㾎 ଘࡏ͠ͳ͍$PNQPOFOU%JSFDUJWF1JQFͷࢀর 㾎 FUD
  35. 88.

    (ࢀߟ) JiT and AoT Unit Testing w "OHVMBSW͔Β͸ɺ"P5$PNQJMFͷ݁ՌΛ୯ମςε τ͔Βར༻Մೳʹ w

    4QFDຖͷ$PNQPOFOU$PNQJMF࣌ؒΛ࡟ݮͰ͖Δ // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( BrowserDynamicTestingModule, platformBrowserDynamicTesting(), [()=> AppModuleNgSummary], );
  36. 91.

    Visual Testingͷେม͞ w ҆ఆͨ͠Ωϟϓνϟऔಘ͸݁ߏେม w ϒϥ΢βͷϨϯμϦϯάΛ଴ͨͳ͍ͱμϝ w ݁ہTFU5JNFPVU NTFD 

    S*$ʹམͪண͘ w ޡݕ஌ 'BMMT1PTJUJWF Λ͍͔ʹݮΒ͔͢ w $44"OJNBUJPO͸ࣄલʹແޮԽ͢ΔͳͲ޻෉͕ඞཁ w * { transition-duration: 0; !important }