Unit Tests as a Learning Tool
Unit testing and patterns like TDD are enabled by tools that create small, isolated execution contexts for your code, allow you to make assertions about data and behavior, and allow you to substitute dependencies with mocks.
These capabilities are quite powerful for testing your code, as one would expect. The same collection of capabilities also creates a fantastic environment for experimenting with code. When I want to learn a new library, I will take the typical paths of reading the documents and looking through the code and then experimenting with the code. When I’m focused on language features, utilities, data structures, and the like, a toy app isn’t always the easiest way to experiment. In these cases, I reach for testing tools as a learning environment.
With a project that is primarily configured with a test runner, assertion library, and mocking tools, I proceed to create “tests” in order to execute the code I’m curious about.
I’m not really testing the code, but I am using the testing tools so I can create small, focused, one-off examples with minimal setup. With this, I gain watch mode, readable output, an easy way to describe each example, and a way to organize many examples.
For example, when I wanted to learn ADTs (algebraic data types) with the crocks library, I created a project full of unit tests. Instead of building out a project that resembled an app and finding ways to force all of the types and their utilities into that project, I just installed and configured Jest. With my “test” project setup, I was able to just write a bunch of sample code. I relied on Jest to structure, organize, and execute that code. I was able to work my way through the docs, creating “tests” for each thing I wanted to see in action or solidify my understanding of. I was able to move much faster through the mechanics in this context than I would have been able to if I was also building a UI or server endpoints to find places for each use case I wanted to understand.
Having a project like this is nice for a few reasons. The organization and focused nature of each example means if I’ve stepped away from something for a while, I can use the code as living notes and get back up to speed quickly when I come back to it. The mix of descriptions, code, and comments makes each concept readily available for review. Because this is executable code, I can also verify that what I’m reading and trying to internalize is in-fact valid, if the tests pass, the code yields the expected results. The pass/fail nature of tests instills an added level of confidence in what I’m looking at. The same can’t be said for the comments, but I also find it helpful to attempt to explain what the code is doing. The comments are subject to change and end up being refined as my understanding changes. With the comments, I can pose questions, then attempt to explain the outcome of an experiment. When I need a refresher down the road, I can use those to recreate the journey I took when learning. If in that review I have a light bulb moment, I can update the notes or expand on them.
Of course, this doesn’t give you same experience as using something in a real-world setting, but this is just one of many tools you can reach for and a single step in the journey of learning something new.
When you’re really looking to move fast, you can write unit tests right in CodeSandbox (though, mocks won’t work there)!