GitHub issues and Emacs Lisp unit tests

In my current project I spent a significant amount of time reviewing other people's code. Progress on those pull requests is not always continuous and it is easy to lose track of those that require my attention. To avoid that, I keep a list of these pull requests in a separate org-mode file in Emacs. Then, with the right key press(es), my Agenda shows me the ones I need to have a(nother) look at.

When I add a pull request to the org-mode file, I also copy the title and URL from the GitHub website. This proved to be cumbersome and error-prone, so I wrote a small Lisp package to retrieve that information automatically. Read on for details on its development and the use of automated tests in Emacs Lisp.

You can find the code of the aforementioned Lisp package in my github-query repo at Bitbucket.

General idea

I wanted a function that asks for an issue number and automatically retrieves the title and the URL of that issue [1]. This proved to be relatively easy to do as

  1. GitHub can be accessed through a Web API that is nicely documented.
  2. Emacs comes with the url package that you can use, among others, to post requests over http and https, and receive responses.

Of course, the devil is in the details and it took me some time to develop the following function:

(defun github-query-get-issue(owner repo issue-number)
  "Return issue ISSUE-NUMBER of GitHub repo OWNER/REPO.

This function returns the response as an association list, but
you can also use `github-query-get-attribute' for that."

The following snippet shows how you can use that function to retrieve the url of issue 1 from the GitHub repo bbatsov/projectile:

(let ((response (github-query-get-issue "bbatsov" "projectile" 1)))
  (let ((url (github-query-get-attribute 'html_url response)))

The nicest thing was that I used automated unit tests during the development of the package, something that I had never done for Lisp code [2].

Automated tests in Emacs Lisp

Emacs comes standard with the library ert, "a tool for automated testing in Emacs Lisp". That library provides macro "ert-deftest" that allows you to define a test as an ordinary functions. It also provides you with several test assertions such as "should" and "should-not". The following test from the github-query shows how it can be used:

(ert-deftest github-query--get-issue-retrieves-correct-response()
  (let ((response (github-query-get-issue "bbatsov" "projectile" 1)))
    (should (equal 1 (github-query-get-attribute 'number response)))
    (should (equal "Obey .gitignore, .bzrignore, etc."
                (github-query-get-attribute 'title response)))
    (should (equal ""
                (github-query-get-attribute 'html_url response)))))

Being able to run automated tests helped me enormously. Without them I find it easy to end up in a spot where I am thinking "but this was working before, or wasn't it?" [3], especially with the interactivity that the REPL provides.

I did have some minor issues with ert. There are two modes in which you can run tests, viz.

  • in interactive mode, where you execute the tests in the current Emacs process, and
  • in batch-mode, where you start a new Emacs that runs your tests and exits.

Working in interactive mode means that you always have to explicitly reload the code under test when you have changed it. Fail to do so and ert uses the code as it was during the previous run. To avoid that explicit reload, I use ert in batch-mode. Then I know for sure that all code under test is (re)loaded. Unfortunately in batch-mode it is more difficult to specify which tests to run. There are things that you can only do in interactive mode, for example only run the tests that failed during the last run.


Well, ert is here to stay for me. I cannot imagine doing Emacs Lisp development without automated tests anymore (not that I develop a lot of Emacs Lisp code :).

I do find ert a bit cumbersome to use in batch-mode, but on the positive side, it forces me to have quick unit tests. There is another test runner for ert, ert-runner, that seems to make it easier to specify which tests to run in batch-mode so. I will have a look at that one.

[1] In my current project, I am part of a team that works in a single (private) repo.
[2] Well, not really the first time. I once worked on a Lisp package to run all functions in an Emacs Lisp file whose name started with test_ . I bootstrapped that functionality during its development. However, that code was neither completed nor published.
[3] For me this is not limited to Emacs Lisp, it holds for any programming language.


Comments powered by Disqus