Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Getting Testy With Raku: Things you wish you knew.

Getting Testy With Raku: Things you wish you knew.

Testing is well integrated into Raku culture, starting with a familiar "Test" module. This talk reviews the basics of testing with "ok" along with data structure issues that affect testing and comparison. The coverage is suitable for someone new to Raku or who wants to improve their testing.

Steven Lembark

July 02, 2024
Tweet

More Decks by Steven Lembark

Other Decks in Technology

Transcript

  1. Time passes This talk was given in 2016 on ‘Testing

    Perl6’. Now it’s “Raku”. Some things have changed.
  2. Time passes This talk was given in 2016 on ‘Testing

    Perl6’. Now it’s “Raku”. Some things have changed. Methods use ‘-’ instead of ‘_’. zef replaced panda. prove6 replaced prove.
  3. Time passes This talk was given in 2016 on ‘Testing

    Perl6’. Now it’s “Raku”. Much of it’s the same. TAP format Test module ok method
  4. Getting Started # hard to test without raku, though #

    installation is straightforward git clone \ git://github.com/rakudo/rakudo.git; cd rakudo; less INSTALL.md;
  5. Getting Started # hard to test without raku, though. #

    installation is straightforward. git clone \ git://github.com/rakudo/rakudo.git; cd rakudo; git pull --all; # doc’s describe other choices. perl Configure.pl \ --prefix=/opt/rakudo \ --gen-moar --gen-nqp \ --backends=moar ;
  6. Getting Started # eventually... +++ Installing default runner NQP has

    been built and installed. Using /opt/rakudo/bin/nqp-m (version 2022.12 / MoarVM 2022.12). Cleaning up ... You can now use 'make' to build Rakudo. After that, 'make test' will run some tests and 'make install' will install Rakudo.
  7. Getting Started # eventually... +++ Installing default runner NQP has

    been built and installed. Using /opt/rakudo/bin/nqp-m (version 2022.12 / MoarVM 2022.12). Cleaning up ... You can now use 'make' to build Rakudo. After that, 'make test' will run some tests and 'make install' will install Rakudo.
  8. Getting Started make -wk all test install; ... # our

    first example of Raku tests! t/01-sanity/02-op-math.t ....................... ok t/01-sanity/03-op-logic.t ...................... ok t/01-sanity/04-op-cmp.t ........................ ok t/01-sanity/01-literals.t ...................... ok
  9. Getting Started t/06-telemetry/01-basic.t ..................... ok All tests successful. Files=111, Tests=1865,

    582 wallclock secs ( 0.57 usr 0.12 sys + 225.93 cusr 22.04 csys = 248.66 CPU) Result: PASS +++ Creating installation directories
  10. Getting Started +++ Preparing installation Installed 20 core modules in

    10.587543038 seconds! +++ MOAR BACKEND INSTALLED +++ Installing MOAR launchers +++ Creating Raku executable alias +++ Rakudo installed succesfully! make: Leaving directory '/scratch/Build/Raku/rakudo'
  11. Getting Started # we will need the package manager: git

    clone https://github.com/ugexe/zef.git ; cd zef ; less README.md;
  12. Getting Started # we will need the package manager: git

    clone https://github.com/ugexe/zef.git ; cd zef ; raku -I. bin/zef install . ;
  13. Getting Started raku -I. bin/zef install . ; ===> Staging

    zef:ver<0.22.0>:auth<github:ugexe>:api<0> ===> Staging [OK] for zef:ver<0.22.0>:auth<github:ugexe>:api<0> ===> Testing: zef:ver<0.22.0>:auth<github:ugexe>:api<0> ===> Testing [OK] for zef:ver<0.22.0>:auth<github:ugexe>:api<0> ===> Installing: zef:ver<0.22.0>:auth<github:ugexe>:api<0> 1 bin/ script [zef] installed to: /opt/rakudo/share/perl6/site/bin
  14. Getting Started # validate zef install, update the libraries, #

    validate the network connection. zef update ;
  15. Getting Started # validate zef install, update the libraries, #

    validate the network connection. zef update ; ===> Updating fez mirror: https://360.zef.pm/ ===> Updating rea mirror: https://raw.githubusercontent.com/Raku/REA/main/META.json ===> Updated fez mirror: https://360.zef.pm/ ===> Updated rea mirror: https://raw.githubusercontent.com/Raku/REA/main/META.json
  16. Getting Started # Linenoise gives command line editing, etc. zef

    install Linenoise ; ===> Searching for: Linenoise ===> Searching for missing dependencies: LibraryMake ===> Searching for missing dependencies: Shell::Command, File::Which ===> Searching for missing dependencies: File::Find ===> Building: Linenoise:ver<0.1.2>:auth<zef:raku-community-modules>
  17. Modules are more specific in Raku! Perl modules are Foo::Bar.

    Raku modules support version owner api
  18. Modules are more specific in Raku! Perl modules are Foo::Bar.

    Raku module with version and author: Linenoise:ver<0.1.2>:auth<zef:raku-community-modules>
  19. Modules are more specific in Raku! Perl modules are Foo::Bar.

    Raku module with version and author: Linenoise:ver<0.1.2>:auth<zef:raku-community-modules> Raku testing may reqire more metadata.
  20. Also helps to have prove6 # install the prove utility

    zef install App::Prove6 ; ... ===> Installing: App::Prove6:ver<0.0.17>:auth<cpan:LEONT> 1 bin/ script [prove6] installed to: /opt/rakudo/share/perl6/site/bin ===> Installing: App::Prove6:ver<0.0.17>:auth<cpan:LEONT> 1 bin/ script [prove6] installed to: /opt/rakudo/share/perl6/site/bin
  21. Also helps to have prove6 # install the prove utility

    zef install App::Prove6 ; ... ===> Installing: App::Prove6:ver<0.0.17>:auth<cpan:LEONT> 1 bin/ script [prove6] installed to: /opt/rakudo/share/perl6/site/bin ===> Installing: App::Prove6:ver<0.0.17>:auth<cpan:LEONT> 1 bin/ script [prove6] installed to: /opt/rakudo/share/perl6/site/bin
  22. Basic Test in Raku Start with Test use Test; my

    $x = 0; my $y = 'asdf'; ok $x == $y, "$x == $y"; done-testing;
  23. Basic Test in Raku Start with Test Test is run

    with ”ok” as usual. use Test; my $x = 0; my $y = 'asdf'; ok $x == $y, "$x == $y"; done-testing;
  24. Basic Test in Raku Start with Test Notice the dash!

    Raku methods use ‘-’ in their names! use Test; my $x = 0; my $y = 'asdf'; ok $x == $y, "$x == $y"; done-testing;
  25. Running the test $ prove6 t/01.t Cannot convert string to

    number: base-10 number must begin with valid digits or '.' in ' asdf' (indicated by ) ⏏ ⏏ in block <unit> at t/01.t line 6 t/01.t .. Dubious, test returned 1 No subtests run Test Summary Report ------------------- t/01.t (Wstat: 256 Tests: 0 Failed: 0) Non-zero exit status: 1 Parse errors: No plan found in TAP output Files=1, Tests=0, 0 wallclock secs Result: FAILED
  26. Running the test $ prove6 t/01.t Cannot convert string to

    number: base-10 number must begin with valid digits or '.' in ' asdf' (indicated by ) ⏏ ⏏ in block <unit> at t/01.t line 6 my $x = 0; my $y = 'asdf'; ok $x == $y, "$x == $y";
  27. Running the test my $x = 0; my $y =

    'asdf'; ok $x == $y, "$x == $y"; Lesson: Raku is more type-specific than Perl.
  28. Stringy test with Raku “is” does stringy tests. use Test;

    my $x = '0'; my $y = 'asdf'; is $x, $y, "$x eq $y"; done-testing;
  29. Stringy test with Raku $ prove6 t/02.t # Failed test

    '0 eq asdf' # at t/02.t line 6 # expected: 'asdf' # got: '0' # You failed 1 test of 1 t/02.t .. Dubious, test returned 1 Failed 1/1 subtests Test Summary Report ------------------- t/02.t (Wstat: 256 Tests: 1 Failed: 1) Failed tests: 1 Non-zero exit status: 1 Files=1, Tests=1, 0 wallclock secs Result: FAILED
  30. Smarter tests with ~~ Smart-Match finds ‘reasonable’ ways to compare

    objects. 0 ~~ ‘asdf’ fails gracefully based on the value. use Test; my $x = 0; my $y = 'asdf'; ok $x ~~ $y, "$x ~~ $y"; done-testing;
  31. Smarter tests with ~~ $ prove6 t/05.t # Failed test

    '0 ~~ asdf' # at t/05.t line 6 # You failed 1 test of 1 t/05.t .. Dubious, test returned 1 Failed 1/1 subtests Test Summary Report ------------------- t/05.t (Wstat: 256 Tests: 1 Failed: 1) Failed tests: 1 Non-zero exit status: 1 Files=1, Tests=1, 0 wallclock secs Result: FAILED
  32. Introspection in tests “WHAT” returns a type object. use Test;

    my $x = 0; is $x.WHAT, Int, "$x is Int"; done-testing;
  33. Introspection in tests Int is an object, not a string:

    Notice the lack of quotes. use Test; my $x = 0; is $x.WHAT, Int, "$x is Int"; done-testing;
  34. Introspection in tests $ prove6 t/03.t ; t/03.t .. ok

    All tests successful. Files=1, Tests=1, 0 wallclock secs Result: PASS
  35. More introspection with WHICH WHICH identifies the object. use Test;

    my $x = 0; is $x.WHAT, Int, "$x is Int"; note '$x is ' ~ $x.WHICH; done-testing;
  36. More introspection with WHICH $x is an Int object with

    a value of 0. Output nice for diagnostics. $ prove6 t/04.t $x is Int|0 t/04.t .. ok All tests successful. Files=1, Tests=1, 0 wallclock secs Result: PASS
  37. Quick tests in the REPL $ raku; Welcome to Rakudo™

    v2022.12. Implementing the Raku® Programming Language v6.d. Built on MoarVM version 2022.12. To exit type 'exit' or '^D' [0] >
  38. Quick tests in the REPL [0] > %a = 'a'

    .. 'e' ; ===SORRY!=== Error while compiling: Variable '%a' is not declared. Perhaps you forgot a 'sub' if this was intended to be part of a signature? ------> <BOL> %a = 'a' .. 'e' ; ⏏ Type what you like.
  39. Quick tests in the REPL [0] > %a = 'a'

    .. 'e' ; ===SORRY!=== Error while compiling: Variable '%a' is not declared. Perhaps you forgot a 'sub' if this was intended to be part of a signature? ------> <BOL> %a = 'a' .. 'e' ; ⏏ Type what you like. Variables must be declared (reverse of ‘perl -d’).
  40. Quick tests in the REPL “my ^a = …” has

    a variable. ‘a’ .. ‘e’ mis-defines the hash: odd elemement count. But what’s in the hash now? [0] > my %a = 'a' .. 'e' ; Odd number of elements found where hash initializer expected: Found 5 (implicit) elements: Last element seen: "e" in block <unit> at <unknown file> line 1
  41. Quick tests in the REPL WHAT & WHICH to the

    rescue. %a is defined – we have a hash. %a is empty due to the assignment error: No partitial assignment or empty value. [1] > say %a.WHAT; (Hash) [1] > say %a.WHICH; Hash|2999974115968 [1] > say %a; {}
  42. Comparing objects: Content vs. Identity Assign %b from %a: Separate

    objects. Equal contents. > my %a = ( 'a' .. 'd' ); {a => b, c => d} > my %b = %a; {a => b, c => d}
  43. Comparing objects: Content vs. Identity Assign %b from %a: Separate

    objects. Equal contents. > my %a = ( 'a' .. 'd' ); {a => b, c => d} > my %b = %a; {a => b, c => d}
  44. Comparing objects: Content vs. Identity Compare Contents > my %a

    = ( 'a' .. 'd' ); {a => b, c => d} > my %b = %a; {a => b, c => d} > ok %a == %b; ok 1 -
  45. Comparing objects: Content vs. Identity > my %a = (

    'a' .. 'd' ); {a => b, c => d} > my %b = %a; {a => b, c => d} > ok %a eq %b; ok 2 - Compare Contents
  46. Comparing objects: Content vs. Identity > my %a = (

    'a' .. 'd' ); {a => b, c => d} > my %b = %a; {a => b, c => d} > ok %a ~~ %b; ok 3 - Compare Contents
  47. Comparing objects: Content vs. Identity > my %a = (

    'a' .. 'd' ); {a => b, c => d} > my %b = %a; {a => b, c => d} > is %a, %b; ok 4 - Compare Contents
  48. Comparing objects: Content vs. Identity > my %a = (

    'a' .. 'd' ); {a => b, c => d} > my %b = %a; {a => b, c => d} > ok %a === %b; not ok 5 - # Failed test at... > %a.WHICH Hash|6519650522896 > %b.WHICH Hash|6519768615264 Compare objects.
  49. Comparing objects vs. containers Value vs Non-value: Values: contents Containers:

    .WHICH == Contents === .WHICH my $a = 42; my $b = 42; my %a = { ‘a’ .. ‘z’ }; my $b = { ‘a’ .. ‘z’ }; $a === $b 42 == 42 true %a === $b %a.WHICH == $b.WHICH false
  50. Hashes don’t ‘flatten’ my %a = ( 'a' .. 'd'

    ); my @b = %a; my @c = ( 'a' .. 'd' ); +> dd %a, @b, @c Hash %a = {:a("b"), :c("d")} Array @b = [:a("b"), :c("d")] Array @c = ["a", "b", "c", "d"]
  51. Hashes don’t ‘flatten’ my %a = ( 'a' .. 'd'

    ); my @b = %a; my @c = ( 'a' .. 'd’ ); +> dd %a, @b, @c Hash %a = {:a("b"), :c("d")} Array @b = [:a("b"), :c("d")] <-- pairs! Array @c = ["a", "b", "c", "d"]
  52. Comparing constants Some things like “Bag” are immutable. These will

    share a WHICH value. True with '===' (identity) False with '=:=' (object id)
  53. Bags are immutable > my @a = ( 'a', 'b',

    'c', '', 'c', 'c' ); [a b c b c c] > my $b = @a.Bag; bag(a, c(3), b(2)) > my $c = @a.Bag; bag(a, c(3), b(2))
  54. Bags are immutable > @a.WHICH; Array|162386224 > $b.WHICH; Bag|Str|a(1) Str|b(2)

    Str|c(3) > $c.WHICH; Bag|Str|a(1) Str|b(2) Str|c(3) > my @a = ( 'a', 'b', 'c', '', 'c', 'c' ); [a b c b c c] > my $b = @a.Bag; bag(a, c(3), b(2)) > my $c = @a.Bag; bag(a, c(3), b(2))
  55. Bags are immutable > @a.WHICH; Array|162386224 > $b.WHICH; Bag|Str|a(1) Str|b(2)

    Str|c(3) > $c.WHICH; Bag|Str|a(1) Str|b(2) Str|c(3) > my @a = ( 'a', 'b', 'c', '', 'c', 'c' ); [a b c b c c] > my $b = @a.Bag; bag(a, c(3), b(2)) > my $c = @a.Bag; bag(a, c(3), b(2)) > $b === $c # contents True
  56. Bags are immutable > @a.WHICH; Array|162386224 > $b.WHICH; Bag|Str|a(1) Str|b(2)

    Str|c(3) > $c.WHICH; Bag|Str|a(1) Str|b(2) Str|c(3) > my @a = ( 'a', 'b', 'c', '', 'c', 'c' ); [a b c b c c] > my $b = @a.Bag; bag(a, c(3), b(2)) > my $c = @a.Bag; bag(a, c(3), b(2)) > $b === $c # contents True > $b =:= $c # objects False
  57. Multimetods allow various comparisons “eqv” used to compare objects. This

    is an operator: multi infix: <eqv> ( Foo $x, Bar $y ) Classes can supply their own. Tests can override them.
  58. Multimetods allow various comparisons Compare to an Int: multi infix:

    <eqv> ( MyClass $a, Int $b ) { ... } Compare undefined value: multi infix: <eqv> (MyClass:U $a, Mu $b) { fail “Undefined MyClass Object” }
  59. Have it your way... Is-deeply uses its own eqv. cmp-ok

    uses yours: cmp-ok $found, 'eqv', $expect; cmp-ok $found, 'oneshot', $expect;
  60. Approximate testing Absolute, relative tolerance to expect. Uses eqv, requires

    same types. Really nice for geo-coding, physical data: my $abs-tol = 0.0005; is-approx $lat, $exp, :$abs-tol;
  61. Summary Testing is a bit more precise in Raku than

    Perl: Strong types == vs. === vs. =:= And more flexible: == vs. eq vs. ~~ vs. eqv Pay attention to underlying types: hashes don’t ‘flatten’, bags are immutable.