2013-11-20 10 views
5

Po realizacji smutny stan pokrycia kodu na naszych testów jednostkowych w pracy staram się stworzyć narzędzie, które skanuje Nasza baza kodu i flagi plików, które nie są na 100 %. Znalazłem dwa podejścia, które popadły wszystkich metod:lista metod/funkcji określonych wyraźnie w module Get

stół symbol dostęp bezpośrednio:

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"; 
    } 
} 

pomocą modułu Class::Inspector z CPAN:

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

te dwa podejścia produkować podobne rezultaty; Problem z nich jest to, że wykazują one wszystkie metod dostępnych dla całego modułu, nie tylko metody zdefiniowane wewnątrz tego modułu.

Czy istnieje jakiś sposób, aby odróżnić metod dostępnych dla modułu i metody zdefiniowane jawnie wewnątrz modułu?

Uwaga: Ja nie próbuje utworzyć pełny test pokrycia kodu do mojego użytku przypadku Chcę po prostu sprawdzić, czy wszystkie metody zostały nazwane co najmniej raz. Pełne testy zasięgu, takie jak Devel::Cover, są dla nas przesadą.

+0

nie mogę zweryfikować to w tej chwili, ale myślę, że trzeba [ 'can'] (http://perldoc.perl.org/perlobj.html#The-UNIVERSAL- klasa): 'print "$ _ \ n" dla grep $ classname-> może ($ _), klucze% { "$ {} classname ::}"' – Zaid

+0

Dzięki za komentarz, niestety 'can' wydaje się nadal spójrz na drzewo dziedziczenia. Nadal dostaję wpisy dla 'Dumper' i innych metod z niektórych naszych niestandardowych modułów. –

+0

Można nazwać 'local @ PACKAGE :: NAME :: INC =();', a następnie wykonać sprawdzanie 'can()'. – frezik

Odpowiedz

4

Każdy sub (lub dokładniej, każde CV), pamięta, który pakiet został pierwotnie zadeklarowane w przypadek testowy.

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; 

Dostęp tabela symboli daje teraz zarówno foo, jak i. Nawiasem mówiąc, chciałbym napisać, że kod tak (z powodów, które zostaną wyjaśnione za chwilę):

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

Następnie musimy patrzeć w B module do introspekcji się underlying C data structure. Robimy to za pomocą funkcji B::svref_2object. Pozwoli to stworzyć B::CV przedmiot, który ma dogodną STASH pole (która zwraca B::HV obiekt, który ma pole NAME):

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}; 
} 

dodać kilka testów poprawności, a to powinno działać całkiem dobrze.

dynamiczna klasa/moduł ładowania nie powinna odbywać się poprzez ciąg eval. Zamiast tego polecam Module::Runtime:

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

To jest dokładnie ten typ podejścia, którego szukałem, dzięki! Niektóre z wywołań do "NAME" są zwracanymi obiektami 'B :: special', z dokumentacji nie jest jasne, co to oznacza, ale po prostu pomijam je, ponieważ wszystkie wydają się pochodzić z drzewa dziedziczenia wyżej –

Powiązane problemy