in reply to How to write testable command line script?

See also:

  • Comment on Re: How to write testable command line script?

Replies are listed 'Best First'.
Re^2: How to write testable command line script?
by thechartist (Monk) on Nov 25, 2018 at 21:31 UTC

    Thanks to the numerous examples, along with some deep thought, I have some success to report!

    While it isn't the prettiest code, my reduce subroutine passes the automated tests. There are still some issues to work out, but the main problems I solved were:
  • 1. Picking which test subroutine to use. I ended up using is_deeply, based on the post by AnomalousMonk. With that example, and some study of the Perl documentation, I figured out how to pass the test data to the subroutine correctly.
  • 2. Due to the assumptions I made when initially writing the script, I had hoped returning the entire array would have been sufficient. I had the issue that the array that was returned contained values from prior calls that are generally non-existent when I use the command line. This affected the structure equality comparisons. I ended up simply returning a list of scalars, which corrected the problem.
  • I will likely re-write this and extend it using some of the suggestions of Perlancer. But the initial goal of getting the tests to run has been met.

    reduce.t

    #!/usr/bin/env/perl use strict; use warnings; use Test::More 'no_plan'; my $test_path = "C:/Users/Greyhat/PDL_Old/trig/src"; ok( require( "$test_path/reduce.pl" ), 'Load file correctly.' ) or ex +it; # Not implemented #my $test_2 = [0,0,0]; #my $answer_2 = [0,0,0]; #my $note_2 = "@$test_2 | @$answer_2 | 2. Call with no value."; my $test_3 = [180, 59, 58]; my $answer_3 = [180, 59, 58]; my $note_3 = "@$test_3 | @$answer_3 | 3. already reduced"; my $test_4 = [19, 0, 60]; my $answer_4 = [19, 1, 0]; my $note_4 = "@$test_4 | @$answer_4 | 4. test if single carry wo +rks correctly"; my $test_5 = [179, 59, 60]; my $answer_5 = [180, 0, 0]; my $note_5 = "@$test_5 | @$answer_5 | 5. tests if multiple carry + works correctly"; my $test_6 = [-179, 60, 0]; my $answer_6 = [-178, 0, 0]; my $note_6 = "@$test_6 | @$answer_6 | 6. test addition with nega +tives"; my $test_7 = [ 0, 0, -60]; my $answer_7 = [-1, 59, 0]; my $note_7 = "@$test_7 | @$answer_7 | 7. test negative borrow wo +rks correctly"; my $test_8 = [90, 360, 360]; my $answer_8 = [96, 6, 0]; my $note_8 = "@$test_8 | @$answer_8 | 8. tests if multiple reduc +e calls work correct"; sub main { reduce( @ARGV ); } # Degree Reduction Tests # Need to pass array refs to is_deeply #ok( &main() == $answer_2, $note_2 ); # From perl monks test code # is_deeply [ reduce(@$ar_args) ], $ar_expected, $full_comment; is_deeply [ reduce(@$test_3 ) ], $answer_3, $note_3 ; + is_deeply [ reduce(@$test_4 ) ], $answer_4, $note_4 ; + is_deeply [ reduce(@$test_5 ) ], $answer_5, $note_5 ; + is_deeply [ reduce(@$test_6 ) ], $answer_6, $note_6 ; + is_deeply [ reduce(@$test_7 ) ], $answer_7, $note_7 ; + is_deeply [ reduce(@$test_8 ) ], $answer_8, $note_8 ; # Degree Subtraction Tests # Degree Multiplication Tests # Degree Division Tests
    reduce.pl
    #!/usr/bin/env/perl use strict; use warnings; my ($a, $b, $c) = 0; print "0. Value of ARGV is @ARGV; Value of magic array var is @_.\n"; # Take list of arguments from any of the 4 other subroutines # In principle, should accept variable length arguments # and recursively reduce the items in list from right to left. # # Termination: @angle has 1 length. This is pushed onto @answer array +. # Case 1: reduce negative number by adding 60 to it, and # subtracting 1 from number on left. # Case 2: reduce positive number >= 60 by subtracting 60 and # adding 1 to number on left. # # NOTE 1: CHECK BRACKETS AROUND UNTIL LOOP! MAY NEED TO MOVE CLOSING +BRACKET! # NOTE 2: Add check to test for length of @angle array! Then it shou +ld work. # NOTE 3: Add elsif to test for case where $c is ok value but scalar( +@angle) > 1. # Just push value to answer array. # NOTE 4: Does not handle multiples of 60 correctly. Likely scope is +sue. my @answer; sub reduce { # print "0. Value of magic array var is @_.\n"; my @angle = @_; # @_ = undef; my ($b, $c) = ($angle[-2], $angle[-1]); # reduce from end. if ($c < 0 && scalar(@angle) > 1) { until ($c >= 0 && $c < 60) { $c += 60; $b -= 1; } unshift(@answer, $c); pop(@angle); @angle[-1] = $b; # Debug print statements print "2. b = $b, c = $c\n"; print "2. Angle array is @angle.\n "; print "2. Value of magic array var is @_.\n"; print "2. Values in answer array: @answer.\n"; #### &reduce(@angle); } elsif ($c >= 60 && scalar(@angle) > 1 ) { until ($c < 60 && $c >= 0) { $c -= 60; $b += 1; } unshift(@answer, $c); pop(@angle); @angle[-1] = $b; # Debug print statements print "3. b = $b, c = $c\n"; print "3. Angle array is @angle.\n "; print "3. Value of magic array var is @_.\n"; print "3. Values in answer array: @answer.\n"; #### &reduce(@angle); } elsif ( ($c >= 0 && $c < 60 ) && scalar(@angle) > 1) { unshift(@answer, $c); pop(@angle); &reduce(@angle); } else { unshift(@answer, @angle); print "Reduced answer: @answer \n"; return $answer[0], $answer[1], $answer[2]; } } main( @ARGV ) unless caller(); sub main { reduce( @_ ); }

      ... the initial goal of getting the tests to run has been met.

      I'm looking at your  $test_7: (0, 0, -60) -> (-1, 59, 0) (result: -1° 59' 0"). (There's a similar test 7 here with result (-1, 59, 2).) I would have thought the normalized or reduced result to be  (0 -1 0) (0° -1' 0"). If the required result shown in the code is correct, it suggests a set of sign propagation rules I have yet to see. Can you expand on this?

      I notice that you haven't recast your core code into a module yet. Doing so is a good idea for many reasons, including testing. Note that Test::More::use_ok() is available for modules and Test::More::require_ok() for more humble files.

      The other point that occurred to me is also in terms of general design. Rather than always reduce()-ing deg/min/sec tuples to other d/m/s tuples, it might be less of a headache to normalize d/m/s tuples to, say, integer or decimal fraction arc-seconds (or maybe radians?) or whatever's most convenient, do all the trig in these standard units, then convert back to the ultimate d/m/s (or whatever) output form just once as a final step. Just a thought...


      Give a man a fish:  <%-{-{-{-<

        Negative numbers are only permitted in the degree column. Any minutes or seconds need to be a positive integer from 0-59 inclusive. Although an angle has a combined decimal and base 60 format, I treat the base 60 components of an angle like the fractional components of a decimal are treated; negatives get propagated to the next highest term. You can find the procedure Albert Klaf's Trigonometry Refresher on page 12; the section is available on google books. As I was going through the tests, I noticed some errors in the tests that I have no idea how they made their way into the code. I suspect they were just typos or search/replace errors I had missed. Regarding extensions to decimals -- that is a planned future extension based upon the problems in the study guide I am going through. Instead of doing the problems manually, I write a program to calculate the solutions, and learn Perl testing in the process :)