Good test, bad test

Good Test, Bad Test is an insightful article. The article has opinions that are sure to be polarizing, but the reasoning is spot on. For example, doing this:

    self.assertEqual(
        datetime(2015, 3, 11, 20, 9, 25),
        parsed_dict["date"],
    )
    self.assertEqual("GET", parsed_dict["method"])
    self.assertEqual("/foo", parsed_dict["path"])
    self.assertEqual("bar=baz", parsed_dict["query"])

instead of this:

    self.assertEqual({
        "date": datetime(2015, 3, 11, 20, 9, 25),
        "method": "GET",
        "path": "/foo",
        "query": "bar=baz",
    }, parsed_dict)

The former is more code, but the useful error messages pay dividends if this test fails. You'll thank yourself when you spend a day refactoring and chasing test failures.

Also, there are quite a few quotes to remember when looking through your test code:

  1. Whenever you write a test, you have to consider: what would cause this test case to pass when it ought to fail?
  2. It's important to acknowledge that tests can smell just as well as real code. We deal with code smell in a test suite the same as we would in any other type of code -- with thought and care.

How to write reusable code

Do yourself a favor and spend 30 minutes watching this great talk on writing reusable code.

Improved test name printing for nosetests

I reach for pytest first when setting up a new project. However, I still have some old projects using the nose framework. Nose is great, but pytest provides better output and shorter syntax for testing, command-line options, etc. I don't want to convert my old projects over because nose is still useful except for one nagging issue. The syntax for running a single test from the command-line is frustrating.

For example, say I have a bunch of tests in a file located at tests/test_external_apps.py, but I only want to run the one called testLaunch. That test is written with Python's unittest framework, and I use nose to run it. The default syntax requires the full path to the test module and then additional syntax using a colon to denote the class. So, calling my single test would look like this tests.test_external_apps.TestLaunchExternalApps:testLaunch.

That is a lot to type. To make matters worse the default output of a test failure with nose doesn't use this syntax:

======================================================================
ERROR: testLaunch (tests.test_external_apps.TestLaunchExternalApps)
----------------------------------------------------------------------

So, you can't easily copy and paste the output to re-run the failed test. Yes, there is an option, --failed, for re-running the previously failed tests. This is great, but often times I might have a bunch of test failures in a single run. Then, I want to run each test individually and fix them one at a time.

Plugins to the rescue

It would be much better if the output of a failure at least printed the test information in the syntax needed by the command-line. Luckily, a plugin exists that does this called nose_runnable_test_names. You can install it with the standard pip install nose_runnable_test_names routine and then use the new command-line argument --with-runnable-test-names. The new output is easy to copy and paste:

======================================================================
ERROR: tests.test_external_apps:TestLaunchExternalApps.testLaunch
----------------------------------------------------------------------

What a great example of the Unix philosophy.

GitHub – durden

Luke Lee

Dresden, Germany

Over the last 10 years, Luke Lee has professionally written software for applications ranging from ...