DACエンジニアブログ:アドテクゑびす界

DACのエンジニアやマーケター、アナリストが執筆するアドテクの技術系ブログです。

ゼロからAngularでSPAを作ってみた(3) CI: 継続的インテグレーション編

前回までのおさらいと今回やること

前々回(はじめてのアプリ編)前回(デプロイ・公開編)で、作成した Angular のチャットアプリを Web に公開するところまでいきました。 でも、デプロイするのに、毎回決まったコマンドを打つのって面倒ですよね。 今回は GitHub でソースコードを管理して、ビルド〜テスト〜デプロイのプロセスを自動化してみます。

初心者でもできるように基本的なことも書いているので、わかっている人は読み飛ばしてください。

前回までと同様に macOS 前提となっているので Windows の人は適宜読み替えてください。

GitHub でソースコードを管理する

GitHub に作成したコードを公開して管理します。アカウントが必要なので、持っていなければ作成します。

インストール・設定

必要なコマンドをインストールしていきます。

git コマンド

Homebrew を使って最新の Git をインストールします。

$ brew install git

hub コマンド

GitHub の操作に使うコマンドです。インストールしておきます。

$ brew install hub

自動補完とブランチ・ステータス表示設定

ここここを参考に、git-completion.bashgit-prompt.shhub.bash_completion.sh の設定をして git コマンドと hub コマンドを使いやすくします。

$ echo "source /usr/local/etc/bash_completion.d/git-completion.bash" >> ~/.bash_profile
$ echo "source /usr/local/etc/bash_completion.d/hub.bash_completion.sh" >> ~/.bash_profile
$ echo "source /usr/local/etc/bash_completion.d/git-prompt.sh" >> ~/.bash_profile
$ echo "GIT_PS1_SHOWDIRTYSTATE=1 GIT_PS1_SHOWUPSTREAM=1 GIT_PS1_SHOWSTASHSTATE=1" >> ~/.bash_profile
$ echo "export PS1='\h:\W \u\[\033[1;30m\]\$(__git_ps1)\[\033[0m\] \\$ '" >> ~/.bash_profile
$ source ~/.bash_profile

リポジトリ作成・コミット・プッシュ

GitHub への接続設定

インストールが終わったらここらへんを参考にして、公開鍵を使って GitHub に SSH で接続できるようにしておきます。

GitHub へのリポジトリ作成

アプリのディレクトリ(サンプルそのままであれば dac-angular )に移動して次のコマンドを打ってリポジトリを作成します。

$ hub create

コミット・プッシュ

これまでの変更内容を追加してコミット・プッシュしてリポジトリに反映させます。

$ git add .
$ git commit -m 'my 1st commit'
$ git push origin master

[caption id="attachment_4285" align="aligncenter" width="300"]GitHub でソースコードを公開・管理 GitHub でソースコードを公開・管理[/caption]

これで GitHub でソースコードが管理できるようになりました。これから先、Git や GitHub の操作についての説明は省略するのでわからない人はこことかで勉強してください。

Angular でのデバッグ・テスト

自動化する前に、Angular でのデバッグ・テストについて説明しておきます。

Visual Studio Code で Chrome を使ってデバッグ

Visual Studio Code では Chrome を使ってデバッグ(ステップ実行など)ができるようになっていて、次の手順で使えます。(大前提として Google Chrome がインストールされている必要があります) 拡張機能で Chrome を検索すると Debugger for Chrome が見つかるのでインストールします。

[caption id="attachment_4286" align="aligncenter" width="300"]Debugger for Chrome をインストール Debugger for Chrome をインストール[/caption]

メニューで「デバッグ」>「デバッグの開始」を選ぶと、Node.js と並んで「Chrome」が表示されるので「Chrome」を選択します。

[caption id="attachment_4287" align="aligncenter" width="300"]Visual Studio Code でデバッグを開始 Visual Studio Code でデバッグを開始[/caption]

[caption id="attachment_4288" align="aligncenter" width="300"]デバッグで Chrome を選択 デバッグで Chrome を選択[/caption]

デバッグ用の設定ファイル launch.json が生成されるのでポートを 8080 から 4200 に変更します。

[caption id="attachment_4289" align="aligncenter" width="300"]launch.json が生成される launch.json が生成される[/caption]

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "chrome",
            "request": "launch",
            "name": "Launch Chrome against localhost",
            "url": "http://localhost:4200",
            "webRoot": "${workspaceFolder}"
        }
    ]
}

