Bugzilla – Attachment 32285 Details for
Bug 12603
TestBuilder - Module to simplify the writing of tests
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 12603: creating TestBuilder to simplify tests
Bug-12603-creating-TestBuilder-to-simplify-tests.patch (text/plain), 20.52 KB, created by
Marcel de Rooy
on 2014-10-13 11:37:57 UTC
(
hide
)
Description:
Bug 12603: creating TestBuilder to simplify tests
Filename:
MIME Type:
Creator:
Marcel de Rooy
Created:
2014-10-13 11:37:57 UTC
Size:
20.52 KB
patch
obsolete
>From ffb0e2397d2a55ea8e0516fee02e3970a42d2678 Mon Sep 17 00:00:00 2001 >From: Yohann Dufour <dufour.yohann@gmail.com> >Date: Fri, 18 Jul 2014 16:23:07 +0200 >Subject: [PATCH] Bug 12603: creating TestBuilder to simplify tests >Content-Type: text/plain; charset=utf-8 > >This patch contains a new module t::lib::TestBuilder which allows to write tests easier and it contains the unit tests of this module. >For more information, see the documentation of the module. > >This module uses the DBIx::Class schema and works with a clean DBIx::Class schema. In order to use it, you have to remove the current circular dependence (existing in the DBIx::Class) by applying the last patch of the bug 11518. > >Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com> > >Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl> >--- > t/db_dependent/TestBuilder.t | 249 ++++++++++++++++++++++++++ > t/lib/TestBuilder.pm | 397 ++++++++++++++++++++++++++++++++++++++++++ > 2 files changed, 646 insertions(+), 0 deletions(-) > create mode 100644 t/db_dependent/TestBuilder.t > create mode 100644 t/lib/TestBuilder.pm > >diff --git a/t/db_dependent/TestBuilder.t b/t/db_dependent/TestBuilder.t >new file mode 100644 >index 0000000..6110bea >--- /dev/null >+++ b/t/db_dependent/TestBuilder.t >@@ -0,0 +1,249 @@ >+#!/usr/bin/perl >+ >+# This file is part of Koha. >+# >+# Copyright 2014 - Biblibre SARL >+# >+# Koha is free software; you can redistribute it and/or modify it >+# under the terms of the GNU General Public License as published by >+# the Free Software Foundation; either version 3 of the License, or >+# (at your option) any later version. >+# >+# Koha is distributed in the hope that it will be useful, but >+# WITHOUT ANY WARRANTY; without even the implied warranty of >+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >+# GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License >+# along with Koha; if not, see <http://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+use Test::More tests => 40; >+ >+BEGIN { >+ use_ok('t::lib::TestBuilder'); >+} >+ >+ >+my $builder = t::lib::TestBuilder->new(); >+ >+is( $builder->build(), undef, 'build without arguments returns undef' ); >+ >+my @sources = $builder->schema->sources; >+my $nb_failure = 0; >+for my $source (@sources) { >+ eval { $builder->build( { source => $source } ); }; >+ $nb_failure++ if ($@); >+} >+is( $nb_failure, 0, 'TestBuilder can create a entry for every sources' ); >+ >+my $my_overduerules_transport_type = { >+ message_transport_type => { >+ message_transport_type => 'my msg_t_t', >+ }, >+ letternumber => 1, >+ branchcode => { >+ branchcode => 'codeB', >+ categorycode => 'codeC', >+ }, >+ categorycode => undef, >+}; >+$my_overduerules_transport_type->{categorycode} = $my_overduerules_transport_type->{branchcode}; >+my $overduerules_transport_type = $builder->build({ >+ source => 'OverduerulesTransportType', >+ value => $my_overduerules_transport_type, >+}); >+is( >+ $overduerules_transport_type->{message_transport_type}, >+ $my_overduerules_transport_type->{message_transport_type}->{message_transport_type}, >+ 'build stores the message_transport_type correctly' >+); >+is( >+ $overduerules_transport_type->{letternumber}, >+ $my_overduerules_transport_type->{letternumber}, >+ 'build stores the letternumber correctly' >+); >+is( >+ $overduerules_transport_type->{branchcode}, >+ $my_overduerules_transport_type->{branchcode}->{branchcode}, >+ 'build stores the branchcode correctly' >+); >+is( >+ $overduerules_transport_type->{categorycode}, >+ $my_overduerules_transport_type->{categorycode}->{categorycode}, >+ 'build stores the categorycode correctly' >+); >+is( >+ $overduerules_transport_type->{_fk}->{message_transport_type}->{message_transport_type}, >+ $my_overduerules_transport_type->{message_transport_type}->{message_transport_type}, >+ 'build stores the foreign key message_transport_type correctly' >+); >+is( >+ $overduerules_transport_type->{_fk}->{branchcode}->{branchcode}, >+ $my_overduerules_transport_type->{branchcode}->{branchcode}, >+ 'build stores the foreign key branchcode correctly' >+); >+is( >+ $overduerules_transport_type->{_fk}->{categorycode}->{categorycode}, >+ $my_overduerules_transport_type->{categorycode}->{categorycode}, >+ 'build stores the foreign key categorycode correctly' >+); >+is_deeply( >+ $overduerules_transport_type->{_fk}->{branchcode}, >+ $overduerules_transport_type->{_fk}->{categorycode}, >+ 'build links the branchcode and the categorycode correctly' >+); >+isnt( >+ $overduerules_transport_type->{_fk}->{branchcode}->{letter2}, >+ undef, >+ 'build generates values if they are not given' >+); >+ >+my $my_user_permission = $t::lib::TestBuilder::default_value->{UserPermission}; >+my $user_permission = $builder->build({ >+ source => 'UserPermission', >+}); >+isnt( >+ $user_permission->{borrowernumber}, >+ undef, >+ 'build generates a borrowernumber correctly' >+); >+is( >+ $user_permission->{module_bit}, >+ $my_user_permission->{module_bit}->{module_bit}->{bit}, >+ 'build stores the default value correctly' >+); >+is( >+ $user_permission->{code}, >+ $my_user_permission->{module_bit}->{code}, >+ 'build stores the default value correctly' >+); >+is( >+ $user_permission->{borrowernumber}, >+ $user_permission->{_fk}->{borrowernumber}->{borrowernumber}, >+ 'build links the foreign key correctly' >+); >+is( >+ $user_permission->{_fk}->{borrowernumber}->{surname}, >+ $my_user_permission->{borrowernumber}->{surname}, >+ 'build stores the foreign key value correctly' >+); >+is( >+ $user_permission->{_fk}->{borrowernumber}->{address}, >+ $my_user_permission->{borrowernumber}->{address}, >+ 'build stores the foreign key value correctly' >+); >+is( >+ $user_permission->{_fk}->{borrowernumber}->{city}, >+ $my_user_permission->{borrowernumber}->{city}, >+ 'build stores the foreign key value correctly' >+); >+is( >+ $user_permission->{_fk}->{borrowernumber}->{_fk}->{branchcode}->{branchcode}, >+ $my_user_permission->{borrowernumber}->{branchcode}->{branchcode}, >+ 'build stores the foreign key value correctly' >+); >+is( >+ $user_permission->{_fk}->{borrowernumber}->{_fk}->{branchcode}->{branchname}, >+ $my_user_permission->{borrowernumber}->{branchcode}->{branchname}, >+ 'build stores the foreign key value correctly' >+); >+is( >+ $user_permission->{_fk}->{borrowernumber}->{_fk}->{categorycode}->{categorycode}, >+ $my_user_permission->{borrowernumber}->{categorycode}->{categorycode}, >+ 'build stores the foreign key value correctly' >+); >+is( >+ $user_permission->{_fk}->{borrowernumber}->{_fk}->{categorycode}->{hidelostitems}, >+ $my_user_permission->{borrowernumber}->{categorycode}->{hidelostitems}, >+ 'build stores the foreign key value correctly' >+); >+is( >+ $user_permission->{_fk}->{borrowernumber}->{_fk}->{categorycode}->{category_type}, >+ $my_user_permission->{borrowernumber}->{categorycode}->{category_type}, >+ 'build stores the foreign key value correctly' >+); >+is( >+ $user_permission->{_fk}->{borrowernumber}->{_fk}->{categorycode}->{defaultprivacy}, >+ $my_user_permission->{borrowernumber}->{categorycode}->{defaultprivacy}, >+ 'build stores the foreign key value correctly' >+); >+is( >+ $user_permission->{_fk}->{borrowernumber}->{privacy}, >+ $my_user_permission->{borrowernumber}->{privacy}, >+ 'build stores the foreign key value correctly' >+); >+is( >+ $user_permission->{_fk}->{module_bit}->{_fk}->{module_bit}->{bit}, >+ $my_user_permission->{module_bit}->{module_bit}->{bit}, >+ 'build stores the foreign key value correctly' >+); >+is( >+ $user_permission->{_fk}->{module_bit}->{code}, >+ $my_user_permission->{module_bit}->{code}, >+ 'build stores the foreign key value correctly' >+); >+is_deeply( >+ $user_permission->{_fk}->{module_bit}, >+ $user_permission->{_fk}->{code}, >+ 'build links the codes correctly' >+); >+isnt( >+ $user_permission->{_fk}->{borrowernumber}->{cardnumber}, >+ undef, >+ 'build generates values if they are not given' >+); >+isnt( >+ $user_permission->{_fk}->{borrowernumber}->{_fk}->{branchcode}->{branchaddress1}, >+ undef, >+ 'build generates values if they are not given' >+); >+isnt( >+ $user_permission->{_fk}->{borrowernumber}->{_fk}->{categorycode}->{description}, >+ undef, >+ 'build generates values if they are not given' >+); >+isnt( >+ $user_permission->{_fk}->{module_bit}->{description}, >+ undef, >+ 'build generates values if they are not given' >+); >+isnt( >+ $user_permission->{_fk}->{module_bit}->{_fk}->{module_bit}->{flag}, >+ undef, >+ 'build generates values if they are not given' >+); >+ >+ >+my $nb_basket = $builder->schema->resultset('Aqbasket')->search(); >+isnt( $nb_basket, 0, 'add stores the generated entries correctly' ); >+$builder->clear( { source => 'Aqbasket' } ); >+$nb_basket = $builder->schema->resultset('Aqbasket')->search(); >+is( $nb_basket, 0, 'clear removes all the entries correctly' ); >+ >+ >+my $rs_aqbookseller = $builder->schema->resultset('Aqbookseller'); >+my $bookseller = $builder->build({ >+ source => 'Aqbookseller', >+ only_fk => 1, >+}); >+delete $bookseller->{_fk}; >+my $bookseller_from_db = $rs_aqbookseller->find($bookseller); >+is( $bookseller_from_db, undef, 'build with only_fk = 1 does not store the entry' ); >+my $bookseller_result = $rs_aqbookseller->create($bookseller); >+is( $bookseller_result->in_storage, 1, 'build with only_fk = 1 creates the foreign keys correctly' ); >+ >+$bookseller = $builder->build({ >+ source => 'Aqbookseller', >+}); >+delete $bookseller->{_fk}; >+$bookseller_from_db = $rs_aqbookseller->find($bookseller); >+is( $bookseller_from_db->in_storage, 1, 'build without the parameter only_sk stores the entry correctly' ); >+ >+$bookseller = $builder->build({ >+ source => 'Aqbookseller', >+ only_fk => 0, >+}); >+delete $bookseller->{_fk}; >+$bookseller_from_db = $rs_aqbookseller->find($bookseller); >+is( $bookseller_from_db->in_storage, 1, 'build with only_fk = 0 stores the entry correctly' ); >diff --git a/t/lib/TestBuilder.pm b/t/lib/TestBuilder.pm >new file mode 100644 >index 0000000..bb3ab74 >--- /dev/null >+++ b/t/lib/TestBuilder.pm >@@ -0,0 +1,397 @@ >+package t::lib::TestBuilder; >+ >+use Modern::Perl; >+use Koha::Database; >+use String::Random; >+ >+ >+my $gen_type = { >+ tinyint => \&_gen_int, >+ smallint => \&_gen_int, >+ mediumint => \&_gen_int, >+ integer => \&_gen_int, >+ bigint => \&_gen_int, >+ >+ float => \&_gen_real, >+ decimal => \&_gen_real, >+ double_precision => \&_gen_real, >+ >+ timestamp => \&_gen_date, >+ datetime => \&_gen_date, >+ date => \&_gen_date, >+ >+ char => \&_gen_text, >+ varchar => \&_gen_text, >+ tinytext => \&_gen_text, >+ text => \&_gen_text, >+ mediumtext => \&_gen_text, >+ longtext => \&_gen_text, >+ >+ set => \&_gen_set_enum, >+ enum => \&_gen_set_enum, >+ >+ tinyblob => \&_gen_blob, >+ mediumblob => \&_gen_blob, >+ blob => \&_gen_blob, >+ longblob => \&_gen_blob, >+}; >+ >+our $default_value = { >+ UserPermission => { >+ borrowernumber => { >+ surname => 'my surname', >+ address => 'my adress', >+ city => 'my city', >+ branchcode => { >+ branchcode => 'cB', >+ branchname => 'my branchname', >+ }, >+ categorycode => { >+ categorycode => 'cC', >+ hidelostitems => 0, >+ category_type => 'A', >+ default_privacy => 'default', >+ }, >+ privacy => 1, >+ }, >+ module_bit => { >+ module_bit => { >+ bit => '10', >+ }, >+ code => 'my code', >+ }, >+ code => undef, >+ }, >+}; >+$default_value->{UserPermission}->{code} = $default_value->{UserPermission}->{module_bit}; >+ >+ >+sub new { >+ my ($class) = @_; >+ my $self = {}; >+ bless( $self, $class ); >+ >+ $self->schema( Koha::Database->new()->schema ); >+ $self->schema->txn_begin(); >+ $self->schema->storage->sql_maker->quote_char('`'); >+ return $self; >+} >+ >+sub schema { >+ my ($self, $schema) = @_; >+ >+ if( defined( $schema ) ) { >+ $self->{schema} = $schema; >+ } >+ return $self->{schema}; >+} >+ >+sub clear { >+ my ($self, $params) = @_; >+ my $source = $self->schema->resultset( $params->{source} ); >+ return $source->delete_all(); >+} >+ >+sub build { >+ my ($self, $params) = @_; >+ my $source = $params->{source} || return; >+ my $value = $params->{value}; >+ my $only_fk = $params->{only_fk} || 0; >+ >+ my $col_values = $self->_buildColumnValues({ >+ source => $source, >+ value => $value, >+ }); >+ >+ my $data; >+ my $foreign_keys = $self->_getForeignKeys( { source => $source } ); >+ for my $fk ( @$foreign_keys ) { >+ my $fk_value; >+ my $col_name = $fk->{keys}->[0]->{col_name}; >+ if( ref( $col_values->{$col_name} ) eq 'HASH' ) { >+ $fk_value = $col_values->{$col_name}; >+ } >+ elsif( defined( $col_values->{$col_name} ) ) { >+ next; >+ } >+ >+ my $fk_row = $self->build({ >+ source => $fk->{source}, >+ value => $fk_value, >+ }); >+ >+ my $keys = $fk->{keys}; >+ for my $key( @$keys ) { >+ $col_values->{ $key->{col_name} } = $fk_row->{ $key->{col_fk_name} }; >+ $data->{ $key->{col_name} } = $fk_row; >+ } >+ } >+ >+ my $new_row; >+ if( $only_fk ) { >+ $new_row = $col_values; >+ } >+ else { >+ $new_row = $self->_storeColumnValues({ >+ source => $source, >+ values => $col_values, >+ }); >+ } >+ $new_row->{_fk} = $data if( defined( $data ) ); >+ return $new_row; >+} >+ >+sub _formatSource { >+ my ($params) = @_; >+ my $source = $params->{source}; >+ $source =~ s|(\w+)$|$1|; >+ return $source; >+} >+ >+sub _buildColumnValues { >+ my ($self, $params) = @_; >+ my $source = _formatSource( { source => $params->{source} } ); >+ my $value = $params->{value}; >+ >+ my $col_values; >+ my @columns = $self->schema->source($source)->columns; >+ for my $col_name( @columns ) { >+ my $col_value = $self->_buildColumnValue({ >+ source => $source, >+ column_name => $col_name, >+ value => $value, >+ }); >+ $col_values->{$col_name} = $col_value if( defined( $col_value ) ); >+ } >+ return $col_values; >+} >+ >+# Returns [ { >+# rel_name => $rel_name, >+# source => $table_name, >+# keys => [ { >+# col_name => $col_name, >+# col_fk_name => $col_fk_name, >+# }, ... ] >+# }, ... ] >+sub _getForeignKeys { >+ my ($self, $params) = @_; >+ my $source = $self->schema->source( $params->{source} ); >+ >+ my @foreign_keys = (); >+ my @relationships = $source->relationships; >+ for my $rel_name( @relationships ) { >+ my $rel_info = $source->relationship_info($rel_name); >+ if( $rel_info->{attrs}->{is_foreign_key_constraint} ) { >+ my $rel = { >+ rel_name => $rel_name, >+ source => $rel_info->{source}, >+ }; >+ >+ my @keys = (); >+ while( my ($col_fk_name, $col_name) = each($rel_info->{cond}) ) { >+ $col_name =~ s|self.(\w+)|$1|; >+ $col_fk_name =~ s|foreign.(\w+)|$1|; >+ push @keys, { >+ col_name => $col_name, >+ col_fk_name => $col_fk_name, >+ }; >+ } >+ $rel->{keys} = \@keys; >+ >+ push @foreign_keys, $rel; >+ } >+ } >+ return \@foreign_keys; >+} >+ >+sub _storeColumnValues { >+ my ($self, $params) = @_; >+ my $source = $params->{source}; >+ my $col_values = $params->{values}; >+ >+ my $new_row; >+ eval { >+ $new_row = $self->schema->resultset($source)->update_or_create($col_values); >+ }; >+ die "$source - $@\n" if ($@); >+ >+ eval { >+ $new_row = { $new_row->get_columns }; >+ }; >+ warn "$source - $@\n" if ($@); >+ return $new_row; >+} >+ >+sub _buildColumnValue { >+ my ($self, $params) = @_; >+ my $source = $params->{source}; >+ my $value = $params->{value}; >+ my $col_name = $params->{column_name}; >+ my $col_info = $self->schema->source($source)->column_info($col_name); >+ >+ my $col_value; >+ if( exists( $value->{$col_name} ) ) { >+ $col_value = $value->{$col_name}; >+ } >+ elsif( exists( $default_value->{$source}->{$col_name} ) ) { >+ $col_value = $default_value->{$source}->{$col_name}; >+ } >+ elsif( not $col_info->{default_value} and not $col_info->{is_auto_increment} and not $col_info->{is_foreign_key} ) { >+ eval { >+ my $data_type = $col_info->{data_type}; >+ $data_type =~ s| |_|; >+ $col_value = $gen_type->{$data_type}->( $self, { info => $col_info } ); >+ }; >+ die "The type $col_info->{data_type} is not defined\n" if ($@); >+ } >+ return $col_value; >+} >+ >+ >+sub _gen_int { >+ my ($self, $params) = @_; >+ my $data_type = $params->{info}->{data_type}; >+ >+ my $max = 1; >+ if( $data_type eq 'tinyint' ) { >+ $max = 127; >+ } >+ elsif( $data_type eq 'smallint' ) { >+ $max = 32767; >+ } >+ elsif( $data_type eq 'mediumint' ) { >+ $max = 8388607; >+ } >+ elsif( $data_type eq 'integer' ) { >+ $max = 2147483647; >+ } >+ elsif( $data_type eq 'bigint' ) { >+ $max = 9223372036854775807; >+ } >+ return int( rand($max+1) ); >+} >+ >+sub _gen_real { >+ my ($self, $params) = @_; >+ my $max = 10 ** 38; >+ if( defined( $params->{info}->{size} ) ) { >+ $max = 10 ** ($params->{info}->{size}->[0] - $params->{info}->{size}->[1]); >+ } >+ return rand($max) + 1; >+} >+ >+sub _gen_date { >+ my ($self, $params) = @_; >+ return $self->schema->storage->datetime_parser->format_datetime(DateTime->now()); >+} >+ >+sub _gen_text { >+ my ($self, $params) = @_; >+ my $random = String::Random->new( max => $params->{info}->{size} ); >+ return $random->randregex('[A-Za-z]+[A-Za-z0-9_]*'); >+} >+ >+sub _gen_set_enum { >+ my ($self, $params) = @_; >+ return $params->{info}->{extra}->{list}->[0]; >+} >+ >+sub _gen_blob { >+ my ($self, $params) = @_;; >+ return 'b'; >+} >+ >+ >+sub DESTROY { >+ my $self = shift; >+ $self->schema->txn_rollback(); >+} >+ >+ >+=head1 NAME >+ >+t::lib::TestBuilder.pm - Koha module to simplify the writing of tests >+ >+=head1 SYNOPSIS >+ >+ use t::lib::TestBuilder; >+ >+Koha module to insert the foreign keys automatically for the tests >+ >+=head1 DESCRIPTION >+ >+This module allows to insert automatically an entry in the database. All the database changes are wrapped in a transaction. >+The foreign keys are created according to the DBIx::Class schema. >+The taken values are the values by default if it is possible or randomly generated. >+ >+=head1 FUNCTIONS >+ >+=head2 new >+ >+ $builder = t::lib::TestBuilder->new() >+ >+Constructor - Begins a transaction and returns the object TestBuilder >+ >+=head2 schema >+ >+ $schema = $builder->schema >+ >+Getter - Returns the schema of DBIx::Class >+ >+=head2 clear >+ >+ $builder->clear({ source => $source_name }) >+ >+=over >+ >+=item C<$source_name> is the name of the source in the DBIx::Class schema (required) >+ >+=back >+ >+Clears all the data of this source (database table) >+ >+=head2 build >+ >+ $builder->build({ >+ source => $source_name, >+ value => $value, >+ only_fk => $only_fk, >+ }) >+ >+=over >+ >+=item C<$source_name> is the name of the source in the DBIx::Class schema (required) >+ >+=item C<$value> is the values for the entry (optional) >+ >+=item C<$only_fk> is a boolean to indicate if only the foreign keys are created (optional) >+ >+=back >+ >+Inserts an entry in the database by instanciating all the foreign keys. >+The values can be specified, the values which are not given are default values if they exists or generated randomly. >+Returns the values of the entry as a hashref with an extra key : _fk which contains all the values of the generated foreign keys. >+ >+=head1 AUTHOR >+ >+Yohann Dufour <yohann.dufour@biblibre.com> >+ >+=head1 COPYRIGHT >+ >+Copyright 2014 - Biblibre SARL >+ >+=head1 LICENSE >+ >+This file is part of Koha. >+ >+Koha is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by >+the Free Software Foundation; either version 3 of the License, or (at your option) any later version. >+ >+Koha is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. >+ >+You should have received a copy of the GNU General Public License along with Koha; if not, see <http://www.gnu.org/licenses>. >+ >+=cut >+ >+1; >-- >1.7.7.6
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 12603
:
29845
|
30086
|
30087
| 32285 |
32286