Perl build, unit testing, code coverage: ตัวอย่างการทำงานที่สมบูรณ์


86

คำตอบ Stackoverflow ส่วนใหญ่ที่ฉันพบเกี่ยวกับกระบวนการสร้าง Perl และการทดสอบหน่วยและการครอบคลุมโค้ดเพียงแค่ชี้ให้ฉันไปที่ CPAN เพื่อดูเอกสารที่นั่น ไม่มีอะไรผิดปกติในการชี้ไปที่โมดูล CPAN เพราะนั่นคือที่ที่เอกสารฉบับเต็มควรจะอยู่ ฉันมีปัญหาในการค้นหาตัวอย่างโค้ดการทำงานที่สมบูรณ์ในหลาย ๆ กรณี

ฉันได้ค้นหาทั่วอินเทอร์เน็ตเพื่อหาตัวอย่างโค้ดที่ใช้งานได้จริงซึ่งฉันสามารถดาวน์โหลดหรือวางลงใน IDE ของฉันได้เช่นซอร์สโค้ดตัวอย่าง "Hello World" ของบทช่วยสอนทั่วไปของคุณ แต่เป็นตัวอย่างที่แสดงให้เห็นถึงกระบวนการสร้างด้วยการทดสอบหน่วยและโค้ด การวิเคราะห์ความครอบคลุม ใครมีตัวอย่างเล็ก ๆ ของโครงการทำงานที่แสดงให้เห็นถึงเทคโนโลยีและกระบวนการเหล่านี้หรือไม่?

(ฉันมีตัวอย่างการทำงานเล็กน้อยและฉันจะตอบคำถามของตัวเองด้วย แต่อาจมีผู้ใช้ SO รายอื่นที่มีตัวอย่างที่ดีกว่าตัวอย่างที่ฉันคิดขึ้นมา)

คำตอบ:


105

ฉันใช้เวลาสักพักและยังต้องใช้ตัวอย่างเล็ก ๆ น้อย ๆ จากแหล่งที่มาที่แตกต่างกันจำนวนมากและหลอมรวมเข้าด้วยกัน แต่ฉันคิดว่าฉันมีตัวอย่างการทำงานเล็ก ๆ ที่แสดงให้เห็นเพียงพอสำหรับมือใหม่ Perl กระบวนการสร้าง Perl รวมถึงการทดสอบหน่วยและการครอบคลุมโค้ด การวิเคราะห์และการรายงาน (ฉันใช้ActiveState ActivePerl v5.10.0 บนพีซี Windows XP Pro, Module :: Build , Test :: More , Devel :: Cover )

เริ่มต้นด้วยไดเร็กทอรีสำหรับโปรเจ็กต์ Perl ของคุณจากนั้นสร้างไดเร็กทอรี "lib" และไดเร็กทอรี "t" ภายใต้ไดเร็กทอรีโปรเจ็กต์ของคุณ:

HelloPerlBuildWorld
        |
        |----------> lib
        |
        |----------> t

ในไดเร็กทอรี "lib" ให้สร้างไฟล์ข้อความชื่อ "HelloPerlBuildWorld.pm" ไฟล์นี้เป็นโมดูล Perl ของคุณที่คุณจะสร้างและทดสอบ วางเนื้อหาต่อไปนี้ลงในไฟล์นี้:

use strict;
use warnings;
package HelloPerlBuildWorld;

$HelloPerlBuildWorld::VERSION = '0.1';

sub hello {
   return "Hello, Perl Build World!";
}

sub bye {
   return "Goodbye, cruel world!";
}

sub repeat {
   return 1;
}

sub argumentTest {
    my ($booleanArg) = @_;

    if (!defined($booleanArg)) {
        return "null";
    }
    elsif ($booleanArg eq "false") {
        return "false";
    }
    elsif ($booleanArg eq "true") {
        return "true";
    }
    else {
        return "unknown";
    }

   return "Unreachable code: cannot be covered";
}

1;

ในไดเร็กทอรี "t" ให้สร้างไฟล์ข้อความชื่อ "HelloPerlBuildWorld.t" ไฟล์นี้เป็นสคริปต์ทดสอบหน่วยของคุณที่จะพยายามทดสอบโมดูล Perl ด้านบนอย่างสมบูรณ์ วางเนื้อหาต่อไปนี้ลงในไฟล์นี้:

use strict;
use warnings;
use Test::More qw(no_plan);

# Verify module can be included via "use" pragma
BEGIN { use_ok('HelloPerlBuildWorld') };

# Verify module can be included via "require" pragma
require_ok( 'HelloPerlBuildWorld' );

