JSCover + WebDriverでテスト対象のJavaScriptのカバレッジを計測する


**追記: この記事ではJSCoverをプロキシモードで利用する方法を説明したが，いくつかのアプリケーションで，動作が不安定な場合(レスポンスが帰ってこない)ことがあった．過去には[こういうissue](https://github.com/tntim96/JSCover/issues/91)もあったようだが，関連はわからない．JavaScriptがサーバ側で動的に生成されるのでなければ，[file-systemモード](http://tntim96.github.io/JSCover/manual/manual.xml#gettingStartedFileSystem)などを利用するほうが安心かもしれない**

---

[Selenium WebDriver](http://www.seleniumhq.org/projects/webdriver/)ですでに書かれたテストが存在するときに，JavaScriptのカバレッジを記録したい．

### [JSCover](http://tntim96.github.io/JSCover/)とは

Javaで書かれたJavaScriptのカバレッジ測定用プログラム．以前は[JSCoverage](http://siliconforks.com/jscoverage/)というものが有名だったが，現在はJSCoverに引き継がれたっぽい．[公式サンプル](https://github.com/tntim96/JSCover-samples)がgithubにあるが，要点をここにまとめておく．

### 導入
pom.xmlに以下のdependencyを追加する

```xml
    <dependency>
      <groupId>com.github.tntim96</groupId>
      <artifactId>JSCover</artifactId>
      <version>1.0.6</version>
      <scope>test</scope>
    </dependency>
```

ついでにJUnitとSelenium (WebDriver)も導入しておく

```xml
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.seleniumhq.selenium</groupId>
      <artifactId>selenium-java</artifactId>
      <version>2.38.0</version>
      <scope>test</scope>
    </dependency>
```

### 方針
JSCoverの使い方もいろいろあるっぽい( [公式ドキュメント](http://tntim96.github.io/JSCover/manual/manual.xml) )が，今回はプロキシモードを利用する．JSCover自体がプロキシの働きをして，テスト時に動的にカバレッジ測定用にJavaScriptを書き換えてくれる．

1. JSCoverをプロキシとして起動
2. WebDriverが上のプロキシを利用する用に設定
3. テストを行い，JSCoverの画面を自動操作してカバレッジ測定結果をHTML形式で保存

*※ 以下のサンプルでは簡単のため常にカバレッジを計測しているが，実際の運用ではフラグなどを立てて必要な時だけカバレッジを図るようにしたほうがパフォーマンス上好ましいかも知れない*

#### JSCoverをプロキシとして起動

```java
    private static Thread server;

    // JSCover起動用オプション
    // 3129ポートを利用してプロキシとして起動する
    private String[] args = new String[]{
            "-ws",
            "--port=3129",
            "--proxy",
            "--local-storage",
           // カバレッジ測定除外設定，今回はjquery関係のファイルは無視
            "--no-instrument-reg=.*jquery.*",
            "--report-dir=" + getReportDir()
    };

    protected String[] getArgs() {
        return args;
    }

    @Before
    public void setUp() throws IOException {
        File jsonFile = new File("js-cover-report/jscoverage.json");
        if (jsonFile.exists())
            jsonFile.delete();
        // プロキシサーバの遅延初期化
        if (server == null) {
            server = new Thread(new Runnable() {
                public void run() {
                    try {
                        Main.main(getArgs());
                    } catch (IOException e) {
                        throw new RuntimeException("Err", e);
                    }
                }
            });
            server.start();
        }
    }
```

#### WebDriverが上のプロキシを利用する用に設定

```java
    private static WebDriver driver = getInstrumentedDriver();
    // JSCoverプロキシを利用する用設定したFirefoxDriverインスタンスの取得
    public static WebDriver getInstrumentedDriver() {
        Proxy proxy = new Proxy().setHttpProxy("localhost:3129");
        DesiredCapabilities cap = new DesiredCapabilities();
        cap.setCapability(CapabilityType.PROXY, proxy);
        return new FirefoxDriver(cap);
    }
```

#### テストを行い，JSCoverの画面を自動操作してカバレッジ測定結果をHTML形式で保存

```java
    @Test
    public void awesomeTestAndSaveJSCoverage() {
        driver.get("http://localhost/your_test_target");

        /* 何かしらのテスト */

        // JSCoverのカバレッジ一覧ページを開く
        driver.get("http://localhost/jscoverage.html");
        // 画面を自動で操作して"Store Report"ボタンを押下，カバレッジ測定結果を記録する
        new WebDriverWait(driver, /* 最大2秒待つ，この値は適当 */ 2).until(
                ExpectedConditions.elementToBeClickable(By.id("storeTab"))).click();
        new WebDriverWait(driver, 2).until(
                ExpectedConditions.elementToBeClickable(By.id("storeButton"))).click();
        new WebDriverWait(driver, 2).until(
                ExpectedConditions.textToBePresentInElement(By.id("storeDiv"), "Coverage data stored at"));

        // 測定結果をWebDriverで開く
        driver.get("file:///" + new File(getReportDir() + "/jscoverage.html").getAbsolutePath());
        // この先は人間が見てもいいし，WebDriverを使ってさらに自動化しても良い
    }
```

上記を合わせたコードをhttps://gist.github.com/daisy1754/8002254 においておく.