$shibayu36->blog;

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

Test::Factory::DBIというモジュールを作りました

 久々に思いついたモジュールを作ってみました。テスト用のデータを入れるために役に立つ(と思う)モジュールです。

Repository

https://github.com/shibayu36/p5-DBIx-DataFactoryに置いてあります。
またPrepanにも上げました。レビューお願いします。http://prepan.org/module/3Yz7PYrBzi

Synopsis

 大体下のような感じに使います。

# schema
CREATE TABLE test_factory (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `int` int,
  `string` varchar(255),
  `text` text DEFAULT NULL,

  PRIMARY KEY (id)
) DEFAULT CHARSET=binary;

# in your t/*.t
use Test::Factory::DBI;
Test::Factory::DBI->username('nobody');
Test::Factory::DBI->password('nobody');
Test::Factory::DBI->create_factory_method(
    method   => 'create_factory_data',
    dbi      => 'dbi:mysql:dbname=test_factory;host=localhost',
    table    => 'test_factory',
    columns => {
        int => {
            type => 'Int',
            size => 10,
        },
        string => {
            type => 'Str',
            size => 10,
        },
    },
);

my $values = create_factory_data(
    text => 'test text',
);

# will insert followed data
+----+------------+--------+------------+
| id | int        | double | string     |
+----+------------+--------+------------+
| 1  | 1336609917 |   NULL | ALdGTwmEl3 |
+----+------------+--------+------------+

Description

 テスト用にデータベースにデータを入れるとき、皆さんならどうしてますか?
 よく使われているのはFixtureだと思うのですが、これだとデータを追加したときに他のテストが通らなくなったり、結構複雑にテストをしたいときに、いちいちデータを作るのがめんどくさいです。なので僕は最近は使わなくなりました。
 
 そのかわり、例えばテスト用のデータを生成するようなメソッドを予め作っておいて、テストを書くときにはそのメソッドを使ってデータを入れてました。例えば下のような感じです。

sub create_user (%) {
    my (%args) = @_;
    require String::Random;
    require Sample::User; # 例えばこれがO/Rマッパーだとする。
    my $user = Sample::User->create(
        user_id    => $args{user_id} || int(rand(100000)),
        user_type  => $args{user_type} || 0,
    );

    if ($args{country}) {
        $user->country($args{country});
    }

    if ($args{lang}) {
        $user->lang($args{lang});
    }

    unless ($args{description}) {
        $args{description} = String::Random->new->randregex('[a-zA-Z0-9]{10}');
    }
    $user->description($args{description});

    return $user;
}

 上のようなメソッドを作るとテストの時も結構簡単にデータを作れるので、最近はこういうふうにしてました。ただ、これだとテーブルごとに毎回メソッドを作るのが大変だったり、columnを増やすたびに、毎回手を入れないといけなかったので、面倒になって来ました。

 そのため、このようなメソッドを作るのを簡単にするようなモジュールを作りました。上の例だと、下のように書けます。

use Test::Factory::DBI;
Test::Factory::DBI->username('nobody');
Test::Factory::DBI->password('nobody');
Test::Factory::DBI->create_factory_method(
    method   => 'create_user',
    dbi      => 'dbi:mysql:dbname=user_test;host=localhost',
    table    => 'user_test',
    columns => {
        user_id => {
            type => 'Int',
            size => 5,
        },
        user_type => sub { 0 },
        description => {
            type => 'Str',
            size => 10,
        },
    },
);

my $values = create_user(
   user_type => 2,
   country   => 'jp',
   lang      => 'ja',
);

Typeの拡張

 また、一応columnsで指定するTypeを拡張できるように作りました。例えば、「あるセットの中からどれかrandomで選んで挿入したい」みたいな要求があったときは、以下のようなクラスを作ります。

package Test::Factory::DBI::Type::Set;

use strict;
use warnings;
use Carp;

use base qw(Test::Factory::DBI::Type);

use Smart::Args;

sub type_name { 'Set' }

sub make_value {
    args my $class => 'ClassName',
         my $set   => 'ArrayRef';
    return $set->[int rand(scalar @$set)];
}

1;

 その後、Test::Factory::DBIにこのTypeを登録したら、使えます。

use Test::Factory::DBI;
Test::Factory::DBI->add_type('Test::Factory::DBI::Type::Set');

Test::Factory::DBI->username('nobody');
Test::Factory::DBI->password('nobody');
Test::Factory::DBI->create_factory_method(
    method   => 'create_user',
    dbi      => 'dbi:mysql:dbname=user_test;host=localhost',
    table    => 'user_test',
    columns => {
        user_id => {
            type => 'Int',
            size => 5,
        },
        user_type => sub { 0 },
        description => {
            type => 'Str',
            size => 10,
        },
        lang => {
            type => 'Set',
            set  => ['ja', 'en'],
        },
    },
);

my $values = create_user();

追記

Test::Factory::DBIっていう名前はやめたほうが良いということでDBIx::DataFactoryって名前にしようかなと考えています。repositoryも変わりました。
https://github.com/shibayu36/p5-DBIx-DataFactory

まとめ

 という訳で、こんな感じのモジュールを作って見ました。何か指摘があれば@shiba_yu36か、prepanにでも言ってもらえるとありがたいです。