# Test hello() routine using a regular expression
my $helloCall = HelloPerlBuildWorld::hello();
like($helloCall, qr/Hello, .*World/, "hello() RE test");

# Test hello_message() routine using a got/expected routine
is($helloCall, "Hello, Perl Build World!", "hello() IS test");

# Do not test bye() routine

# Test repeat() routine using a got/expected routine
for (my $ctr=1; $ctr<=10; $ctr++) {
    my $repeatCall = HelloPerlBuildWorld::repeat();
    is($repeatCall, 1, "repeat() IS test");
}

# Test argumentTest() 
my $argumentTestCall1 = HelloPerlBuildWorld::argumentTest();
is($argumentTestCall1, "null", "argumentTest() IS null test");

# Test argumentTest("true") 
my $argumentTestCall2 = HelloPerlBuildWorld::argumentTest("true");
is($argumentTestCall2, "true", "argumentTest() IS true test");

# Test argumentTest("false") 
my $argumentTestCall3 = HelloPerlBuildWorld::argumentTest("false");
is($argumentTestCall3, "false", "argumentTest() IS false test");

# Test argumentTest(123) 
my $argumentTestCall4 = HelloPerlBuildWorld::argumentTest(123);
is($argumentTestCall4, "unknown", "argumentTest() IS unknown test");

สำรองข้อมูลในไดเร็กทอรีโปรเจ็กต์ระดับบนสุดของคุณแล้วสร้างไฟล์ข้อความชื่อ "Build.PL" ไฟล์นี้จะสร้างบิลด์สคริปต์ของคุณที่คุณจะใช้ในภายหลัง วางเนื้อหาต่อไปนี้ลงในไฟล์นี้:

use strict;
use warnings;
use Module::Build;

my $builder = Module::Build->new(
    module_name         => 'HelloPerlBuildWorld',
    license             => 'perl',
    dist_abstract       => 'HelloPerlBuildWorld short description',
    dist_author         => 'Author Name <email_addy@goes.here>',
    build_requires => {
        'Test::More' => '0.10',
    },
);

$builder->create_build_script();

นั่นคือไฟล์ทั้งหมดที่คุณต้องการ จากบรรทัดคำสั่งในไดเร็กทอรีโปรเจ็กต์ระดับบนสุดให้พิมพ์คำสั่งต่อไปนี้:

perl Build.PL

คุณจะเห็นสิ่งที่คล้ายกับสิ่งต่อไปนี้:

Checking prerequisites...
Looks good

Creating new 'Build' script for 'HelloPerlBuildWorld' version '0.1'

ตอนนี้คุณควรจะรันการทดสอบหน่วยของคุณได้ด้วยคำสั่งต่อไปนี้:

Build test

และดูสิ่งที่คล้ายกับสิ่งนี้:

Copying lib\HelloPerlBuildWorld.pm -> blib\lib\HelloPerlBuildWorld.pm
t\HelloPerlBuildWorld....ok
All tests successful.
Files=1, Tests=18,  0 wallclock secs ( 0.00 cusr +  0.00 csys =  0.00 CPU)

ในการเรียกใช้การทดสอบหน่วยของคุณด้วยการวิเคราะห์ความครอบคลุมของรหัสให้ลองทำดังนี้:

Build testcover

และคุณจะเห็นบางอย่างตามลำดับนี้:

t\HelloPerlBuildWorld....ok
All tests successful.
Files=1, Tests=18, 12 wallclock secs ( 0.00 cusr +  0.00 csys =  0.00 CPU)
cover
Reading database from D:/Documents and Settings/LeuchKW/workspace/HelloPerlBuildWorld/cover_db


----------------------------------- ------ ------ ------ ------ ------ ------
File                                  stmt   bran   cond    sub   time  total
----------------------------------- ------ ------ ------ ------ ------ ------
D:/Perl/lib/ActivePerl/Config.pm       0.0    0.0    0.0    0.0    n/a    0.0
D:/Perl/lib/ActiveState/Path.pm        0.0    0.0    0.0    0.0    n/a    0.0
D:/Perl/lib/AutoLoader.pm              0.0    0.0    0.0    0.0    n/a    0.0
D:/Perl/lib/B.pm                      18.6   16.7   13.3   19.2   96.4   17.6
 ...
[SNIP]
 ...
