I originally come from a Python background but I have been doing mostly TypeScript for the last couple of years in my day job.

One of the things that I really enjoy about the JS/TS ecosystem is jest. I found it to be a really nice way of writing and running tests. A very small detail that I always enjoyed when coming from pytest was that you pass the test name as the first argument to your test function. In jest, you might for example write a test like this:

describe("capitalize", () => {
  it("should capitalise a string", () => {
    expect(capitalize("hello")).toEqual("Hello") 
  })
})

Running the tests gives us this output:

 PASS  ./main.test.js
  capitalize
    ✓ should capitalise a string (1 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.165 s

In Python’s unittest, tests are discovered by being methods in a class that extends unittest.TestCase and starting with test as per the documentation.

import unittest

class CapitalizeTests(unittest.TestCase):
  def test_should_capitalise_a_string(self):
    self.assertEqual(capitalize("hello"), "Hello")

Running the tests gives us this output:

test_should_capitalise_a_string (__main__.CapitalizeTests) ... ok

----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

I think that one of the main jobs of a test is to communicate expected behaviour to future readers. One way to achieve this is to use good test names. To me, it always felt a big awkward to use snake_cased_test_names to communicate what I wanted my tests to cover in Python. I just recently learned via stackoverflow that unittest will take into account the methods’ docstrings and display those as method names. I think that this is a nice compromise and lets us communicate more naturally.

This is how you can add more explicit test descriptions as prose via docstrings:

class CapitalizeTests(unittest.TestCase):
  def test_should_capitalise_a_string(self):
    """ Should capitalise the string """
    self.assertEqual(capitalize("hello"), "Hello")

  def test_should_handle_none(self):
    """ Should return an empty string if None is given"""
    self.assertEqual(capitalize(None), "")

Running the tests now gives us this output:

test_should_capitalise_a_string (__main__.CapitalizeTests)
Should capitalise the string ... ok
test_should_handle_none (__main__.CapitalizeTests)
Should return an empty string if None is given ... ERROR

======================================================================
ERROR: test_should_handle_none (__main__.CapitalizeTests)
Should return an empty string if None is given
----------------------------------------------------------------------
Traceback (most recent call last):
  <...abbreviated>
----------------------------------------------------------------------
Ran 2 tests in 0.000s

FAILED (errors=1)

I think this is nice as it might give future readers some more context for what behavior a test expected to cover.

As an additional benefit, those test names are also nicely displayed in PyCharm, my Python IDE of choice: A screenshot of PyCharm showing the test names in the test summary

I still think that jest’s way is a bit more straightforward to use but I quite like this as a small improvement to the Python test that I write.

So there you go, happy testing!