これで準備ができました。メニューで「デバッグ」>「デバッグの開始」を選ぶとデバッグできるようになります。

Visual Studio Code で統合ターミナルを使う

Visual Studio Code で統合ターミナルを使うと、コマンドラインの実行が楽になります。メニューで「表示」>「統合ターミナル」を選ぶと表示されます。

[caption id="attachment_4290" align="aligncenter" width="300"]Visual Studio Code で統合ターミナルを開く Visual Studio Code で統合ターミナルを開く[/caption]

書式チェック(TSLint: ng lint)

次のコマンドで書式チェックができます。内部では TSLint が使われて、設定ファイル tslint.json を参照します。

$ ng lint

スペースがないとか、余計なスペースがあるとか、セミコロンがないとか、ダブルクォーテーションとシングルクォーテーションの使い分けとか、細かいところでいろいろ怒られます…

単体テスト(Karma: ng test)

次のコマンドで単体テストが実行されます。内部では Karma が使われて、設定ファイル karma.conf.js を参照します。

$ ng test --single-run --code-coverage

実行時に --single-run オプションをつけると1回のみ実行されて、--code-coverage オプションをつけると coverage フォルダにコードカバレッジ(網羅率)の結果が保存されます。

[caption id="attachment_4291" align="aligncenter" width="300"]Karma でのテスト画面 Karma でのテスト画面[/caption]

[caption id="attachment_4347" align="aligncenter" width="300"]コードカバレッジ コードカバレッジ[/caption]

単体テストは各コンポーネントの *.spec.ts ファイルに記述します。 元からあるファイルは雛形用なので、作ったアプリに合わせて変更していきます。

import { TestBed, async } from '@angular/core/testing';
import { AngularFirestore } from 'angularfire2/firestore';
import { environment } from '../environments/environment';
import { AppComponent } from './app.component';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/from';

const timeline = 
  { message: 'Hello, Test!' }
;
const data = Observable.from(timeline);
const collectionStub = {
  valueChanges: jasmine.createSpy('valueChanges').and.returnValue(data)
};
const angularFiresotreStub = {
  collection: jasmine.createSpy('collection').and.returnValue(collectionStub)
};

describe('AppComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ],
      providers: [
        { provide: AngularFirestore, useValue: angularFiresotreStub }
      ]
    }).compileComponents();
  }));
  it('should create the app', async(() => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.debugElement.componentInstance;
    expect(app).toBeTruthy();
  }));
  it('should have timeline', async(() => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.debugElement.componentInstance;
    expect(app.timeline).toBeDefined();
  }));
  it('should render message in a li tag', async(() => {
    const fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();
    const compiled = fixture.debugElement.nativeElement;
    expect(compiled.querySelector('ul>li').textContent).toContain('Hello, Test!');
  }));
});

単体テストなので Firestore 上のデータは使いません。その代わりにここらへんを参考に Firestore のモックデータのコードを app.component.spec.ts に追加して、テストを通るようにしました。

結合テスト: エンドツーエンドテスト(Protractor: ng e2e)

次のコマンドで結合テストが実行されます。内部では Protractor が使われて、設定ファイル protractor.conf.js を参照します。

$ ng e2e

[caption id="attachment_4292" align="aligncenter" width="300"]Protractor でのテスト画面 Protractor でのテスト画面[/caption]

結合テストは次のファイルに記述します。

  • + e2e
    • app.e2e-spec.ts
    • - app.po.ts

こちらも元からあるファイルは雛形用なので、アプリに合わせて変更していきます。

import { AppPage } from './app.po';
import { browser } from 'protractor';

describe('dac-angular App', () => {
  let page: AppPage;

  beforeEach(() => {
    page = new AppPage();
  });

  it('should display first chat message', () => {
    page.navigateTo();
    browser.sleep(5000);
    expect(page.getFirstMessage()).toEqual('Hello, World!');
  });
});
import { browser, by, element } from 'protractor';

export class AppPage {
  navigateTo() {
    return browser.get('/');
  }

  getFirstMessage() {
    return element(by.css('app-root ul li')).getText();
  }
}
// (省略)
    });
    jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
    browser.ignoreSynchronization = true; // 追加
  }
};

