テストベッドでないかテストベッドにおける単位試験


私は最近、新しいクライアントのためのコンサルティングを開始しました.私は新しい機能を作成し、ユニットテストを書き始めたので、いくつかのことに気づいた.最初に、書くテストが必要以上に難しくなっていた(これより具体的に後で)、テストランナーはとてもゆっくり走っていた.
私がテストに深く目を向け始めたので、私は私の単体テストとアプリケーションの他の部分から以前に書かれたテストの違いに気がつきました.私は、私が使用していたことを発見TestBed テストを作成するにはこれは、アプリケーション内の任意の場所ではなかった.私は過去にテストベッドを使用していたので、これは非常に興味深いことを発見し、パフォーマンスは問題ではなかった.
これは、私はトピックに関するいくつかのより多くの研究を行うには、角のコミュニティ内の他のいずれかのテストベッドを使用していないかどうかを参照してください.私は多くの記事を見つけることができなかったがThe Angular Show ポッドキャストどこで、なぜあなたがすべきかテストベッドを使用すべきではない非常に健全な議論をしていた.私はあなたのためにエピソードを台無しにしません、しかし、私は毎日、角度で働く誰かのために、これが最初に私がテストベッドを使わないケース(そして、良いもの)を聞いたということを認めます.
私がまだ懐疑的だったけれども、私はそれがこのプロジェクトに打撃を与えて、それが違いをしたかどうか見ると思いました.私はすぐにこのアプローチは私をもたらしたパフォーマンスの増加によって吹き飛ばされた.これはなぜ私の質問を導いた.これは最終的にこのブログ記事につながった.

パフォーマンス