D:/Perl/lib/re.pm                      0.0    0.0    0.0    0.0    n/a    0.0
D:/Perl/lib/strict.pm                 84.6   50.0   50.0  100.0    0.0   73.1
D:/Perl/lib/vars.pm                   44.4   36.4    0.0  100.0    0.0   36.2
D:/Perl/lib/warnings.pm               15.3   12.1    0.0   11.1    0.0   12.0
D:/Perl/lib/warnings/register.pm       0.0    0.0    n/a    0.0    n/a    0.0
blib/lib/HelloPerlBuildWorld.pm       87.5  100.0    n/a   83.3    0.0   89.3
Total                                  9.9    4.6    2.8   11.3  100.0    7.6
----------------------------------- ------ ------ ------ ------ ------ ------


Writing HTML output to D:/Documents and Settings/LeuchKW/workspace/HelloPerlBuildWorld/cover_db/coverage.html ...
done.

(ใครบางคนช่วยบอกวิธีกำหนดค่า Cover ให้ละเว้นไลบรารี Perl ทั้งหมดยกเว้นและรายงานกลับมาให้ฉันในไฟล์เดียวที่ฉันเขียนฉันไม่สามารถรับ Cover filtering ให้ทำงานตามเอกสาร CPAN ได้!)

ตอนนี้ถ้าคุณรีเฟรชไดเรกทอรีระดับบนสุดคุณจะเห็นไดเร็กทอรีย่อยใหม่ชื่อ "cover_db" เข้าไปในไดเรกทอรีนั้นและดับเบิลคลิกที่ไฟล์ "coverage.html" เพื่อเปิดรายงานความครอบคลุมของโค้ดในเว็บเบราว์เซอร์ที่คุณชื่นชอบ ช่วยให้คุณมีรายงานไฮเปอร์เท็กซ์รหัสสีที่สวยงามซึ่งคุณสามารถคลิกที่ชื่อไฟล์ของคุณและดูคำชี้แจงโดยละเอียดสาขาเงื่อนไขสถิติการครอบคลุมรูทีนย่อยสำหรับโมดูล Perl ของคุณที่นั่นในรายงานถัดจากซอร์สโค้ดจริง คุณสามารถดูได้ในรายงานนี้ว่าเราไม่ได้กล่าวถึงกิจวัตร "ลาก่อน ()" เลยและยังมีบรรทัดรหัสที่ไม่สามารถเข้าถึงได้ซึ่งไม่ครอบคลุมตามที่เราคาดไว้

ภาพรวมของรายงานความครอบคลุมของรหัส
(ที่มา: leucht.com )

อีกสิ่งหนึ่งที่คุณสามารถทำได้เพื่อช่วยให้กระบวนการนี้เป็นไปโดยอัตโนมัติใน IDE ของคุณคือการสร้างไฟล์ประเภท "Build.PL" เพิ่มเติมซึ่งดำเนินการกับเป้าหมายการสร้างบางอย่างที่เราทำข้างต้นด้วยตนเองอย่างชัดเจนจากบรรทัดคำสั่ง ตัวอย่างเช่นฉันใช้ไฟล์ "BuildTest.PL" ที่มีเนื้อหาต่อไปนี้:

use strict;
use warnings;
use Module::Build;

my $build = Module::Build->resume (
  properties => {
    config_dir => '_build',
  },
);

$build->dispatch('build');
$build->dispatch('test');

จากนั้นฉันตั้งค่า IDE ของฉันเพื่อเรียกใช้ไฟล์นี้ (ผ่าน "perl BuiltTest.PL") ด้วยการคลิกเมาส์เพียงครั้งเดียวและมันจะเรียกใช้รหัสทดสอบหน่วยของฉันโดยอัตโนมัติจาก IDE แทนที่จะให้ฉันทำด้วยตนเองจากบรรทัดคำสั่ง แทนที่ "dispatch ('test')" ด้วย "dispatch ('testcover')" สำหรับการดำเนินการครอบคลุมโค้ดอัตโนมัติ พิมพ์ "Build help" สำหรับรายการบิลด์เป้าหมายทั้งหมดที่พร้อมใช้งานจาก Module :: Build


1
ความคิดของคุณในการตั้งค่า BuiltTest.PL ฟังดูไม่ดีสำหรับฉัน ทำไมคุณไม่สามารถเขียนสคริปต์ที่ทำBuild buildแล้วBuild test?
Leon Timmermans

2
Leon คุณกำลังแนะนำสคริปต์ perl ที่เรียกใช้บรรทัดคำสั่งหรือไม่? ถ้าเป็นเช่นนั้นฉันไม่ต้องการเรียกใช้บรรทัดคำสั่งหากมีวิธี OO ในการโทรโดยใช้โปรแกรมดังในตัวอย่างไฟล์ BuiltTest.PL
Kurt W. Leucht

