Front-end Testing Research vol.1

フロントエンドテスティングについての資料調査中、良い資料があって資料の一部だけを日本語に翻訳してみました。
以下がNHNの講義資料になります。

REFERENCE: [NHN, "Frontend Testing"](https://nhnent.dooray.com/share/posts/9jRYF1fxRwiCvwi6VM0VgA)

# テストとは？
アプリケーションが要求事項に合わせて動作するかを検証する行為

## テストの例
```
DBにデータを入力するAPIを開発 → API呼び出し → DB値検証
デザイン試案に合わせてHTML/CSSを作成 → ブラウザで実際のレンダリング結果を確認
新しい機能を追加するため、既存モジュールをリファクトリング → 影響を受ける他のモジュールの実行結果を確認
バグを修正するために既存関数を修正 → バグが修正確認&影響を受ける他のモジュールの実行結果を確認
開発環境でテストされたアプリケーションをリアル環境に配布 → 配布過程で発生した問題がないか確認
```

🤖 開発者は事実上コードを作成するよりも多くの時間をテストに使用


# なぜテストを作成するのか
## テスト自動化

#### 🙂 長所
- 人が行うべき反復テストを自動化できる（コスト削減）
- 人が行うことよりはるかに早くテストすることができる
- 人が遂行することより信頼できる

#### 🙃 短所
- 感覚的な要素(視覚、聴覚)などユーザ経験に関する問題を見つけることができない
- 実際の環境で起こる様々な状況を自動化することが困難（ネットワーク、デバイス関連など）
- テスト自動化は誰がするのか
  QA, 開発者二人とも

## 開発者がテスト作成しなければならない理由
#### 製品品質
- 開発者は作成したプログラムのクオリティに対する責任がある。
- QAに渡す前に基本要求事項をすべて満足しているかどうかの検証は開発者が行うこと
- 自動化されたテストを作成しておかないと、アプリケーションが複雑になるほどテスト費用が増える
- その場合、開発期間や人員等は限られているため、テストをおろそかにすることが多い
- そうでない場合、***QAとのコミュニケーション費用***が増え、業務効率が低下する

#### コード品質
- コード品質のためには引き続きリファクトリング等の改善作業が必要
- この過程で、従来うまく動作していたプログラムを台無しにする可能性があるため、積極的にコードを改善しなくなる
- 信頼できる自動化テストがあれば、***積極的にコードを改善できる***
- ~~恐怖(Fear)~~ → ***自信(Confidence)***

## TDD
- TDD:テスト主導開発(TestDrivenDevelopment)
- テストを先に作成した後、実装コードを作成する開発方法論
- 実際に実装コードを使用する立場からまず考えることができるので、デザインに役立つ
- TDDが常に良いデザインを保障していない->思考を助ける方法論であるだけで、
- TDD!==自動化テスト(TD!==単体テスト)
- TDDが適切でない種類のプログラムもある（UI開発など）
[When TDD Doesn't work - Uncle Bob](http://blog.cleancoder.com/uncle-bob/2014/04/30/When-tdd-does-not-work.html)
  > "now we have two places where TDD is impractical or inappropriate. The physical boundary, and the layer just in front of that boundary that requires human interaction to fiddle with the results."

# テストの種類
## 分類
#### 囲によって
- 単体(Unit)テスト
- 統合(Integration)テスト
- E2E(EndtoEnd)テスト
  * 機能(Functional)テスト
  * システムテスト
  * UI(UserInterface)テスト

#### その他
- 回帰(Regression)
- 性能(Performance)テスト

| テストの種類  | 特徴 | 参考 | 
|:----------|:----------:|:----------:|
| 単体(Unit)テスト | モジュール(関数/クラス)単体のテスト<br>テスト部分のコードを他のシステムから分離させたままテスト<br> 作成費用が少なくて実行速度が速い。<br>失敗した時に問題が生じた部分を比較的正確に把握することができる。<br>場合によって、一つや二つの単位をまとめて一つの単体として扱うこともある。|  | 
| 統合テスト| 主に単体テストより大きな範囲のテストを意味する。<br>個別モジュール(関数/クラス)が接続されて正しく相互作用するかをテスト<br>単体テストに比べて作成が難しく実行速度が遅い。<br>単体テストに比べて失敗時に問題が生じた部分を正確に把握することが難しい。 | Narrow vs Broad<br>***狭い統合テスト***:テストダブルを利用して外部サービスを実際に駆動せずにテスト<br>***広い統合テスト***:依存性のあるすべての外部サービスを使用してテスト|
| E2E テスト | 実際にユーザが使用するものと同じ条件で全システムをテスト<br>APIサーバー、DBなどの外部サービスをすべて使用して統合されたシステムをテスト<br>単体/統合テストに比べて作成が難しく実行速度が最も遅い。<br>問題が起こった部分を正確に把握することが最も困難<br>機能(Functional)テストと似た意味で使われる。<br>***(G)UIテスト***:back-endシステムをMockingしたままUIだけをテストすることも可能 ||

# フロントエンドテスト
## フロントエンドの構成要素
#### 視覚的表現
- 画面に表示されるビジュアル要素をデザイン要求事項に合わせて実現
- レイアウト/色/フォント/イメージ/アニメなど
- 主にHTML(DOM)/CSSによって決定

#### ユーザー入力処理
- ユーザーによるマウス/キーボード入力などを要求事項に合わせて処理- 
主にDOMにバインディングされたイベントハンドラーによって処理

#### アプリケーションステータス管理
- ユーザ入力等により変更されるアプリケーションの状態を管理
- Routing/ポップアップ表示-非表示/読み取り-編集モード変更/エラーメッセージ表示
- 主に純粋なジャバスクリプトによって処理

#### サーバーとの通信
- RESTAPI/Socketなどでサーバと通信し、アプリケーションの状態を同期化
- 主にブラウザAPIまたはライブラリを使って非同期ロジックを遂行

# フロントエンド要素別テスト戦略
## フロントエンド要素別テスト戦略

#### 視覚的表現
- 実際の画面をピクセル単体でテスト → 目で直接確認or自動スクリーンショットテスト
- HTML構造をテスト → HTML構造を直接入力orスナップショットテスト
- ~特定のDOM要素の状態のみテスト(ボタンの状態/テキスト) → 視覚的テストではない~

#### ユーザー入力処理
- ジャバスクリプトAPIを使ったイベントシミュレーション
- ライブラリ(jquery,React)を用いたイベントシミュレーション
- E2Eツールを利用したイベントシミュレーション
#### サーバーとの通信
- 実際のAPIサーバーを利用 → 統合/E2Eテスト
- Ajax通信モジュールをMocking/仮想APIサーバーを構築 → 単体/統合テスト
- サービスレイヤーを分離してMocking-単体/統合テスト
#### アプリケーションステータス管理
- アプリケーションの状態を管理するレイヤーだけを分離してテスト → 単体テスト
- 状態とバインディングされたDOM要素の状態をテスト → 統合テスト

## フロントエンドテスト戦略:考慮すべき点
#### アプリケーションの種類
- ビジュアル要素が重要か（例:チャート）
- すべてのブラウザでテストすべきか（例:エディタ）
#### アプリケーションの規模や複雑性
- 簡単なライブラリー(例:デートピッカー)
- 複雑なライブラリ(例:グリッド)
- 複雑なウェブサービス（例:ドゥレイ、トーストファイル）
#### チーム構成
- 別途のQAチームがあるか
- サーバ-クライアントの両方をコントロールできるか

## イベントシミュレーション方式
#### 純粋ジャバスクリプト
- createEvent()またはCustomEvent生成者を利用してイベントを作成
- dispatchEvent()を利用してイベント発生
- シミュレーションが難しいイベント(mousemove,mouseoverなど)はテスト不可
#### ライブラリー/フレームワーク
- jQuery等のDOMライブラリー使用時trigger()等を通じて簡単にシミュレーション可能
- Reactは独自のイベントシステムを持つため、Reactで提供するイベントシミュレーション関数を使用
- Angular、Vueなどもイベントシミュレーションツールを提供
- シミュレーションが難しいイベント(mousemove,mouseoverなど)はテスト不可
#### E2E (Selenium Webdriver)
- 実際のブラウザの行為をシミュレーション
- カスタムイベントを発生させるよりも実際の環境に近くテスト可能

# 実習: UIテスト
実際に以下のテスト戦略で簡単なテストコードを作成して実行してみたときの問題点を見てみた。
## テスト戦略1:HTML構造テスト
#### 戦略
- HTMLを文字列で直接比較
- diffが容易になるようHTML構造を作ってくれるライブラリ使用
- https://github.com/rayrutjes/diffable-html

## テスト戦略2:Snapshotテスト
#### 戦略
- 現在のHTML構造をそのままファイルに保存した後、変更されるたびにテスト失敗
- 実際の結果はファイル内部を直接確認する必要がある。
- HTMLを直接比較するよりも簡単
- 回帰テストの役割

## 戦略1、2に対する評価
#### 細部実装テスト
- 実際のUIに対する結果はピクセルで表現される画面であってHTMLではない。
- HTMLは細部の実装方式についての内容
- HTMLは同じでもCSSによって結果物(ピクセル)が完全に変わることがある。
- HTMLが変更されても結果物(ピクセル)は同一である場合がある。
- テストコード（HTML構造）を見て意図（実際の画面）を把握するのが困難
#### スナップショットテスト
- テストコードを見て意図の把握が困難
- テスト意図をコードで入力しない→回帰テストの用途のみに使用
- スナップショットデータのDiffを見ても意図が把握しにくいときがある。
- 習慣的なアップデート->テストの信頼性減少


## 視覚テスト
#### 視覚テストの難しさ
- 視覚的要素に対するテストは自動化が難しい
- スクリーンショットを直接撮って比較する方法が最もテストの目的に合う。
- 希望するアウトプットをあらかじめ作り出すことが難しいため、「回帰（Regression）」テストのみ可能
  * デザイン試案がケース別にきちんと整理されていれば期待値として使用可能
  * しかし、ブラウザ余白、イメージサイズなど技術的に克服すべき部分が多い
  * また、ブラウザやOSによってレンダリング方式が異なるため、技術的限界がある。
    - 同じ画像ですが、ピクセルが細かく異なる場合が多い。
  * 変更された部分を正確に感知し、意図的な変更かどうか把握することが困難
    - 変更点と履歴管理のための別途ツールが必要
#### ストーリーブック(Storybook)
- アプリケーションから分離された別途の環境でUIだけを開発できるようサポートするツール
  * 視覚的表現の開発だけ速く進めることができる。
- コンポーネント方式の開発によく似合う(React、Vue、Angular)
  * コンポーネントギャラリーとして活用
  * デザイナー/企画者とのコミュニケーションツールとして有用
  * Airbnb Dates
  * Wix Style
- ストーリーブックを使用すると視覚的テストを手動で行うとより効率的になる。

#### 視覚的回帰(regression)テスト
- 近年、技術的限界を克服した様々なテストツールが発売され発展している中
  * AI等を活用して有意義な違いだけを感知
  * 大規模スナップショットファイルの履歴管理、画像直接比較、スナップショット更新などの管理をサポートする。
  * ストーリーブック/Crypess/セレニウムなどのツールと連動して使える
  * [Percy](https://percy.io/), [Applitools](https://applitools.com/), [Chromatic](https://www.chromatic.com/)

## 視覚的要素と機能的要素を分離する
- 視覚的要素と機能を同時にテストすることは、テストの複雑度を増加させる。
- 視覚的要素をストーリーブックのような道具に分離し、機能と状態変更だけをテストするのが効率的である。
  * 実際のUIの"コンテンツ(状態)"のみをテスト(視覚的要素を除く)
  * ボタンの存在の有無/ボタンの状態/画面上のテキストなど
- HTML構造との従属性を最小化する
  * 機能だけのための別途HTMLプロパティ、クラスなどを使用
  * CSSセレクター使用時タグ子供選択者等を指定

# E2E テスト
## Selenium Webdriver

#### Selenium RC(Remote Control)
- Selenium1.0
- Javascriptをブラウザ内部に挿入して制御する方式
- JavaScriptのサンドボックスから抜け出せない
- すべての種類のブラウザに完璧に対応していない
- これ以上サポートされていない

#### Selenium WebDriver
- Selemium 2.0
- ブラウザ外部で制御する方式
- ネイティブレベルでブラウザを直接制御
- WebDriver明細に従う各ブラウザ別ウェブドライバーをインストールして使用
- ほとんどのブラウザや実行環境をサポート
  * FirefoxDriver / ChromeDriver / InternetExplorerDriver / SafariDriver / Appium
#### WebDriver明細
- https://www.w3.org/TR/webdriver/
- Server/Client構造でHTTPを利用してブラウザに命令を下せるAPIを定義
####  Selenium Grid
- ハブの役割を果たすマシンがノードの役割を果たすすべてのマシンにテストを指示し、結果をまとめて返還
- テストを様々な環境のマシンで同時に行える環境を提供
#### 活用
- QA開発者全員が使用
- WebDriverクライアントの言語はどの言語でも使用可能
  * Java / C# / Ruby / Python / Javascript
## SeleniumWebdriverライブラリー
#### Protractor
- http://www.protractortest.org
- プロジェクトの活性化&文書化が最もよくできている
- Angularプロジェクトでのみ使用
#### WD
- http://admc.io/wd
- JsonWireProtocolをジャバスクリプトに呼び出せるAPIのみを提供
- テストランナー/Assertion等のサポートなし:テスト環境の別途構築が必要
- プラグインサポートなし/文書は基本的なレベル
- Replサポート
- Promise(Q)ベース/チェイニング形式のAPI
- ジェネレーターベースのライブラリーも提供(Yiewd)
```
browser
  .get("http://www.google.com")
  .elementById('q')
  .sendKeys('webdriver')
  .elementById('btnG')
  .click()
```
#### seleniumn-webdriverjs
- https://github.com/SeleniumHQ/selenium/wiki/WebDriverJs
- Seleniumwebdriverの正式Node.js実装体
- テストランナー/Assertion等のサポートなし:テスト環境の別途構築が必要
- プラグインサポートなし/文書は気分的なレベル
- APIが複雑
```
driver.get('http://www.google.com');
driver.findElement(webdriver.By.id('q')).sendKeys('webdriver');
driver.findElement(webdriver.By.id('btnG')).click();
```

#### NightWatch
- http://nightwatchjs.org
- Mochaベースのテストランナーをサポート
- Assertionライブラリーを選択可能
- コミュニティの活性化&文書化されている
- SauceLabs/BrowserStackのような外部サービスに対応
```
browser
  .url('http://www.google.com')
  .setValue('#q', 'webdriver')
  .click('#btnG');
```

#### WebdriverIO
- http://webdriver.io
- テストランナー支援
- Assertionライブラリー選択可能(Jasmineプラグイン対応)
- コミュニティの活性化&文書化されている(NightWatchよりもっと)
- SauceLabs/BrowserStackのような外部サービスに対応
- Staticウェブサーバー+Webpack統合支援
- VisualRegressionテスト対応
- Jenkins統合支援
- Replインターフェース対応
- 拡張性が良い
```
client
  .url('http://google.com')
  .setValue('#q', 'webdriver')
  .click('#btnG')
```

## 単体/統合テストvsE2Eテスト
#### 単体/統合テストのデメリット
- 実際のユーザの環境で発生するバグの検出が困難
- 各モジュール間の接続から発生するバグの検出が困難
- Jest等のNode環境でテストする場合、実際の画面を見ることができない。
#### E2Eテストのデメリット
- 速度の遅さ
- テストコードの作成が煩わしい
- 実行環境に対する統制が容易ではなく、安定性に欠ける。
#### 解決策
- ユーザの観点からテストをしながらも、迅速かつ使いやすく安定した環境が必要
- 最近E2Eテストの短所を克服した様々なツールが発売

## 最新E2Eテストツール
#### Cypress
- https://github.com/cypress-io/cypress
- ブラウザを扱うことができる別途のドライバを作って使用
- E2Eだけでなく、統合、単体テストまで使用可能
- GUIツールをサポート。 スペック管理·デバッグが便利。
- ブラウザ内部でテスト(現在クロムのみ対応)
  * ブラウザ対応ロードマップ:https://github.com/cypress-io/cypress/issues/310
- 主な概念:https://docs.cypress.io/guides/overview/key-differences.html
- DashBoard
  * すべてのテスト過程と結果を保存し、一目で分析/管理できるサービス(有料)

#### test cafe
- https://devexpress.github.io/testcafe/
- プロキシサーバーを通じてスクリプトをページに注入して使用
- すべてのブラウザで使用可能(モバイル、クラウドブラウザを含む)
- Test Cafe Studio
  * GUIにより簡単にテスト管理·作成（有料）

#### Selenium vs Cypress
|  | Selenium | Cypress | 
|:----------|:----------:|:----------:|
| ユーザ |　QA/開発者 | フロントエンド開発者  | 
| 使用言語|　ほとんどの言語 | Javascript | 
| 対応ブラウザ | ほとんどのブラウザ | Chrome(追加予定) |
| 動作方式 | ブラウザ外部 | ブラウザ内部 |
| スピード | 遅い | 早い |
| サーバーモックキング | 別途のツール使用 | 内蔵 |
| デバッグ | 難しい | 易い |


# まとめ

## フロントエンドテスト戦略
#### 視覚的要素vs機能的要素
- 視覚的要素をHTMLやCSSを用いてテストすることは避ける。
- ストーリーブックなどの道具を使って視覚的要素の多様な状態をテストしやすく管理する。
- イメージ比較方式のテストツールとストーリーブックを結合すれば、視覚的テストを自動化できる。
- 機能的要素をテストする際には、できるだけHTML構造の変化に影響を受けないようにする。
#### テスト単位
- 核心アルゴリズムを含んでいるモジュールでなければ単体テストを中止する。
- 内部モジュール間の依存性をMockingすることは控え、統合テストを中心に作成する。
- Cypressのような最新のE2Eツールは、より良いフロントエンドテスト環境を提供してくれる。