Blog::kobaken

prove t/foo/bar/baz.t

設定とFixtureのテスト

この記事は、モバイルファクトリー Advent Calendar 2015 2日目の記事です

昨日は、nekobato さんの superagentとaxiosの使い分け でした  

Perl Advent Calender 2014 の最高の記事の ソースコード以外もとにかくテストする。もしくはカバレッジだけではダメだという話 から、 アプリケーション設定ファイルのテストFixtureのテスト の例を書いてみたいと思います

アプリケーション設定ファイルのテスト

本番環境、開発環境、テスト環境で設定が過不足がないかチェックするため、 Test::Deep, Test::Deep::Matcher を利用しています

テストの流れとしては

  1. 本番環境の設定値を、Test::Deep::Matcher のmatcher に変換
  2. 開発環境、テスト環境の設定値が、matcher にあうか比較

です

例えば、以下のような本番設定があったとして..

# production.pl
{
  servers => [
    'XX.XX.XXX.XXX:11211',
    'XX.XX.XXX.YYY:11211',
  ],
  options => {
    utf8 => 1,
  },
}

再帰的にmatcherに変換してあげます

use Test::Deep::Matcher;
use Test::Deep qw//;

sub convert_matcher {
    my $v = shift;

    if (!ref $v) {
        return Data::Util::is_integer($v) ? is_integer
             : Data::Util::is_number($v)  ? is_number
             : Data::Util::is_string($v)  ? is_string
             : Data::Util::is_value($v)   ? is_value
             : undef;
    }
    elsif(ref $v eq 'ARRAY') {
        return Test::Deep::array_each(convert_matcher($v->[0])) # XXX: 仮定
    }
    elsif(rev $v eq 'HASH') {
        return +{ map { $_ => convert_matcher($v->{$_}) } keys %$v }
    }
}

あとは比較するだけです

my $expect = convert_matcher($production_conf)
cmp_deeply $development_conf, $expect;

(余談)

  • 上の例では、本番環境の設定から(雑な)型推定をしました
  • 設定の型定義を用意しておけるなら、その方が良いかもしれません
    • 異常値などの定義をしやすいですし

以下、crystal の例です

require "json"

class Config
  JSON.mapping({
    servers: String,
    options: { type: ServerOptions },
  })
end

class ServerOptions
  JSON.mapping({
    utf8: Int32
  })
end

Fixture のテスト

リレーションチェックのため、 DBIx::Schema::DSL, SQL::Translator::Schema を利用しています

チェックには、外部キー制約を利用します
実際のアプリケーションでの利用は、諸事情 でしていないですが、 テストでの利用では最高に便利です
(この辺の外部キーありなしのスイッチを、no_fk_output/output だけで簡単にできるのが、DBIx::Schema::DSL めっちゃ便利だーってなって便利です)

流れは、

  • 外部キー制約つきのスキーマを用意して、INSERT する

だけです。 ただ、エラーメッセージを、人間が読みやすい形にするため、地味にループを回すようなコードにしています:p

# Schema
use 5.014002;
package MyProj::DB::Schema {
    use DBIx::Schema::DSL;

    create_table 'module' => columns {
        integer 'id', primary_key, auto_increment;
        varchar 'name';
        integer 'author_id';

        add_index 'author_id_idx' => ['author_id'];

        belongs_to 'author';
    };

    create_table 'author' => columns {
        integer 'id', primary_key, auto_increment;
        varchar 'name', unique;
    };
}
# belongs_to.t

$db->execute(MyProj::DB::Schema->output);

my $schema         = MyProj::DB::Schema->context->schema;
my $module_schema  = $schema->get_table('module');
my $constraints    = $module_schema->fkey_constraints;

for my $fkey (@$constraints) {
    my $ref_table = $fkey->reference_table;

    my @errors = try_load($ref_table);
    ok !@errors
       or note explain @errors;
}

sub try_load {
    my ($table) = @_;

    my @errors;
    for my $row (@{$FIXTURE_DATA{$table}}) {
        try {
            $db->insert($table => $row);
        }
        catch {
            push @errors => shift;
        };
    }
    return @errors;
}

まとめ

  • アプリケーション設定ファイルのテストFixtureのテスト の実装例を紹介しました

明日は、 nekobato さんです!