読者です 読者をやめる 読者になる 読者になる

$shibayu36->blog;

株式会社はてなでエンジニアをしています。プログラミングや読書のことなどについて書いています。

設定の仕様をドキュメントに書くのではなく、テストにしてしまう

 以前開発のドキュメントをどこに置くか問題 - $shibayu36->blog;という記事を書いた。まだよい方法はちゃんと考えられてないが、少しずつケースバイケースでいろいろな手法を試してみている。今回は設定項目の仕様のドキュメントという観点で考えたときに、テストを作ることで解決できないか、ということについて書く。

設定項目の仕様

 例えば以下の様な設定があったとする*1

[
   {
      "blog_url" : "http://shibayu36.hatenablog.com/",
      "permission" : "public",
      "can_be_edited_by" : [
         "shiba_yu36"
      ]
   },
   {
      "blog_url" : "http://shibayu36-private.hatenablog.com/",
      "permission" : "private",
      "can_be_edited_by" : [
         "shiba_yu36",
         "shiba_yu37"
      ]
   },
   {
      "blog_url" : "http://blog.example.com/",
      "permission" : "public",
      "can_be_edited_by" : [
         "example-user"
      ]
   }
]

 
 設定項目それぞれの意味は以下のとおり。

  • blog_url: 設定したいブログのURL
  • permission: publicなら公開、privateなら非公開
  • can_be_edited_by: 編集権限を持つユーザ

 この設定項目は特定のブログに対する情報を設定しておけるものとする。この時仕様をどのような形で引き継ぎ可能にしておくかという問題がある。

ドキュメントを書く?

 一つの方法としてドキュメントを書くという方法がある。これはよく使われるやり方だと思う。例えば今回の設定項目に関しては以下のように書いたりする。

今回のドキュメントはブログの情報の設定項目に対するものである。ブログに設定を追加する時はblogs.jsonに記入すること。

それぞれの設定項目は
- blog_url: 設定したいブログのURL
- permission: publicなら公開、privateなら非公開
- can_be_edited_by: 編集権限を持つユーザ
を表している。

また指定できる内容の仕様は以下のとおりである。
- 全体は配列になっている
- blog_urlにはURLを記述すること
  - ただし設定全体で同じblog_urlが二度出てきてはならない
- permissionにはpublicか、privateのどちらかを記述すること
- can_be_edited_byはユーザ名を配列で記述すること

 以上のドキュメントでこの設定に関する仕様は全て説明されている。ただし、以下の様な問題がある。

  • ドキュメントを読んで設定するのは人なので間違える可能性がある
  • そもそもこのドキュメントが見られない可能性がある
  • 仕様が増えていくとどんどんドキュメントが難しくなる

テストで仕様を説明する

 前述のとおりの問題があるので、ではこの問題をテストで解決出来ないかと考えた。

 それぞれの設定項目の説明は日本語のコメントで分かりやすく書いた上で、それが正しいかどうかを検証するテストを書いてみる。

use Test::More;
use Test::Deep;
use JSON::XS;
use Path::Class;
use URI;
use List::UtilsBy qw(uniq_by);

# それぞれの設定項目
# - blog_url: 設定したいブログのURL
# - permission: publicなら公開、privateなら非公開
# - can_be_edited_by: 編集権限を持つユーザ

my $blogs_config = decode_json(file('blogs.json')->slurp);

subtest 'それぞれの設定項目が正しい' => sub {
    for my $config (@$blogs_config) {
        cmp_deeply $config, {
            blog_url         => ignore(),
            permission       => ignore(),
            can_be_edited_by => ignore(),
        }, '全ての必須項目が揃っている';

        my $blog_url = URI->new($config->{blog_url});
        is $blog_url->scheme, 'http', "$blog_url はhttpのURLである";

        cmp_deeply $config->{permission}, any('public', 'private'), 'permissionはpublicかprivateのどちらかである';

        cmp_deeply $config->{can_be_edited_by}, array_each(re('[a-zA-Z0-9-]+')), 'can_be_edited_byは文字列の配列である';
    }
};

subtest '同じblog_urlが全体で二つあってはならない' => sub {
    # blog_urlでユニークな配列を作り直す
    # blog_urlが全てユニークであればもともとの個数と同じになるはず
    my $blogs_config_uniq_by_blog_url = [ uniq_by { $_->{blog_url} } @$blogs_config ];

    # 個数をチェック
    is scalar @$blogs_config_uniq_by_blog_url, scalar @$blogs_config, "blog_urlが全体でユニークである";
};

done_testing();

 このテストはドキュメントに書こうとしていた以下の仕様を正しくテスト出来ていて、

- 全体は配列になっている
- blog_urlにはURLを記述すること
  - ただし設定全体で同じblog_urlが二度出てきてはならない
- permissionにはpublicか、privateのどちらかを記述すること
- can_be_edited_byはユーザ名を配列で記述すること

さらに

  • 人が間違えてもテストが落ちる
  • テストが落ちるので、ドキュメントが見られなくても気づく
  • 仕様が増えてきた時もテストを増やしていけば、誰かが正しいかチェックする必要が省ける

というようにドキュメントの問題も解決している。

 なのでこういう項目はドキュメントを作るより、テストを書くほうがよいと思った。

まとめ

 上に書いたように今回は設定の仕様をドキュメントに書くという話と、そうではなくてテストにしたほうが良いかもしれないということについて書いた。実際にはケースバイケースでこの方向では解決できないものも非常にたくさんあるので、何か考えたことがあったらまた書いていこうと思う。

*1:解説用にでっち上げた設定項目なので、こういうのはデータベースに入れるべきとかはなしで