Angularユニットテスト

# Angularユニットテスト

---

## ユニットテスト・・・？

クラスやメソッドを対象に動作を保証するもの  

### 進め方

1. テスト対象を選定する  
2. その機能に関してのテスト項目を網羅する  
3. 実装する  
4. テストコードのレビュー  
5. テストをする  
6. 実装結果に応じてテスト対象の修正等を行う  
7. テストする  
...


### 基準？

[テスト設計技法](https://www.deep-rain.com/programming/program/615#i-3)  
テストコードは数行で済むものにしなければならない  
１つのテストには１つのアサーション  


### 戒め

1. テストを実施するということが目的ではない
2. ユニットテストにはある程度の知識、スキルが必須
3. テストをしながらテスト対象を改修する体制が必要
4. ユニットテストの工数は実装コストの二倍以上
5. テストしたから品質が上がるわけではない
6. 全てのコードにテストが出来るわけではない
7. カバレッジが品質や進捗を指し示すわけではない
8. 書いて満足してはいけない
9. 結合テストは別途必要
10. テストに必ず効果があるわけではない

---


Angularのテストでは、JavaScriptで使用されているテストツールを使用する。
Angular公式ではJasmine+Karmaの組み合わせでユニットテストを実施することが推奨されている。

### Jasmine

JavaScript用のテスティングフレームワーク

### Karma

テストランニグツール

上記はAngular用である為、ionic-cliでプロジェクトを作成した場合は導入されていない

---

## ionicプロジェクトにテストツール導入

### テスト用のプロジェクト作成

~~~
ionic start PROJECT_NAME blank
~~~

### 公式からテストプロジェクトをクローン

~~~
git clone https://github.com/ionic-team/ionic-unit-testing-example.git my-project-name
~~~

公式のテストサンプルから必要ファイルを持ってくる為、フォルダを開いておく

### テストを行いたいプロジェクトを開き必要なモジュールをインストール

~~~
cd PROJECT_NAME

npm install --save-dev angular2-template-loader html-loader jasmine jasmine-spec-reporter karma karma-chrome-launcher karma-jasmine karma-jasmine-html-reporter karma-sourcemap-loader karma-webpack karma-coverage-istanbul-reporter istanbul-instrumenter-loader null-loader protractor ts-loader ts-node @types/jasmine @types/node
~~~

### テストを行いたいプロジェクトの`package.json`を開き`scrip`部分に以下を追加

~~~JSON:package.JSON
"test": "karma start ./test-config/karma.conf.js",
"test-ci": "karma start ./test-config/karma.conf.js --single-run",
"test-coverage": "karma start ./test-config/karma.conf.js --coverage",
"e2e": "npm run e2e-update && npm run e2e-test",
"e2e-test": "protractor ./test-config/protractor.conf.js",
"e2e-update": "webdriver-manager update --standalone false --gecko false"
~~~

文末のカンマを忘れてはならない  

### クローンしたプロジェクトから以下ファイルが含まれている`test-config`フォルダをテストを行いたいプロジェクトのルートにコピーする

~~~
test-config
      >-karma-test-shim.js
      >-karma-config.js
      >-mocks-ionic.js
      >-protractor.conf.js
      >-webpack.test.js
~~~

### e2eテスト用に`e2e`フォルダをテストを行いたいプロジェクトのルートにコピーする

~~~
e2e
      >-app.e2e-spec.ts
      >-app.po.ts
      >-tsconfig.json
~~~

### 必要なモジュールをインストールする
~~~
npm install
~~~

### テストを試してみる

クローンしてきたサンプルの`/src/app`内の`app.component.spec.ts`をテストしたいプロジェクトの`/src/app`にコピー

### テストを実行

~~~
npm run test
~~~

### other

テストが実行できない場合はクローンしてきたプロジェクトのpackage.jsonの`devDependencies`の値に合わせる

`descrive`等が赤線がついている場合は、`spec.ts`ファイルに以下を追加する

~~~
import {} from 'jasmine';
~~~

---

##  Jasmineでのテストの書き方

### descrive

~~~
descrive(name,specs)
name:テストスイートの名前
specs:テストケース
~~~

テストコード全体を`descrive`で囲む
テストグループを作成するもの

`beforeEach`→テスト実行前の事前準備  
`afterEach`→テスト実行後の後処理

### it

~~~
it(name,test)
name:テストケースの名前
test:テスト内容
~~~

`it`が個々のテストケース

### expect

~~~
expect(resultValue).matcher(expectValue)
resultValue:テスト対象のコード
matcher:検証メソッド
expectValue:期待値
~~~

`expect().matcher()`でテスト検証を行う

[matcher一覧](https://mae.chab.in/archives/59850)

### SAMPLE

~~~typescript:sample.spec.ts
descrive('テストサンプル',()=>{
    beforeEach(() =>{
        //初期化
      });

    it('テスト内容サンプル',() =>{
        expect(1+1).toEqual(2);
      });
  });
~~~

---

## 具体的なテストの書き方

### パイプ

通常のクラスを検証するときと同様にテストコードを作成する。
パイプクラスをインスタンス化し、itメソッドでパイプのメソッドを呼び出してMatcherで検証する

~~~typescript:sample_pipe.spec.ts
//小文字を大文字に変換するパイプの場合
descrive('パイプテストのサンプル',()=>{
    let pipe = new SamplePipe();
    let check_msg = 'sample';

    it('挙動確認',()=>{
        expect(pipe.sample(check_msg)).toEqual('SAMPLE');
      });
  });
~~~

### サービス

パイプと同様にクラスを実行し、Matcherで検証する

~~~typescript:sample_service.spec.ts
//格納されたデータを取り出すサービスの場合

import{SampleService} from './sample.service';

descrive('サービスのサンプルテスト',()=>{
    let service SampleService;

    beforeEach(()=>{
        service = new SampleService();
      });

    it('getDataメソッドの動作確認',()=>{
        let data = service.getData();
        //データが取得できたのか確認
        expect(data).toBeDefined();
      });

  });
~~~

サービスが単体で完結することは少ない  
→http/jsonpなどの非同期通信サービスに依存することが多い

### コンポーネント

Angular内部の処理を意識する必要がある為、複雑になる  
コンポーネントのテストには、  
テストモジュールの準備→コンポーネントのインスタンス化→変更検知  
というAngular内部の処理をテストコードとして明示的に記載しなくてはいけない。  

~~~typescript:app.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyApp } from './app.component';

import { By } from '@angular/platform-browser';
import { DebugElement} from '@angular/core';

describe('MyApp Component', () => {
  let de: DebugElement;
  let component: MyApp;
  let fixture: ComponentFixture;

  beforeEach(() => {
    //テスト用モジュール
    TestBed.configureTestingModule({
      declarations: [MyApp]
    });

    //コンポーネントのインスタンス化
    fixture = TestBed.createComponent(MyApp);
    component = fixture.componentInstance;

    //テスト対象要素を取得
    de = fixture.debugElement.query(By.css('h1'));
  });

  it('<h1>要素の存在確認', () => {
    fixture.detectChanges();
    const h1 = de.nativeElement;
    expect(h1.innerText).toMatch(/angular/i);
  });

});

~~~

TestBedはユニットテストのための環境を初期化/設定するためのクラス。コンポーネント/サービス作成するのためのメソッドも提供。