tiffi
tiffi

Reputation: 683

Write test to check for runtimeerrors

I am writing tests for an interpreter that panics for a number of syntactically valid input. This is a minimal example of how I do it:

package minimalexample

import (
    "testing"
)

func access(i int) string {
    return []string{"a", "b"}[i]
}

func TestAccess(t *testing.T) {
    tests := []struct {
        testdatum int
        expected  string
    }{
        {0, "a"},
        {2, ""},
        {1, "c"},
        {4, ""},
    }

    for _, tt := range tests {
        testAccess(tt, t)
    }
}

func testAccess(tt struct {
    testdatum int
    expected  string
}, t *testing.T) {

    defer func() {
        if err := recover(); err != nil {
        t.Errorf("Runtime error for testdatum %v: %q", tt.testdatum, err)

        }
    }()
    result := access(tt.testdatum)
    if result != tt.expected {
        t.Errorf("%v evaluates to %q, though it should be %q", tt.testdatum, result, tt.expected)
    }

}

The idea to handle panic is taken from https://golang.org/doc/effective_go#recover, however I do not know whether it is good practice to use it in tests like I do in the above example or there is some reason why it is to be preferred to just let the test fail until the runtime error is fixed.

The advantage of this setting is that I see what happens for all testcases, even if one of then causes panic. The reason that I am still hesitant to say that is a good solution is that I am unsure whether it is the tester's job to care about runtime errors or whether that should be dealt with by the developer that has to fix the code to make the tests pass. Another reason is that after running a bunch of these tests, I get a msg from vscode that I don't understand but suspect to have something to do with the exhaustive deferring: "Running the contributed command: '_vscode_delegate_cmd_kms3v2s2' failed."

Upvotes: 0

Views: 686

Answers (1)

Hymns For Disco
Hymns For Disco

Reputation: 8395

I don't think there's anything wrong with recovering panic in a test to catch it more cleanly, except that it is possibly indicative of a poor design pattern where you (or the package author) are using panic for normal error handling.

See https://go-proverbs.github.io/

Don't panic.

This isn't an actual rule, but let's invent a new proverb to illustrate a point:

10 errors for every panic, 10 panics for every recover.

(looking at the GitHub search results we'll see that this is vaguely accurate)

The point being panics should be used sparingly, as often an error is more appropriate. And recover should be used ever more sparingly, because panic means something went so wrong that it's unlikely there's any way to fix it.

As a result, you should never use a panic with the intent that it should be recovered under typical use.

Again this is not a rule, it is just my philosophy about error handling in Go


panics should generally be reserved for particular circumstances. There could be more, but here are the main 2 in my view.

  1. There is something statically wrong with the program, i.e. the programmer is using the function outside of it's normal, safe usage. For example:
  • Tried to access outside of the array bounds
  • Tried to use a nil pointer
  1. There is something really unexpectedly wrong with the environment conditions and the program can't continue.

The main, legitimate use of recover is for process isolation. For example, with the standard library HTTP server, if the goroutine handling an HTTP request panics, the server will recover the panic in order to let that request crash alone, and allow the server process to keep running.

Upvotes: 1

Related Questions