DEV Community

Tony Metzidis
Tony Metzidis

Posted on

Testing Without Excuses

Every app has that last inch (or mile) of code that's not covered by tests. Usually it's an interactive cycle of compile-run-inspect on the command line like

You Test

 curl -X POST https://reqbin.com/echo/post/json
Enter fullscreen mode Exit fullscreen mode

👀 You Expect:

{"success":"true"}
Enter fullscreen mode Exit fullscreen mode

Despite having 3-4 testing frameworks for unit tests, e2e, regression etc-- there's always a gap where you find yourself re-playing commands in the terminal to test.

A common case is 🔥firefighting where ad-hoc tests are needed to validate an emergency config change or deployment.

Not only is this a waste of time, it's error prone and reduces the number of assertions per run.

With Test::More, you can easily run dozens of assertions in < 1 second. Moreover, you can run them in a loop with watch perl test.t (read to the end to find out)

Close the Testing Gap with Perl

To close this Gap, you want a basic testing framework to easily test assertions on the command line: asserting expected output or return status (e.g. success == 0, failure >= 1)

Perl Test::More is an elegant solution:

  • it has a trivial and easy-to-remember interface: ok(), is(), isnt()
  • it easily tests shell scripts like head myfile.txt to test output, or system grep needle < haystack to test return code
  • is universally available on every distro. No apt-get, npm-install, downloading, linking, compiling needed.

Let's Get Started

use Test::More tests => 1; # or

# basic test of stdout
is(`echo -n "hello dev.to"`, "hello dev.to", "test echo")  
Enter fullscreen mode Exit fullscreen mode

The most basic is() test using actual backticks. First arg is the command, second arg is expected output, third is a test description.

Testing Return Status Code Success

# test success status code = 0
is((system "echo hello dude | grep -q dude"), 0, "test output contains dude");
Enter fullscreen mode Exit fullscreen mode

Similar to above, but use (system "COMMAND") to test the return status code instead of the output

Test For Failure / Status code = 1

In this case, grep for "dude", assert that it fails (status=1)

# test failure status code = 1 (shift left by 8)
is((system "echo hello bob | grep -q dude"), 1<<8, "test output does not contain dude");
Enter fullscreen mode Exit fullscreen mode

To test non-zero, bit-shift by 8 (read the docs for system to understand why)

Test File Contents

# test reading a file
is(`head -n 1 /etc/passwd`, "root:x:0:0:root:/root:/bin/bash\n", 'top of passwd')
Enter fullscreen mode Exit fullscreen mode

This pattern is useful, using head , tail or grep to test for file content. Works well for testing log output upon running a command.

Testing APIs

# test curl output with special chars and newline
is(`curl -X POST https://reqbin.com/echo/post/json`, 
<<WANT
{"success":"true"}
WANT
, "test post req");
Enter fullscreen mode Exit fullscreen mode

Use "heredoc" (multi-line doc) to test special characters & newlines without needing lots of escapes.

Run the Suite in the Background

use Test::More tests => 6; # or

# basic test of stdout
is(`echo -n "hello dev.to"`, "hello dev.to", "test echo");
# test success status code = 0
is((system "echo hello dude | grep -q dude"), 0, "test output contains dude");
# test failure status code = 1 (shift left by 8)
is((system "echo hello bob | grep -q dude"), 1<<8, "test output does not contain dude");
is((system "false"), 1<<8, "false not ok");
# test reading a file
is(`head -n 1 /etc/passwd`, "root:x:0:0:root:/root:/bin/bash\n", 'top of passwd');
# test curl output with special chars and newline
is(`curl -X POST https://reqbin.com/echo/post/json`, 
<<WANT
{"success":"true"}
WANT
, "test post req");
Enter fullscreen mode Exit fullscreen mode

Now you have a suite of 6 tests, open a second terminal and run it with watch perl test.t

Every 2.0s: perl test.t                                                                                   pi4plus: Sat Aug 21 05:09:50 2021

1..6
ok 1 - test echo
ok 2 - test output contains dude
ok 3 - test output does not contain dude
ok 4 - false not ok
ok 5 - top of passwd
ok 6 - test post req
Enter fullscreen mode Exit fullscreen mode

With Perl shell tests running in a loop, you can focus on code changes and your terminal will ring if a test case breaks.

Wrap Up

I hope this helps you close that last gap of code that's not tested. Even in the middle of a fire, you can copy-paste your tests into a test.t file within the terminal.

Read the Perl Docs for More Info on Test::More & system

Discussion (0)