Protractor を Angular で使うと、描画が完了するまでテストの実行を待ってくれる便利な機能があるのですが、Firebase と組み合わせたときにうまくいかなかったので、5秒後にテストを実行するように変更しました。 また、このテストは実際の Firestore 上のデータを参照するので、Firestore のデータが変わるとテストが失敗します。そのため注意が必要です。

Travis CI でビルド〜テスト〜デプロイを自動化する

ここから CI(継続的インテグレーション)として、ビルド〜テスト〜デプロイを自動化していきます。 CI の環境としては、古くからあるものとしては Jenkins、サービス型としては CircleCI などもありますが、今回は Travis CI を使います。アカウントが必要なので GitHub のアカウント情報を使ってログインします。

インストール・初期設定

Travis CI Client を使って設定を行うので、その前提として必要となる Ruby からインストールしていきます。(Ruby 環境が設定済みの場合は飛ばしてください)

rbenv のインストール・設定

今後のことも考えて Ruby のバージョンを切り替えられるように rbenv からインストール・設定していきます。

$ brew install rbenv
$ rbenv init
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile

Ruby のインストール・設定

以下では、執筆時点での最新安定板の 2.5.0 をインストールしています。(インストール可能なバージョンは rbenv install --list で確認できます)

$ rbenv install 2.5.0
$ rbenv global 2.5.0

Travis CI Client のインストール

Travis CI Client をインストールします。

$ gem install travis

Travis CI の初期設定

次のコマンドで Travis CI の初期設定を行います。

$ travis login --auto
$ travis init node_js

コマンドを実行すると、Travis CI の設定ファイル .travis.yml が生成されるので以降ではこのファイルに設定を追加していきます。

ビルド・テスト設定

ここここを参考に Travis CI でビルドやテストを実行する設定を追加していきます。

dist: trusty
sudo: required
language: node_js
node_js:
- '8.9.4'
addons:
  chrome: stable
cache:
  apt: true
  bundler: true
  directories:
  - node_modules
before_install:
- export CHROME_BIN=chromium-browser
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
before_script:
- ng lint
- ng test --single-run --code-coverage
- ng e2e
script:
- ng build --prod

カバレッジ設定

Coveralls と連携してコードカバレッジを取得・表示させるための設定をします。

Coveralls アカウントの作成・設定

Coveralls に GitHub のアカウントを使ってログインして、連携設定を ON にします。

[caption id="attachment_4354" align="aligncenter" width="300"]Coveralls で連携設定を ON にする Coveralls で連携設定を ON にする[/caption]

Coveralls のインストール

次のコマンドで Coveralls をインストールします。

$ npm install coveralls --save-dev

Coveralls 用の設定を追加

.travis.yml に Coveralls のための設定を追加します。

# (省略)
after_success:
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js

デプロイ設定

ここを参考にして Firebase のデプロイ設定を進めていきます。

Firebase キーの取得と設定

次のコマンドを実行するとブラウザの画面が開くので Firebase CLI の権限を与えるとキーが取得できます。(ここで取得したキーは秘密です)

$ firebase login:ci

取得したキーを使って次のコマンドを実行して .travis.yml に暗号化したキーを追加します。(警告は無視。以下コマンドのXXXX... の部分は実際のキーに置き換えてください)

$ travis encrypt XXXXXXXXXXXXXXXXXXXXXX... --add

Firebase デプロイ設定を追加

暗号化キーが追加された部分を書き換えて、Firebase にデプロイするように設定します。(以下の YYYY... の部分は暗号化されたキーです)

# (省略)
deploy:
  provider: firebase
  token:
    secure: YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY...
  skip_cleanup: true
  on:
    branch: master

まとめ

これで、コミットして GitHub にプッシュすると、ビルド〜テスト〜デプロイが自動的に実行されるようになりました〜

[caption id="attachment_4359" align="aligncenter" width="300"]GitHub での表示 GitHub での表示[/caption]

[caption id="attachment_4360" align="aligncenter" width="300"]Travis CI の実行結果 Travis CI の実行結果[/caption]

[caption id="attachment_4361" align="aligncenter" width="300"]Coveralls でのカバレッジ表示 Coveralls でのカバレッジ表示[/caption]

ここまでのサンプルはここからダウンロードできます。(Firebase の設定などは自分のものに変更して使ってください) 次回は、マテリアルデザインに挑戦してみる予定です。

↓↓↓ ¡ DACはエンジニアを募集しています ! ↓↓↓