1
ไม่จำเป็นดูคำตอบของฉันเอง
Leon Timmermans

2
โมดูล :: Build ไม่ใช่สำหรับ CPAN คุณยังคงสามารถรับคุณสมบัติทั้งหมดจากเครื่องมือ CPAN ต่างๆได้แม้ว่าจะไม่ได้อยู่ใน CPAN ก็ตาม คุณยังคงสร้างทดสอบแจกจ่ายและติดตั้งได้ด้วยกระบวนการเดียวกันแม้ว่าจะเป็นโมดูลส่วนตัวก็ตาม
brian d foy

4
เพื่อผลการกรองใน Devel :: $ENV{HARNESS_PERL_SWITCHES}ปกฉันจะเพิ่มตัวเลือกในการ ตัวอย่างเช่นไลบรารีส่วนตัวของแอปพลิเคชันอยู่-MDevel::Cover=+ignore,.t$,+inc,/app/lib,-select,MyModule.pmที่ไหน/app/libและMyModule.pmเป็นโมดูลที่อยู่ระหว่างการทดสอบ
Michael Carman

14

เพื่อตอบสนองต่อเคิร์ตฉันจะเสนอทางเลือกนี้ให้กับสคริปต์ BuiltTest.PL ของเขา

use strict;
use warnings;
use Module::Build;

my $build = Module::Build->resume (
  properties => {
    config_dir => '_build',
  },
);

$build->dispatch('build');
$build->dispatch('test');

มันนำฐานข้อมูลที่สร้างโดย Build.PL มาใช้ซ้ำ (และถือว่ารันแล้ว)


สมบูรณ์แบบ! ขอบคุณ Leon ฉันรู้ว่ามีบางอย่างผิดปกติกับตัวอย่างของฉัน แต่ฉันยังใหม่กับสิ่งที่สร้าง perl ด้วยตัวเอง! :-)
Kurt W. Leucht

12

ฉันครอบคลุมในระดับกลาง Perlเช่นเดียวกับMastering Perl อย่างไรก็ตามเคิร์ตได้ให้บทสรุปที่ดี

I รวมทั้งหมดนี้เป็นสคริปต์เปิดตัวโดยใช้โมดูล :: Releaseแม้ว่า ฉันพิมพ์คำสั่งเดียวและทุกอย่างเกิดขึ้น


12

สิ่งนี้มีประโยชน์อย่างน่าอัศจรรย์module-starterสร้างโครงการโครงกระดูกที่ใช้งานง่ายซึ่งจัดการการติดตั้งโมดูลการสร้างเอกสารและรูปแบบที่ดีสำหรับไฟล์โมดูลที่จะใช้งานได้และ - ฉันคิดว่า - รองรับการครอบคลุมโค้ด IMO เป็นการเริ่มต้นที่ดีสำหรับความพยายามที่เกี่ยวข้องกับโมดูล Perl

นอกจากนี้: การใช้เครื่องมือ CPAN ที่เกี่ยวข้องเช่นModule::Build- แม้สำหรับโมดูลซึ่งมีแนวโน้มที่จะไม่เคยไปที่จะออกสู่สาธารณะ - เป็นความคิดที่ดีมาก


7

(การเปิดเผย: ฉันเป็นผู้เขียน)

เมื่อคุณจัดเรียงทุกอย่างตามที่อธิบายไว้ข้างต้นแล้วคุณสามารถทำขั้นตอนต่อไปและใช้Devel :: CoverX :: Coveredเพื่อเช่น

  • ให้ไฟล์ต้นฉบับแสดงรายการไฟล์ทดสอบที่ให้ความครอบคลุมกับไฟล์ต้นฉบับนั้น ซึ่งสามารถทำได้ในไฟล์รูทีนย่อยและระดับแถว
  • ให้ไฟล์ทดสอบแสดงรายการไฟล์ต้นฉบับและไฟล์ย่อยที่ครอบคลุมโดยไฟล์ทดสอบนั้น
  • รับไฟล์ต้นฉบับรายงานอย่างมีประสิทธิภาพเกี่ยวกับรายละเอียดความครอบคลุมต่อแถวหรือย่อย

ดูบทสรุปสำหรับตัวอย่างบรรทัดคำสั่งที่เป็นรูปธรรม

ในDevel :: PerlySenseมีการสนับสนุน Emacs เพื่อแสดงข้อมูลความครอบคลุมในบัฟเฟอร์ซอร์สโค้ด ( ภาพหน้าจอ ) และนำทางไปยัง / จากไฟล์ทดสอบที่ครอบคลุม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.