Twitter製レグレッション検知ツールDiffyを試してみた

## はじめに

先日TwitterがDiffyというレグレッション検知ツールをOSSとしてリリースしました。

* [ブログpost](https://blog.twitter.com/2015/diffy-testing-services-without-writing-tests)
* [github](https://github.com/twitter/diffy)

新環境・旧環境両方にリクエストを投げるプロキシとして働き、両環境のレスポンスの差分からレグレッションが無いか判定してくれるツールのようです。リクエストをプロキシしてレスポンスをチェックするツールなので、テスト対象側はどんな言語で書かれていてもOKです。

主にAPIサーバ等でのテストの負担を減らせそうなのでとりあえず試してみました。
とりあえず試してみた程度なので、ブログ記事ちゃんと読んで把握された方や既に試した方はスルーして貰えればと思いますm(_ _)m

## 手順

### アプリをデプロイする

レグレッションテストしたいアプリをデプロイします。
デプロイは旧環境(正常に動いている環境)を2つ、リリースターゲットととなる新環境を1つデプロイする必要があります。

今回はサンプルとしてこんな感じのjsonを返すアプリをデプロイしました。

* 正常系(新も旧もtimestampを除いて同じ結果を返す)

```bash:旧環境1
$ curl http://localhost:3000/home/user1?q=hogehoge
{"url":"/home","name":"user1","query":"hogehoge","timestamp":1441405745615}
```

```bash:旧環境2
$ curl http://localhost:3001/home/user1?q=hogehoge
{"url":"/home","name":"user1","query":"hogehoge","timestamp":1441405755831}
```

```bash:新環境
$ curl http://localhost:3002/home/user1?q=hogehoge
{"url":"/home","name":"user1","query":"hogehoge","timestamp":1441405761161}
```

* 異常系(新はレスポンスのqueryというフィールドに'extra'という余計な値を付けて返している)

```bash:旧環境1
$ curl http://localhost:3000/fail/user1?q=hogehoge
{"url":"/fail","name":"user1","query":"hogehoge","timestamp":1441405772799}
```

```bash:旧環境2
$ curl http://localhost:3001/fail/user1?q=hogehoge
{"url":"/fail","name":"user1","query":"hogehoge","timestamp":1441405776149}
```

```bash:新環境
$ curl http://localhost:3002/fail/user1?q=hogehoge
{"url":"/fail","name":"user1","query":"hogehoge extra","timestamp":1441405778646}
```

異常系の新環境では戻り値のjsonで" extra"という余分なテキストをつけています。
これがレグレッションとして判定されることを確認したいと思います。
また、timestampの値は全環境で異なりますが、これはレグレッションとして誤判定されないことを確認したいと思います。
(少しだけ注意点もあるので最後にコードを載せました)

### diffyの入手とビルド

9月4日現在はgithubのレポジトリにリリースは無いので、自分でビルドする必要があります。
特に詰まることは無いと思います。

```bash:入手とビルド
git clone https://github.com/twitter/diffy.git
cd diffy
./sbt assembly
```

### 起動

同じディレクトリで以下のように起動します。

```bash:起動
java -jar target/scala-2.11/diffy-server.jar -candidate="localhost:3002" -master.primary="localhost:3000" -master.secondary="localhost:3001" -service.protocol="http" -serviceName="My Service" -proxy.port=:3003 -admin.port=:3004 -http.port=:3005 -rootUrl='localhost:3005'
```

今回は旧環境2つを3000(上記のmaster.primary)、3001(同master.secondary)ポート、新環境を3002(同candidate)ポートで動かしています。他のポートは後述します。

なお、githubのREADMEに記載されているダブルクォートをエスケープするような書き方をするとエラーになってしまいました。
ブログ側は直っているのでgithub側もいずれ直ると思います。

### WebのUIにアクセスしてみる

まず、起動時にhttp.portで指定したURLにアクセスするとこんな感じです。
まだリクエストを送っていないので、何も表示されません。

![empty.png](https://qiita-image-store.s3.amazonaws.com/0/36003/5eeecd9b-2a6a-2a53-a798-7aec00dfd8c6.png)


### リクエストを送信してみる

起動時にproxy.portで指定したポートにリクエストを投げてみます。
ここに投げると新・旧それぞれの環境に同じリクエストを投げてくれるようです。

```bash:正常系と異常系それぞれのURLにリクエスト送信
$ curl http://localhost:3003/home/user1?q=hogehoge
curl: (52) Empty reply from server

$ curl http://localhost:3003/fail/user1?q=hogehoge
curl: (52) Empty reply from server
```

レスポンスが無いですが、これでOKです。
レスポンスが無いのは[こちら](https://github.com/twitter/diffy/issues/5)で話題に上がっているのでその内修正されるのではないでしょうか。

### 再びWebのUIにアクセスしてみる

こんな感じで結果が表示されています。
正常系のつもりのhomeと異常系のつもりのfailのいずれも失敗(100% Failing)になってしまっています。

![result1.png](https://qiita-image-store.s3.amazonaws.com/0/36003/781dad0a-c9ce-3836-4dec-da0400d0bad7.png)


レスポンスのtimestampが異なっているのも誤検知されてしまったようです。
が、Exclude Noiseというスイッチをオンにすると無事にhome側は成功(0% Failing)になったようです。

![result2.png](https://qiita-image-store.s3.amazonaws.com/0/36003/e2d82acd-e86d-3c34-d170-3b8bca586ae3.png)


続いてfail/側は66.7% Failingとなっています。
開いてみると想定通り、新環境で戻り値に余分につけたextraという値が判定されているのが分かります。

![result3.png](https://qiita-image-store.s3.amazonaws.com/0/36003/587a96cc-4182-5ddc-3749-553a100f7f49.png)

さらに右のペインを展開すると他にもContent Lengthとtimestampが違うと教えてくれます。

![result4.png](https://qiita-image-store.s3.amazonaws.com/0/36003/a9a80378-e351-3cf1-7232-19f6a51d1fdb.png)


さらにポップアップさせると詳細なビューを見ることができます。

![result5.png](https://qiita-image-store.s3.amazonaws.com/0/36003/db9d33a3-8680-4edf-890d-f83e00d81ed8.png)


### 管理画面にアクセスしてみる

管理画面もあるようです。統計情報等が見れるようです。
色々展開してみましたがぶっちゃけよく分かりませんでしたｗ

![admin.png](https://qiita-image-store.s3.amazonaws.com/0/36003/40ff3ead-5537-fa53-3d63-1dd0ca8bd75a.png)


## 最後に

まだまだ開発途上感は半端ないですが、APIサーバのレグレッションテストとかには大活躍しそうなツールだなぁと思いました。

一応、最後に今回使ったウンコードを載せておきます。
最初に書いた通り言語は何でも良いんですが、今回はnode.jsにしました。
追加でexpressだけ使ってます。

```js:uncode.js
var express = require('express');
var app = express();

app.get('/home/:userName', okHandler);
app.get('/fail/:userName', ngHandler);

function okHandler(req, res) {
  var userName = req.params['userName'];
  var query = req.query.q;
  // Content-Typeを"application/json"にすると記録されなかった。
  // X-Action-NameがないとWebの画面で全て同じカテゴリに入ってしまう(https://github.com/twitter/diffy/issues/6)
  res.writeHead(200, {"Content-Type": "text/html; charset=utf-8", "X-Action-Name": "home"});
  var resp = {
    url: '/home',
    name: userName,
    query: query,
    timestamp: Date.now()
  };
  res.end(JSON.stringify(resp) + "\n");
  return;
}

function ngHandler(req, res) {
  var userName = req.params['userName'];
  var query = req.query.q;
  res.writeHead(200, {"Content-Type": "text/html; charset=utf-8", "X-Action-Name": "fail"});
  var resp = {
    url: '/fail',
    name: userName,
    // 新環境の場合はqueryに'extra'という文字列をベタ書きで追加
    query: query,
    timestamp: Date.now()
  };
  res.end(JSON.stringify(resp) + "\n");
  return;
}
app.listen(3000);// primaryを3000、secondaryを3001、candidateを3002とした。
```

```bash:実行方法
# uncode.jsを保存したディレクトリで
npm init
npm install express --save
node uncode.js
```