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:
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!