コンポーネントSpecファイルからテストベッドを削除すると、基本的にDOMをテストしなくなります.今ではコンポーネントクラス自体のみをテストします.これは最初はコード臭いのように感じました、しかし、結局、私がそれについて考えたより多く、私は本当の単位テストがコードの1つの単位をテストするだけであると理解しました.コンポーネントのHTMLテンプレートがコンポーネントのクラスとどのようにやり取りされているかは、統合テストとなります.
それで、私に少しこれを解凍させてください.角CLIを使用して新しいコンポーネントを生成するときng g c my-feature 次のファイルをレンダリングします.
  • my-feature.component.html
  • my-feature.component.scss
  • my-feature.component.ts
  • my-feature.component.spec.ts
  • あなたが開くときmy-feature.component.spec.ts ファイルを以下に示します.
    import { async, ComponentFixture, TestBed } from '@angular/core/testing';
    
    import { MyFeatureComponent } from './my-feature.component';
    
    describe('MyFeatureComponent', () => {
      let component: MyFeatureComponent;
      let fixture: ComponentFixture<MyFeatureComponent>;
    
      beforeEach(async(() => {
        TestBed.configureTestingModule({
          declarations: [ MyFeatureComponent ]
        })
        .compileComponents();
      }));
    
      beforeEach(() => {
        fixture = TestBed.createComponent(MyFeatureComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
      });
    
      it('should create', () => {
        expect(component).toBeTruthy();
      });
    });
    
    これは基本的に各テストの前にMyModiureComponentクラスとDOMの新しいインスタンスを作成します.この例は些細なものですが、何百ものコンポーネントを持つアプリケーションでは、すべてのテストにDOMを生成することは高価になることがあります.

    無試験で


    import { async, ComponentFixture, TestBed } from '@angular/core/testing';
    
    import { MyFeatureComponent } from './my-feature.component';
    
    describe('MyFeatureComponent', () => {
      let component: MyFeatureComponent;
    
      beforeEach(() => {
        component = new MyFeatureComponent()
      });
    
      it('should create', () => {
        expect(component).toBeTruthy();
      });
    });
    
    ジャストアップでMyFeatureComponent 各テストの前のクラスは、クラスインスタンスを作成し、DOM自体を除外します.

    依存関係は?


    私たちのコンポーネントは、現在2つの依存関係を持っているとしましょう.一つUserService もう一つはMyFeatureService . どのように依存関係を必要とする書き込みテストを処理するのですか?

    テストベッド付き


    @angular/core/testing';
    
    import { MyFeatureComponent } from './my-feature.component';
    import { UserService, MyFeatureService } from 'src/app/services';
    
    describe('MyFeatureComponent', () => {
      let component: MyFeatureComponent;
      let fixture: ComponentFixture<MyFeatureComponent>;
    
      beforeEach(async(() => {
        TestBed.configureTestingModule({
          declarations: [ MyFeatureComponent ],
          providers: [UserService, MyFeatureService]
        })
        .compileComponents();
      }));
    
      beforeEach(() => {
        fixture = TestBed.createComponent(MyFeatureComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
      });
    
      it('should create', () => {
        expect(component).toBeTruthy();
      });
    });
    

    テストベッド


    import { async, ComponentFixture, TestBed } from '@angular/core/testing';
    
    import { MyFeatureComponent } from './my-feature.component';
    import { UserService, MyFeatureService } from 'src/app/services';
    
    describe('MyFeatureComponent', () => {
      let component: MyFeatureComponent;
      const userService = new UserService();
      const myFeatureService = new MyFeatureService();
    
      beforeEach(() => {
        component = new MyFeatureComponent(userService, myFeatureService);
      });
    
      it('should create', () => {
        expect(component).toBeTruthy();
      });
    });
    
    注意:新しいコンポーネントクラスインスタンスに追加する依存関係の順序は、この方法で正しい順序にする必要があります.

    私の依存関係が依存しているならば、どうですか?


    ほとんどの依存関係が他の依存関係を持っているので、前の例を見るとき、あなたはおそらく同じことを考えていたでしょう.例えば、サービスは一般的にHttpClient これは、APIへのネットワーク要求を行うことができます.これが起こるとき(私たちはたいていモックまたは偽を使います).

    テストベッド付き


    @angular/core/testing';
    
    import { MyFeatureComponent } from './my-feature.component';
    import { UserService, MyFeatureService } from 'src/app/services';
    
    class FakeMyFeatureService {
    
    }
    
    class FakeUserService {
    
    }
    
    describe('MyFeatureComponent', () => {
      let component: MyFeatureComponent;
      let fixture: ComponentFixture<MyFeatureComponent>;
    
      beforeEach(async(() => {
        TestBed.configureTestingModule({
          declarations: [ MyFeatureComponent ],
          providers: [
            { provide: UserService, useClass: FakeUserService },
            { provide: MyFeatureService, useClass: FakeMyFeatureService }
          ]
        })
        .compileComponents();
      }));
    
      beforeEach(() => {
        fixture = TestBed.createComponent(MyFeatureComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
      });
    
      it('should create', () => {
        expect(component).toBeTruthy();
      });
    });
    

    無試験で


    import { async, ComponentFixture, TestBed } from '@angular/core/testing';
    
    import { MyFeatureComponent } from './my-feature.component';
    import { UserService, MyFeatureService } from 'src/app/services';
    
    class FakeMyFeatureService {
    
    }
    
    class FakeUserService {
    
    }
    
    describe('MyFeatureComponent', () => {
      let component: MyFeatureComponent;
      const userService = new FakeUserService();
      const myFeatureService = new FakeMyFeatureService();
    
      beforeEach(() => {
        component = new MyFeatureComponent(userService, myFeatureService);
      });
    
      it('should create', () => {
        expect(component).toBeTruthy();
      });
    });
    
    注意:これらの依存関係のスパイを使用して、実際に気にかけるコンポーネントの部分をテストします.

    鈍い試験


    テストベッドなしでは、DOMへの変更がもはやテストを中断しないことを意味します.私は何度もあなたの角のアプリケーションのどこかのコンポーネントを作成した突然のテスト起動失敗のすべてを意味しますか?これは、テストベッドがDOMbeforeEach テスト.コンポーネントとその依存関係が追加されると、親コンポーネントが失敗します.
    親コンポーネントを作成することによって、より深くこれを見ましょうMyParentComponent with ng g c my-parentでは、見てみましょうmy-parent.component.spec.ts ファイル

    テストベッド付き


    @angular/core/testing';
    
    import { MyParentComponent } from './my-parent.component';
    
    describe('MyParentComponent', () => {
      let component: MyParentComponent;
      let fixture: ComponentFixture<MyParentComponent>;
    
      beforeEach(async(() => {
        TestBed.configureTestingModule({
          declarations: [ MyParentComponent ]
        })
        .compileComponents();
      }));
    
      beforeEach(() => {
        fixture = TestBed.createComponent(MyParentComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
      });
    
      it('should create', () => {
        expect(component).toBeTruthy();
      });
    });
    

    無試験で


    import { async, ComponentFixture, TestBed } from '@angular/core/testing';
    
    import { MyParentComponent } from './my-parent.component';
    
    describe('MyParentComponent', () => {
      let component: MyParentComponent;
    
      beforeEach(() => {
        component = new MyParentComponent();
      });
    
      it('should create', () => {
        expect(component).toBeTruthy();
      });
    });
    
    今すぐ追加しましょうMyFeatureComponent テンプレートとしてMyParentComponent .
    <my-parent>
      <my-feature />
    </my-parent>
    
    この例ではmy-parent.component.spec.ts テストは現在、すべての失敗していますMyFeatureComponent またはプロバイダUserService and MyFeatureService . 以下は、我々が戻って、それらのテストを得るために必要なものです.

    テストベッド付き


    @angular/core/testing';
    
    import { MyParentComponent } from './my-parent.component';
    import { MyFeatureComponent } from './my-feature/my-feature.component';
    import { UserService, MyFeatureService } from 'src/app/services';
    
    class FakeMyFeatureService {
    
    }
    
    class FakeUserService {
    
    }
    
    describe('MyParentComponent', () => {
      let component: MyParentComponent;
      let fixture: ComponentFixture<MyParentComponent>;
    
      beforeEach(async(() => {
        TestBed.configureTestingModule({
          declarations: [ MyParentComponent, MyFeatureComponent ],
          providers: [
            { provide: UserService, useClass: FakeUserService },
            { provide: MyFeatureService, useClass: FakeMyFeatureService }
          ]
        })
        .compileComponents();
      }));
    
      beforeEach(() => {
        fixture = TestBed.createComponent(MyParentComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
      });
    
      it('should create', () => {
        expect(component).toBeTruthy();
      });
    });
    

    無試験で



    テンプレートへの変更がテストスイートに影響を及ぼしなかったので、これは変更を必要としません!

    他の事柄


    DOMのどの部分もテストしないことで考慮する必要があるトレードオフがあります.我々がもはやDOMをテストしていない最大の、または、それとそれのコンポーネントクラスの間の統合.ほとんどの場合、ボタンがクリックされたときに、コンポーネントのクラスでメソッドを呼び出すことをテストします.イベントのバインディングをクリックするだけで動作します.したがって、私たちは、それが実際に呼び出す方法が予想通りに働くとほとんど心配します.しかし、我々はもはやこの統合をテストしていないため、我々はもはやチームの別の開発者が誤ってその統合を削除することを保証している.または、リファクタリング後にこの特定のボタンがこの特定のメソッドを呼び出します.
    私は、これが比較的小さいトレードオフでありえて、この種のテストがE 2 Eテストを使用してより適切に扱われることができると思っています.私は、これがテストへのオールオアナッシングアプローチでないと言及します.テンプレートとクラスの間の統合をテストするアプリケーションでは、テストベッドを使用することができます.あなたは本質的にちょうど今テストベッドを使用している部分の上に利益を得ることはありません.
    注:角のアプリは角バージョン7で実行されているこの例では.角度9以降では、テストベッドのためのいくつかのパフォーマンスの改善でリリースされたIvyを使用してアプリケーションをレンダリングします.

    結論


    私たちの些細な例から見ることができるように、我々の角度のコンポーネントspecファイルからテストベッドを削除することによって、我々は我々のテストランナーのパフォーマンスを向上させることができますし、いくつかのflakinessを削除することができます.もちろん、テスト速度が向上する大きさは、アプリケーションのサイズやアプリケーションの構築方法によって異なります.非常に大きなコンポーネント(より大きいコード匂いである)をもつアプリケーションは、このアプローチから最も利益を得ます.テストベッドのないテストを書くために最終的に最大の利点は、あなたが本当に書くこと、より信頼性の高い、簡単なフィードバックを提供する必要があります単体テストを書いていることです.より簡単に、より信頼性の高い、より迅速なフィードバックをテストすることができますより多くの場合は、単位のテストの利点を活用することができますテストを書くから得ることができます.