ert: Useful Techniques

 
 3.4 Useful Techniques when Writing Tests
 ========================================
 
 Testing simple functions that have no side effects and no dependencies
 on their environment is easy.  Such tests often look like this:
 
      (ert-deftest ert-test-mismatch ()
        (should (eql (cl-mismatch "" "") nil))
        (should (eql (cl-mismatch "" "a") 0))
        (should (eql (cl-mismatch "a" "a") nil))
        (should (eql (cl-mismatch "ab" "a") 1))
        (should (eql (cl-mismatch "Aa" "aA") 0))
        (should (eql (cl-mismatch '(a b c) '(a b d)) 2)))
 
    This test calls the function ‘cl-mismatch’ several times with various
 combinations of arguments and compares the return value to the expected
 return value.  (Some programmers prefer ‘(should (eql EXPECTED ACTUAL))’
 over the ‘(should (eql ACTUAL EXPECTED))’ shown here.  ERT works either
 way.)
 
    Here’s a more complicated test:
 
      (ert-deftest ert-test-record-backtrace ()
        (let ((test (make-ert-test :body (lambda () (ert-fail "foo")))))
          (let ((result (ert-run-test test)))
            (should (ert-test-failed-p result))
            (with-temp-buffer
              (ert--print-backtrace (ert-test-failed-backtrace result))
              (goto-char (point-min))
              (end-of-line)
              (let ((first-line (buffer-substring-no-properties
                                 (point-min) (point))))
                (should (equal first-line
                               "  signal(ert-test-failed (\"foo\"))")))))))
 
    This test creates a test object using ‘make-ert-test’ whose body will
 immediately signal failure.  It then runs that test and asserts that it
 fails.  Then, it creates a temporary buffer and invokes
 ‘ert--print-backtrace’ to print the backtrace of the failed test to the
 current buffer.  Finally, it extracts the first line from the buffer and
 asserts that it matches what we expect.  It uses
 ‘buffer-substring-no-properties’ and ‘equal’ to ignore text properties;
 for a test that takes properties into account, ‘buffer-substring’ and
 ‘ert-equal-including-properties’ could be used instead.
 
    The reason why this test only checks the first line of the backtrace
 is that the remainder of the backtrace is dependent on ERT’s internals
 as well as whether the code is running interpreted or compiled.  By
 looking only at the first line, the test checks a useful property—that
 the backtrace correctly captures the call to ‘signal’ that results from
 the call to ‘ert-fail’—without being brittle.
 
    This example also shows that writing tests is much easier if the code
 under test was structured with testing in mind.
 
    For example, if ‘ert-run-test’ accepted only symbols that name tests
 rather than test objects, the test would need a name for the failing
 test, which would have to be a temporary symbol generated with
 ‘make-symbol’, to avoid side effects on Emacs’s state.  Choosing the
 right interface for ‘ert-run-tests’ allows the test to be simpler.
 
    Similarly, if ‘ert--print-backtrace’ printed the backtrace to a
 buffer with a fixed name rather than the current buffer, it would be
 much harder for the test to undo the side effect.  Of course, some code
 somewhere needs to pick the buffer name.  But that logic is independent
 of the logic that prints backtraces, and keeping them in separate
 functions allows us to test them independently.
 
    A lot of code that you will encounter in Emacs was not written with
 testing in mind.  Sometimes, the easiest way to write tests for such
 code is to restructure the code slightly to provide better interfaces
 for testing.  Usually, this makes the interfaces easier to use as well.