2016-04-22 8 views
1

Ich beginne in der Unit Testing Welt in Perl. Hier ist meine Frage: Ich habe eine Klasse in Perl (ich benutze Moo BTW) und diese Klasse hat 3 Attribute (ich werde den Code unten setzen). Eines dieser Attribute ist ein Array und wird automatisch im Konstruktor generiert. Um das Array zu generieren, muss ich auf eine Datenbank zugreifen und eine Abfrage ausführen.perl Test :: MockModule mit DB-Zugang

Jetzt möchte ich Unit-Tests ausführen, um das Verhalten der vorherigen Klasse zu überprüfen. Bis jetzt bin ich mit Test :: MockModule als nächstes:

use diagnostics; # this gives you more debugging information 
use warnings; # this warns you of bad practices 
use strict;  # this prevents silly errors 
use Moo; 
use Data::Dumper; 
use Test::More tests => 2; # for the is() and isnt() functions 
use Customer; 
use FindBin qw/$RealBin/; 
use Test::Deep; 
use Test::MockModule; 
use DBI; 
my $dbh = DBI->connect("dbi:SQLite:dbname=$RealBin/test-cutomer.db","","") or die $DBI::errstr; 
$dbh->do(" 
CREATE TABLE IF NOT EXISTS table (
    id INTEGER PRIMARY KEY AUTOINCREMENT, 
    field1 INTEGER, 
    field2 INTEGER, 
    field3 TEXT, 
    field4 INTEGER, 
    field5 INTEGER 
) 
"); 

$dbh->do(' 
     INSERT INTO table (field1,field2,field3,field4,field5) VALUES 
     (?,?,?,?,?)',undef,undef,92,'[email protected]',1,1 
); 

END { 
    unlink "$RealBin/test-customer.db"; 
} 

{ 

my $mock = Test::MockModule->new("Customer"); 
$mock->mock("get_db_handler", sub { return $dbh }); 

my $customer=Customer->new(id=>92,type=>'other'); 

ok(defined $customer); 
my $e=$customer->emails; 
my @[email protected]$e; 
my [email protected]; 
is($length,1, 'the emails are OK'); 
} 

Ich möchte die get_db_handler Methode verspotten den Test-Customer.db Handler abzurufen und die Abfragen über diese lokale DB zu laufen. Bisher habe ich die folgende Fehlermeldung erhalten:

1..2 
ok 1 
Can't locate object method "execute" via package "DBI::db" at Customer.pm line 
    46 (#1) 
    (F) You called a method correctly, and it correctly indicated a package 
    functioning as a class, but that package doesn't define that particular 
    method, nor does any of its base classes. See perlobj. 

Uncaught exception from user code: 
    Can't locate object method "execute" via package "DBI::db" at Customer.pm line 46. 
at Customer.pm line 46 
    Customer::getEmails('Customer=HASH(0x11359c8)') called at Customer.pm line 23 
    Customer::__ANON__('Customer=HASH(0x11359c8)') called at (eval 23) line 18 
    Customer::emails('Customer=HASH(0x11359c8)') called at t/stc.t line 66 
# Looks like you planned 2 tests but ran 1. 
# Looks like your test exited with 2 just after 1. 

Das Skript läuft OK, ich meine, gibt es kein Problem mit dem Code. Das Problem ist mit dem Test. Könnten Sie sich das bitte ansehen? Ich werde es sehr schätzen. Danke im Voraus.

+0

Danke für die schnelle Antwort! Ich arbeite im Beispiel. Aber kannst du ein bisschen mehr über deinen ersten Kommentar erzählen? – ccalderon911217

+0

Wenn ich nicht wählen muss, beides! hahahaha Es wird großartig für mich sein, wenn du mir ein Beispiel für die Verwendung von Test: MockModule liefern kannst, aber auf diese Weise meine ich, ein Objekt zu verspotten, das im Konstruktor auf die DB zugreift. Vielen Dank!!! – ccalderon911217

+0

Sie können [codereview.se] posten, wenn Sie eine vollständige Analyse wünschen. Aber jetzt, wenn Sie wollen, dass der Kunde Sachen aus einer Datenbank bekommt, würde ich die '$ dbh' eine Eigenschaft mit' has' machen. Auf diese Weise können Sie einfach einen anderen '$ dbh' in Ihren Tests übergeben und müssen sich nicht um Spott kümmern. Wenn Sie mehrere Klassen haben, die eine dbh benötigen, verwandeln Sie sie in eine Moo :: Role.Aber achten Sie darauf, dass sqlite nicht alle Funktionen von Postgres unterstützt, es gibt also einige Fallstricke mit dem ganzen SQLite-Ansatz. Es ist wahrscheinlich sicherer, DBIx :: Class zu verwenden und sich darum zu kümmern. – simbabque

Antwort

2

Der Grund, warum Sie diesen Fehler erhalten, dass Ihr Produktionscode execute auf der Datenbank behandeln, und nicht auf einer Anweisung ruft behandeln. Du musst prepare die Abfrage vor dir können execute es.

my $sth = $db2->prepare($query); 
my $ref = $sth->execute($fmuser); 

Unter Verwendung herkömmliche Namen wie $dbh, $sth und $res für Ihre DBI Variablen hätten dazu beigetragen, dass leichter zu erkennen.


Test :: MockModule ist nicht das richtige Werkzeug für das, was Sie tun. Es ist nützlich, wenn Sie Abhängigkeiten in anderen Modulen oder möglicherweise nur Teilen davon mocksen möchten.

Aber im Moment haben Sie eine interne Abhängigkeit. Was Sie tun möchten, ist Abhängigkeit Injektion, aber Ihr Code ist nicht dafür vorbereitet, so müssen Sie einen anderen Weg finden.

Ich würde empfehlen, Sub::Override für diesen Job zu verwenden. Es ist ziemlich einfach. Es überschreibt ein Sub im lexikalischen Bereich. Das ist alles was du hier brauchst.

use Sub::Override; 

my $sub = Sub::Override->new('frobnicate' => sub { return 'foo' }); 

Also, wenn Sie das verwenden, würde Ihr Code etwa wie folgt aussehen. Beachten Sie, dass ich für den zweiten Testfall einige Zeilen aufgeräumt habe.

use strict; 
use warnings; 

use Test::More; 
use Sub::Override; # this line is new 

use DBI; 
use FindBin qw/$RealBin/; 

#               typo here 
my $dbh = DBI->connect("dbi:SQLite:dbname=$RealBin/test-customer.db", "", "") 
    or die $DBI::errstr; 
$dbh->do(<<'EOSQL'); 
CREATE TABLE IF NOT EXISTS table (
    id INTEGER PRIMARY KEY AUTOINCREMENT, 
    field1 INTEGER, 
    field2 INTEGER, 
    field3 TEXT, 
    field4 INTEGER, 
    field5 INTEGER 
) 
EOSQL 

$dbh->do(<<'EOSQL'); 
     INSERT INTO table (field1,field2,field3,field4,field5) VALUES 
     (?,?,?,?,?), undef, undef, 92, '[email protected]', 1, 1); 
EOSQL 

{ 
    # this makes $customer->get_db_handler temporarily return 
    # our $dbh from outside 
    my $sub = Sub::Override->new('Customer::get_db_handler' => sub { 
     return $dbh 
    }); 

    my $customer = Customer->new(id => 92, type => 'other'); 

    # this test-case is pretty useless   
    ok defined $customer, 'Customer is defined';  

    is scalar @{ $customer->emails }, 1, 
     '... and the emails were fetched form the DB'; 
} 

END { 
    unlink "$RealBin/test-customer.db"; 
} 
+0

Ich bekomme den gleichen Fehler wie am Anfang ... Objekt Methode "Ausführen" kann nicht über das Paket "DBI :: db" gefunden werden. – ccalderon911217

+0

Keine Sorgen! Vielen Dank! – ccalderon911217

+0

Wo soll ich es hinstellen? Ich bin mir ziemlich sicher, dass das der Fehler ist! Wow Mann, du bist großartig! – ccalderon911217