Testing Scripts Randal L. Schwartz, merlyn@stonehenge.com Version LT-1.05 on 13 Jun 2012 This document is copyright 2012 by Randal L. Schwartz, Stonehenge Consulting Services, Inc. This work is licensed under Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License http://creativecommons.org/licenses/by-nc-sa/3.0/ Monday, June 25, 12 1
• Problem: • Ya gotta test! • Solution: • use Test::More and friends • But: • What about scripts! Monday, June 25, 12 2
• Problem: • scripts are separate process • hard to mock things • Solution: • Don’t use a separate process • Require your script in your .t • But: • How will I invoke it then? Monday, June 25, 12 3
• Problem: • Loose code is effectively “main” • Solution: • Bundle loose code into a run subroutine: sub run { ... } • Also ensure true value at end of script • But: • What will invoke “run” then? Monday, June 25, 12 4
• Problem: • Invoke “run” when run normally • Don’t invoke “run” via require • Solution: • Use “caller”: run(@ARGV) unless caller; • But: • What about namespace of .t file Monday, June 25, 12 5
• Problem: • Collision between script and .t names • Solution: • Bring it into its own package: BEGIN { package Program; require "your-script"; die $@ if $@; } • But: • How to “invoke the program” from tests? Monday, June 25, 12 6
• Problem: • Simulate execution • Solution: • Invoke run() with desired @ARGV: subtest try_it => sub { Program::run(qw(--foo --bar abc)); }; • But: • What about exceptions, exit, stdout? Monday, June 25, 12 7
• Problem: • Trapping everything (not just die) • eval doesn’t cut it! • Solution: • Test::Trap! use Test::Trap; trap { Program::run(qw(--foo --bar abc)); }; • But: • How will I know how the code finished? Monday, June 25, 12 8
• Problem: • Was it normal exit, “exit”, or die? • Solution: • examine $trap object after trap { .. } ok $trap->exit, 0, "exited 0"; like $trap->die, qr{missing args}; • But: • What about stdout, stderr, warnings? Monday, June 25, 12 9
• Problem: • What about those outputs? • Solution: • Test::Trap captures those too! like $trap->stdout, qr{usage}; is $trap->stderr, q{}, "quiet errors"; is @{$trap->warn}, 1, "exactly 1 warn"; • But: • What about stubbing or mocking? Monday, June 25, 12 10
• Problem: • Want to override some behavior • Solution: • Monkey patching! subtest stub_it => sub { local *Program::some_sub = sub { ... }; trap { Program::run() }; }; • But: • What about stdin? Monday, June 25, 12 11
• Problem: • Provide stdin for script • Solution: • Small matter of programming: local *STDIN; open STDIN, "<", (my $S = join(q{})); $$S .= "onentwonthreen"; trap { ... }; $$S .= "fournfiven"; trap { ... }; • But: • What about chdir? Monday, June 25, 12 12
• Problem: • chdir has global effect • Solution: • Test::Trap is pluggable! use Test::Trap::mine qw(:cwd); trap { chdir "/tmp"; Program::run() }; • See my blog, or might be core now • But: • Does this really work for all scripts Monday, June 25, 12 13
• Problem: • Script might need complex interaction • Maybe can’t edit code into run() • Code might fork • Solution: • Yeah, traditional subprocesses • Perhaps combined with Expect • But: • Test::Trap is amazingly useful! Monday, June 25, 12 14
Follow me • Twitter: @merlyn • G+: Randal L. Schwartz • Personal blog: merlyn.posterous.com • http://blogs.perl.org/users/randal_l_schwartz/ • merlyn, realmerlyn, or RandalSchwartz Monday, June 25, 12 15

Testing scripts

  • 1.
    Testing Scripts Randal L. Schwartz, merlyn@stonehenge.com Version LT-1.05 on 13 Jun 2012 This document is copyright 2012 by Randal L. Schwartz, Stonehenge Consulting Services, Inc. This work is licensed under Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License http://creativecommons.org/licenses/by-nc-sa/3.0/ Monday, June 25, 12 1
  • 2.
    • Problem: • Ya gotta test! • Solution: • use Test::More and friends • But: • What about scripts! Monday, June 25, 12 2
  • 3.
    • Problem: • scripts are separate process • hard to mock things • Solution: • Don’t use a separate process • Require your script in your .t • But: • How will I invoke it then? Monday, June 25, 12 3
  • 4.
    • Problem: • Loose code is effectively “main” • Solution: • Bundle loose code into a run subroutine: sub run { ... } • Also ensure true value at end of script • But: • What will invoke “run” then? Monday, June 25, 12 4
  • 5.
    • Problem: • Invoke “run” when run normally • Don’t invoke “run” via require • Solution: • Use “caller”: run(@ARGV) unless caller; • But: • What about namespace of .t file Monday, June 25, 12 5
  • 6.
    • Problem: • Collision between script and .t names • Solution: • Bring it into its own package: BEGIN { package Program; require "your-script"; die $@ if $@; } • But: • How to “invoke the program” from tests? Monday, June 25, 12 6
  • 7.
    • Problem: • Simulate execution • Solution: • Invoke run() with desired @ARGV: subtest try_it => sub { Program::run(qw(--foo --bar abc)); }; • But: • What about exceptions, exit, stdout? Monday, June 25, 12 7
  • 8.
    • Problem: • Trapping everything (not just die) • eval doesn’t cut it! • Solution: • Test::Trap! use Test::Trap; trap { Program::run(qw(--foo --bar abc)); }; • But: • How will I know how the code finished? Monday, June 25, 12 8
  • 9.
    • Problem: • Was it normal exit, “exit”, or die? • Solution: • examine $trap object after trap { .. } ok $trap->exit, 0, "exited 0"; like $trap->die, qr{missing args}; • But: • What about stdout, stderr, warnings? Monday, June 25, 12 9
  • 10.
    • Problem: • What about those outputs? • Solution: • Test::Trap captures those too! like $trap->stdout, qr{usage}; is $trap->stderr, q{}, "quiet errors"; is @{$trap->warn}, 1, "exactly 1 warn"; • But: • What about stubbing or mocking? Monday, June 25, 12 10
  • 11.
    • Problem: • Want to override some behavior • Solution: • Monkey patching! subtest stub_it => sub { local *Program::some_sub = sub { ... }; trap { Program::run() }; }; • But: • What about stdin? Monday, June 25, 12 11
  • 12.
    • Problem: • Provide stdin for script • Solution: • Small matter of programming: local *STDIN; open STDIN, "<", (my $S = join(q{})); $$S .= "onentwonthreen"; trap { ... }; $$S .= "fournfiven"; trap { ... }; • But: • What about chdir? Monday, June 25, 12 12
  • 13.
    • Problem: • chdir has global effect • Solution: • Test::Trap is pluggable! use Test::Trap::mine qw(:cwd); trap { chdir "/tmp"; Program::run() }; • See my blog, or might be core now • But: • Does this really work for all scripts Monday, June 25, 12 13
  • 14.
    • Problem: • Script might need complex interaction • Maybe can’t edit code into run() • Code might fork • Solution: • Yeah, traditional subprocesses • Perhaps combined with Expect • But: • Test::Trap is amazingly useful! Monday, June 25, 12 14
  • 15.
    Follow me • Twitter: @merlyn • G+: Randal L. Schwartz • Personal blog: merlyn.posterous.com • http://blogs.perl.org/users/randal_l_schwartz/ • merlyn, realmerlyn, or RandalSchwartz Monday, June 25, 12 15