2013-11-20 3 views
5

Nachdem ich den traurigen Zustand der Codeabdeckung bei unseren Komponententests bei der Arbeit erkannt habe, versuche ich ein Dienstprogramm zu erstellen, das unsere Codebasis scannt und Dateien ohne 100 markiert %. Ich fand zwei Ansätze, die alle Methoden erhalten:Liste der Methoden/Funktionen, die explizit in einem Modul definiert sind

Zugang Symboltabelle direkt:

for my $classname (@ARGV) { 
    eval "require $classname"; 
    die "Can't load $classname $EVAL_ERROR" 
     if $EVAL_ERROR; 

    no strict 'refs'; 
    METHODS: 
    for my $sym (keys %{ "${classname}::" }) { 
     next METHODS unless defined &{"${classname}::${sym}"}; 
     print "$sym\n"; 
    } 
} 

Verwenden Sie das Class::Inspector Modul aus dem CPAN:

for my $classname (@ARGV) { 
    my @methods = Class::Inspector->methods($classname, 'public'); 
    print Dumper \@methods; 
} 

diese beiden Ansätze produzieren ähnliche Ergebnisse; Das Problem mit diesen ist, dass sie alle der Methoden zeigen, die für das gesamte Modul verfügbar sind, nicht nur die Methoden innerhalb dieses Moduls.

Gibt es eine Möglichkeit, zwischen Methoden zu unterscheiden, die für ein Modul zugänglich sind, und Methoden, die explizit in einem Modul definiert sind?

Hinweis: Ich versuche nicht, einen vollständigen Code-Coverage-Test zu erstellen, für meinen Anwendungsfall möchte ich nur testen, ob alle Methoden mindestens einmal aufgerufen wurden. Vollständige Abdeckung Tests wie Devel::Cover sind für uns Overkill.

+0

ich das im Moment nicht verifizieren kann, aber ich glaube, Sie brauchen [ 'can'] (http://perldoc.perl.org/perlobj.html#The-UNIVERSAL- Class): 'print" $ _ \ n "für grep $ klassenname-> can ($ _), keys% {" $ {classname} ::} "' – Zaid

+0

Danke für deinen Kommentar, leider 'can' scheint immer noch schaue den Vererbungsbaum an. Ich bekomme immer noch Einträge für 'Dumper' und andere Methoden von einigen unserer benutzerdefinierten Module. –

+0

Sie könnten 'local @PACKAGE :: NAME :: INC =();' aufrufen und dann die 'can()' Prüfung durchführen. – frezik

Antwort

4

Jede Unter (oder genauer gesagt, jeder CV), erinnert sich, welches Paket es ursprünglich in deklariert wurde Testfall:.

Foo.pm:

package Foo; 
sub import { 
    *{caller . "::foo"} = sub{}; 
} 
1; 

Bar.pm:

package Bar; 
use Foo; 

our $bar; # introduces *Bar::bar which does not have a CODE slot 
sub baz {} 
1; 

Zugriff Die Symboltabelle gibt nun foo und baz. By the way, würde ich diesen Code wie folgt (aus Gründen, die in einem Moment klar werden wird) schreiben:

my $classname = 'Bar'; 
for my $glob (values %{ "${classname}::" }) { 
    my $sub = *$glob{CODE} or next; 
    say *$glob{NAME}; 
} 

Als nächstes müssen wir die B module Blick in die underlying C data structure introspect. Wir machen das mit der B::svref_2object Funktion. Dadurch wird ein B::CV Objekt erzeugen, das das bequeme STASH Feld hat (die ein B::HV Objekt zurückgibt, das ein NAME Feld hat):

use B(); 
my $classname = 'Bar'; 
for my $glob (values %{ "${classname}::" }) { 
    my $sub = *$glob{CODE} or next; 
    my $cv = B::svref_2object($sub); 
    $cv->STASH->NAME eq $classname or next; 
    say *$glob{NAME}; 
} 

einige Plausibilitätsprüfungen hinzufügen, und dies sollte ganz gut funktionieren.

Das Laden dynamischer Klassen/Module sollte nicht über die Zeichenfolge eval erfolgen. Stattdessen empfehle ich Module::Runtime:

Module::Runtime::require_module($classname); 
+0

ausgeführt habe. Dies ist genau die Art von Ansatz, die ich gesucht habe, danke! Einige der Aufrufe von 'NAME' sind Rückgabeobjekte von' B :: special', aus der Dokumentation ist nicht klar, was das bedeutet, aber ich werde sie einfach überspringen, da sie alle von oben nach oben im Vererbungsbaum erscheinen –

Verwandte Themen