本番に近いDBでテストしたい。日本語を含むデータをそれなりに正しくマスキングしたい。

自分はよく利用するので、自分用のメモです。
(すみません、ちょっと編集ミスって再投稿になります)

# 本番に近いテストデータを用意したい

- 本番でしか再現しない!
- 本番のデータだったら気づけたバグだった!
- ユーザ A さんでしか再現しないけど当然 A さんのパスワードを聞いてログインするわけにはいかない

このようなもどかしい思いをしたことはないでしょうか?
私は何度もあります。

本番と全く同じ環境、同じユーザ、同じデータで再現できればベストですが、個人情報を含んだり怖いデータも多いのでそのままでは使えないことが多いです。

本番のデータを取得してダミーデータにマスキングすればよいのですが、アプリケーションのロジック的に正しいデータを入れたいです。例えば名前とよみがななどです。
`a123` などにしてしまうといざ使おうとしたらアプリケーションのロジックのバリデーションエラーで使えませんでした、なんてことも。
また、全部 `山田一郎` だとテストデータとしてはよくありません。

MySQL の場合を例にして、こんな問題について考えてみます。

# 何がともあれ本番のデータをダンプして、テスト環境に入れる

1. `mysqldump` コマンドで本番 DB をダンプ
2. `mysql` コマンドでテスト環境にインポートする

うっかり本番 DB に対してインポートしてしまうとサービスや会社終了の危険性もあるので、最低限の権限のユーザで慎重にスクリプトを作成するのをおすすめします。

## 備考: 悩ましいポイント

この後マスキングを行うわけですが、悩ましいのは上記のタイミングではマスキング前の本番データがテスト環境で見えてしまうことになります。
深夜のバッチなら誰も見ていないでしょ、で許されるなら良しですが、それも許されないケースもあるでしょう。

会社によってはこの操作が許されない可能性もありますので、できればマスク済みのダンプデータがほしいところではありますが、なかなか難しいです…。

# マスキングする

今回はこのようなテーブルを例として考えます。

```sql
CREATE TABLE users(
  id BIGINT NOT NULL AUTO_INCREMENT,
  email VARCHAR(255) NOT NULL,
  password VARCHAR(255) NOT NULL,
  first_name VARCHAR(255) NOT NULL,
  last_name VARCHAR(255) NOT NULL,
  first_name_reading VARCHAR(255) NOT NULL,
  last_name_reading VARCHAR(255) NOT NULL,
  PRIMARY KEY(id)
);

-- データ例
INSERT INTO users(
  email,
  password,
  first_name,
  last_name,
  first_name_reading,
  last_name_reading
)
VALUES(
  "kaiba@example.com",
  "800f55f5c67d603e742cde5f4df9568d814d48f56869a7e5c1edcc189495d214",
  "太郎",
  "山田",
  "たろう"
  "やまだ",
);
```

以下のような感じでマスキングをしてみました。

```sql
UPDATE
  users
SET
  email = concat("test+", id, "@example.com"),
  password = "fixed_test_hash",
  last_name = ELT(id % 5 + 1, "海馬", "山田", "佐藤", "外村", "武者小路"),
  first_name = ELT(id % 5 + 1, "瀬戸", "太郎", "次郎", "ケビン", "いちご"),
  last_name_reading = ELT(id % 5 + 1, "かいば", "やまだ", "さとう", "そとむら", "むしゃのこうじ"),
  first_name_reading = ELT(id % 5 + 1, "せと", "たろう", "じろう", "けびん", "いちご");
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21799/eb32c21a-b570-2b91-4db9-d5343db598d7.png)

こんな感じになりました。

注目ポイントは以下です。

- PK である `id` を使うことで重複を避けています
- `ELT` 関数を利用して 5 つの文字列から指定番目(1〜)の文字列を取得しています。要素数を示すマジックナンバー `5` が気持ち悪いが…
- `ELT` 関数でも `id` を使用して名前とよみがなが一致するようにしています。今回はわかりやすさを優先しこのようにしましたが、これだと決まった順番で生成されてしまうので、`RAND(seed)` などを使ってもうひと工夫してもいいかもしれませんね。
- パスワードのハッシュはテスト環境で生成したものを固定で指定することで、テスト環境では好きなユーザでログインできるようになります(パスワードのハッシュのアルゴリズムによりますが…)。
  - `id: 100` のユーザでしか再現しない！
  - `test+100@example.com`, `my-password` でログインして再現を試みる

# スクリプト例

```bash
#!/bin/bash

# 変数を設定します。ベタ書きは危険なので管理方法をよく考えましょう。
# WARNING: 誤ってFROMとTOを逆にすると本番DBに書き込んでしまう可能性があります。最低限の権限のユーザで実施しましょう。
# export FROM_DB_HOST=production.db.example.com
# export FROM_DB_USER=dump_only_user
# export FROM_DB_PASSWORD=super_dangerous_production_password
# export FROM_DB=super_dangerous_production_db
# export TO_DB_HOST=127.0.0.1
# export TO_DB_USER=root
# export TO_DB_PASSWORD=password
# export TO_DB=kaiba_test2

echo "Start dump from $FROM_DB @ $FROM_DB_HOST"
mysqldump -u$FROM_DB_USER -p$FROM_DB_PASSWORD -h$FROM_DB_HOST $FROM_DB > dump.sql

echo "Start import to $TO_DB @ $TO_DB_HOST"
cat dump.sql | mysql -u$TO_DB_USER -p$TO_DB_PASSWORD -h$TO_DB_HOST $TO_DB

# mask
mysql -u$TO_DB_USER -p$TO_DB_PASSWORD -h$TO_DB_HOST $TO_DB -e 'update users set password = "予めstagingで生成したパスワードのハッシュ";'
mysql -u$TO_DB_USER -p$TO_DB_PASSWORD -h$TO_DB_HOST $TO_DB -e 'update users set email=concat("admin+", id, "@example.com");'
mysql -u$TO_DB_USER -p$TO_DB_PASSWORD -h$TO_DB_HOST $TO_DB -e 'update users set screen_name=concat("admin+", id);'
mysql -u$TO_DB_USER -p$TO_DB_PASSWORD -h$TO_DB_HOST $TO_DB -e 'update users set user_name=ELT(id % 10 + 1, "佐藤一郎", "山田次郎", "鈴木三郎", "加藤四郎", "山岡五郎", "毛利六郎", "岡田七郎", "ケビンハチチロー", "山田クロウ", "柳生十兵衛");'
```