Share
Embed
Transcript
91 slides
-
About me w גࣜձࣾ8"$6-ॴଐͷϑϩϯτΤϯδχΞ w $PNNVOJUZ w OHKBQBO TUB⒎
w (SBQI2-5PLZP PSHBOJ[FS w ॻ੶ w ʮ&MFDUSPOͰ͡ΊΔΞϓϦ։ൃʯ w झຯ w 7JN5ZQF4DSJQUQMVHJOͷ։ൃϝϯς -
γεςϜߏ w 4JOHMF1BHF"QQMJDBUJPO w 'SPOUFOE w 5ZQF4DSJQU"OHVMBS w #BDLFOE w
8FC"1*(PMBOH w %BUB"OBMZTJT#BUDI1ZUIPO 4DBMB -
γεςϜಛੑ w σʔλղੳͱ͍͏αʔϏεͷ߹্ɺ࣮σʔλͰ݁Ռ֬ ೝ͕ߦ͑ΔΑ͏ʹͳΔ·Ͱ͕͍ w ཧը໘͔ΒղੳδϣϒΛΩϡʔΠϯά w ղੳδϣϒͷྃΛͭ d࣌ؒ
w ղੳ݁ՌΛ"1*Ͱ(&5 w ݁Ռը໘Λඳը -
Angular and TypeScript w "OHVMBSͱݴ͑5ZQF4DSJQU w .JDSPTPGUͷ"MU+4 w ੩తͳܕ WFSVQຖʹݡ͘ͳΔ
ܕਪ w $PNQJMF͕௨Βͳ͍ίʔυͳΜͯ֎ͩʂ -
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 -
AoT Compile w "P5 "IFBEPG5JNF $PNQJMF w ੩తʹ"OHVMBS$PNQPOFOUΛղੳͯ͠ %0.ૢ࡞ίʔυΛੜ͢Δ͜ͱ w
+J5 +VTUJO5JNF $PNQJMF w ࣮ߦ࣌ʹ$PNQPOFOU͔Β$PNQJMFͯ͠ %0.ૢ࡞ؔΛ࡞Γग़͢͜ͱ -
How to use AoT ? w !BOHVMBSDMJ w !BOHVMBSDPNQJMFSDMJ $
ng build --prod $ ngc -p tsconfig.json -
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[])); -
AoT CompileͷԸܙ w ࣮ߦ࣌ύϑΥʔϚϯε ಛʹॳճඳը࣌ؒ ͷվળ w 5FNQMBUFͷ੩తίʔυνΣοΫ 㾎 "OHVMBS&YQSFTTJPOͷจ๏Τϥʔ
㾎 ଘࡏ͠ͳ͍ϓϩύςΟͷࢀর 㾎 ଘࡏ͠ͳ͍$PNQPOFOU%JSFDUJWF1JQFͷࢀর 㾎 FUD -
Make more “strict” w --strictNPEF w 5ZQF4DSJQU͔Βར༻ՄೳͳΦϓγϣϯ w ԼهͷશΦϓγϣϯ͕༗ޮԽ͞ΕΔ --noImplicitAny
--noImplicitThis --alwaysStrict --strictNullChecks -
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.fooOVMMVOEFpOFE͔ΑʁɹͱౖΒΕΔ -
Null check example export interface Hoge { foo?: { bar:
string; } } function main(hoge: Hoge) { if (hoge.foo) { console.log(hoge.foo.bar); } } ͪΌΜͱOVMMDIFDL͢ΔͱΤϥʔফ͑Δ -
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; } ͜ΕͲ͏ͩΖ͏ʁ -
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͔͍͜͠ʂ -
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͢ΔͱΤϥʔফ͑Δ -
@angular/language-service w "OHVMBSνʔϜۘͷ-BOHVBHF4FSWJDF1MVHJO w ෦తʹ"P5$PNQJMFSΛར༻ w 5ZQF4DSJQUʹରԠͨ͠ΤσΟλɾ*%&Ͱಈ࡞ { "compilerOptions": {
: "plugins": [ { "name": "@angular/language-service" } ] } } -
Angular ͱ୯ମςετ w 5FTUJOHؔ࿈ͷαϙʔτखް͍ ͕͢͞l0OF'SBNFXPSLz w "OHVMBSʹ͓͚Δඪ४5FTUTUBDL * ,BSNB **
+BTNJOF *** "OHVMBSUFTUJOHVUJMJUJFT -
I. Karma w 5FTU3VOOFSGPS+BWB4DSJQU w 5FTUDPEFΛϒϥβͰ࣮ߦ͢ΔͨΊͷUPPM w ϒϥβͷىಈ w ςετίʔυͷૹΓࠐΈ
TFSWFSCSPXTFS w ςετ݁Ռͷදࣔ CSPXTFSTFSWFS w FUD -
II. Jasmine w 5FTUJOH'SBNFXPSLGPS+BWB4DSJQU w ࠷ॳ͔Β৭ʑೖͬͯΔ w %FTDSJCFTQFDT #%%
w "TTFSUJPO w 4QZJOH w FUD w /PEFKTPS8FC#SPXTFSͷͲͪΒͰಈ࡞ -
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'); }); }); -
III. Angular testing utilities w !BOHVMBSDPSFUFTUJOH w ςετ༻"OHVMBSNPEVMFͷηοτΞοϓ w
ඇಉظ੍ޚؔ w FUD w ଞʹ!BOHVMBSIUUQUFTUJOH!BOHVMBSSPVUFS UFTUJOHͳͲɺςετΛαϙʔτ͢Δػೳ܈͕ඪ४Ͱ͍ ͯ͘Δ -
@angular/cli and test w ͔Βςετ͕ಈ࡞͢ΔڥΛ࡞Δ݁ߏେมʜ w !BOHVMBSDMJʹͤΔͷ͕٢ w ,BSNBͷઃఆ XFCQBDLͷઃఆ
UFTUDPEFͷTDB⒎PME w ͪͳΈʹFFUFTUJOHηοτΞοϓͯ͘͠ΕΔ -
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) -
3 ways to Component Testing w "OHVMBS$PNQPOFOUͷςετύλʔϯେ͖͘௨Γ * *TPMBUFE5FTUJOH **
4IBMMPX5FTUJOH *** *OUFHSBUJPO5FTUJOH https://vsavkin.com/three-ways-to-test-angular-2-components-dcea8e90bd8d -
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; } } -
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; } } -
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); }); }); -
I. Isolated Testing w 5ZQF4DSJQUDMBTTͱͯ͠ͷςετ w 4FSWJDF5FTUJOHͱ΄΅ಉ༷ͷख๏ w $PNQJMF ඳըίετθϩඇৗʹߴ
w %0.ؔ࿈ͷݕূͰ͖ͳ͍ w ෳࡶͳΠϯελϯεϝιουͷݕূʹ͘ -
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; } } -
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(); }); }); -
II. Shallow Testing w /0@&33034@4$)&."ʹΑΓԼ$PNQPOFOUͷ $PNQJMF͕ແࢹ͞ΕΔ ௨ৗΤϥʔʹͳͬͯ͠·͏ w $PNQJMF
ඳըίετࣗͷ$PNQPOFOUͷΈ ·͊·͊ߴ w ෦తʹ%0.ͷݕূ͕Մೳ w )5.-ཁૉͷඳը %0.Πϕϯτϋϯυϥͷݕূ w 5FNQMBUFͷ"OHVMBSFYQSFTTJPO֬ೝʹ͘ -
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; } } -
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); })); }); -
III. Integration Testing w $PNQPOFOUͷඳըʹඞཁͳґଘؔશͯΛಡΈࠐΉ w શґଘ$PNQPOFOUͷ$PNQJMF ඳըίετΛཁ͢Δ ͬͱߴίετ w
શͳ%0.ͷݕূ͕Մೳ w $PNQPOFOU͕ؒ࿈ܞ͢Δ෦ͷ֬ೝʹ͘ -
݁ہͲͷύλʔϯ͕ྑ͍ͷΒ w 8"$6-Ͱ*OUFHSBUJPO5FTUJOH͕ଟΊ w ,BSNB্Ͱݟͨ ಛʹΤοδέʔε ֬ೝ͍ͨ͠ ˞ࣾ௨শz,BSNBۦಈ։ൃz w $PNQPOFOUͷߏʹࡉ͔͍6*$PNQPOFOU͕ඞཁ
ྫJDPO CVUUPO FUD w 4IBMMPX5FTUJOHͩͱԿඳը͞Εͳ͍͜ͱଟ͍ w ࡶʹ.PEVMFΛJNQPSU͢Δํָ͕ ଵຫͱ -
Ͳ͜·Ͱݕূ͢Δͷ͔ w ओͳݕূର w OH*G<DMBTTYYYY>ͷӈล͕ਖ਼͘͠ಈ࡞͍ͯ͠Δ͜ͱ w ࢠ$PNQPOFOUͷ!*OQVUʹਖ਼͘͠Λ͍ͯ͠Δ͜ͱ w %0.Πϕϯτࢠ$PNQPOFOUͷ!0VUQVUʹԠͯ͡ɺ $PNQPOFOUͷϝιου͕ൃՐ͢Δ͜ͱ
w ٯʹςΩετ \\YYY^^ 1JQFͷ݁ՌͳͲɺࡉ͔͍ඳը݁Ռ BTTFSUJPO͍ͯ͠ͳ͍ ཧ༝ޙड़ -
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(); }); -
Snapshot Testing w ςετ݁Ռ 4OBQTIPU Λʮਖ਼͍݁͠Ռʯͱͯ͠อଘ w ࣍ճςετ࣌ʹલճ݁ՌͱͷࠩΛݕࠪ͢Δ ƭ The
latest snapshot (DOM String) ƭ The actual snapshot (DOM String) Update if snapshot is accepted Assertion -
Visual Snapshot Testing w ୯ମςετͰඳըΛ࣮ߦ͍ͯ͠Δ 4IBMMPX*OUFHSBUJPO w ඳը݁Ռಉ࢜ͷൺֱͰ4OBQTIPU5FTUΠέΔ͔ʁ Ʃ
The latest snapshot (PNG Image) Ʃ The actual snapshot (PNG Image) Update if snapshot is accepted Assertion -
I. Capture Actual Images w ,BSNBʹΑΔ୯ମςετ࣮ߦ w $PNQPOFOU5FTUJOHͷ֤TQFDຖʹ ը໘ΩϟϓνϟΛऔಘ w
Ωϟϓνϟऔಘʹ &MFDUSPO /JHIUNBSF ͷ"1*Λར༻ ♥ -
III. Compare w ը૾ͷࠩݕ࣮ߦ JNBHFNBHJDL w "DUVBMࠓճͷςετ݁Ռը૾܈ w &YQFDUFEEPXOMPBEͨ͠ը૾܈
w 4ࠓճͷςετը૾ΛΞοϓ w 4ൺֱϨϙʔτΛΞοϓ w (JU)VC݁ՌΛ௨ -
Tools for Visual Regression test w OQNQBDLBHFT TQFDJBMUIBOLTGPS!CPLVXFC w
ΩϟϓνϟऔಘLBSNBOJHIUNBSF w ը૾ࠩݕग़݁ՌϨϙʔτͷग़ྗSFHDMJ w "844(JU)VC"1*DBMMTIFMMTDSJQUʹϕλॻ͖ w ݱঢ়4FUVQ͕େม Ώ͘Ώ͘TVJUతͳUPPMʹ͍͖͍ͯͨ͠ -
Visual TestingͷԸܙ w $44TUZMFQJQF݁ՌͷݕূΛ໌ࣔతʹॻ͔ͣͱɺ ҙਤͤ͵ഁյʹؾ͚Δ w ॳճίϛοτ࣌ͷϨϏϡʔΛ͔ͬ͠Γ͓͚ͯ͠Α͍ w ςετ݁Ռ͕ը૾Խ͞Ε͍ͯΔɿ 㾎
ϨϏϡʔ࣌ʹը૾Λ֬ೝͰ͖ΔˠϨϏϡΞෛՙܰݮ 㾎 ͜ΜͳQOHͰ͖ͨΑʂײ͕ग़Δˠ ςετίʔυΛॻ͘ϞνϕʔγϣϯVQʹܨ͕Δ -
ࠓ͔͑ͨͬͨ͜ͱ w ੩తղੳπʔϧΛςετʹ׆༻ w 5ZQF4DSJQU "P5$PNQJMF -BOHVBHF4FSWJDF w $PNQPOFOU5FTUJOHQBUUFSOͷ͍͚
w *TPMBUFE4IBMMPX*OUFHSBUJPO w νʔϜͰςετΛָ͠ΊΔΈ -
(࠶ܝ) AoT CompileͷԸܙ w ࣮ߦ࣌ύϑΥʔϚϯε ಛʹॳճඳը࣌ؒ ͷվળ w 5FNQMBUFͷ੩తίʔυνΣοΫ 㾎
"OHVMBS&YQSFTTJPOͷจ๏Τϥʔ 㾎 ଘࡏ͠ͳ͍ϓϩύςΟͷࢀর 㾎 ଘࡏ͠ͳ͍$PNQPOFOU%JSFDUJWF1JQFͷࢀর 㾎 FUD -
(ࢀߟ) JiT and AoT Unit Testing w "OHVMBSW͔Βɺ"P5$PNQJMFͷ݁ՌΛ୯ମςε τ͔Βར༻Մೳʹ w
4QFDຖͷ$PNQPOFOU$PNQJMF࣌ؒΛݮͰ͖Δ // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( BrowserDynamicTestingModule, platformBrowserDynamicTesting(), [()=> AppModuleNgSummary], ); -
Visual Testingͷେม͞ w ҆ఆͨ͠Ωϟϓνϟऔಘ݁ߏେม w ϒϥβͷϨϯμϦϯάΛͨͳ͍ͱμϝ w ݁ہTFU5JNFPVU NTFD
S*$ʹམͪண͘ w ޡݕ 'BMMT1PTJUJWF Λ͍͔ʹݮΒ͔͢ w $44"OJNBUJPOࣄલʹແޮԽ͢ΔͳͲ͕ඞཁ w * { transition-duration: 0; !important }