The definition of a unit test that I hear the most often is that it is “the smallest unit of behavior that can be tested”. Usually, that’s a single function or public methods on a instance of a class.

The problem with that definition is that it is limited by the word smallest. When writing a unit test, you shouldn’t be asking “Is this the smallest unit I can test?”. You might end up with test confirming that language specific operators work as expected if you keep writing smaller and smaller tests or artificially dividing up logic to be tested until it’s scattered and unreadable.

The other limiting part of the definition is that it makes the assumption that some things can be tested and some things cannot. Almost everything CAN be tested, the question is how much effort you’re willing to put forth into testing it.

If remove the problematic parts of the standard definition, a unit test is simply a test on a unit of behavior. Size shouldn’t be a factor. How much time you spend creating the test should be tied to the expected value of the test. The unit of behavior should be isolated and be repeatable and well defined. The unit of behavior is the most important part of the definition, if it isn’t well-defined, you may have a flakey test.

Different Kinds of Tests

There are different tests out there, your framework or organization is likely the go to for definitions. It’s all going to be slightly different so don’t get too hung up on the jargon. Think of the tests as unit tests and try the unit of behavior for each test as the thing you care about.

Pure Unit Tests

These are small pieces of code. This might be tests on utility functions. The unit of behavior that you are testing is usually code and function calls from other functions. The tests and the code should be uncomplicated. If you have to mock a large amount of other services to isolate the logic, you may want to refactor code so that the logic is testable with out mocks.

Keep your thoughts on value when adding these tests to a code base. It’s easy to write unit tests for the sake of unit tests and end up with a collection of low value tests. Someone will likely have to maintain those later, when refactoring, someone will have to read through tests to decide if they need migrating with other code changes. I’ll go over my process in deciding if a tests is valuable in a later entry. Know if the test is preventing a bug, cementing developer intent, locking out other developers from making changes etc..

Integration Tests

These are still unit test if you’re doing them right. They focus on how parts of the code integrate with each other. It might be looking at how code integrates in a framework.

The unit of behavior might be something that the framework is supposed to do, like render html for webpage. The behavior your testing could come from the user or the framework. The behavior your testing should still be isolated but the code you’ve written is no longer running in complete isolation.

Acceptance Tests

These are tests that are able to be tied back to organizational expectations of how a feature should work.

Keep these as unit tests by making sure each unit tests has a focus on the results of a single behavior.

Selenium Tests

This is a web app concern, but these tests run a browser and simulate user interaction.

The unit of behavior is focused on the results of end users actions.

Smoke tests

Where there is smoke there is fire. These are a select group of robust tests that can be run against the code to quickly confirm that the code works as expected. The could be selenium tests, integration tests, test that ping your api.

You probably should be able to run these tests against a live production environment. They could be unit tests but what they are testing is the developer behavior. The question they may be asking is “Did the developer write and launch a functional app?”.

Exploratory testing

If you haven’t tried exploratory testing legacy code, you really should. You can use tests to drive your understanding of the code, isolate functions. You use your test framework to understand the the code faster. If you’ve set up your test framework right, you’ll likely be able to create a faster feed back loop. It’s a great tool to have, but you shouldn’t expect to keep the tests you write. It’s not unit testing, it’s debugging. Tests can lock in structure and behavior, and if you’re tests were written before truly understanding the code are likely low value. Stop and rethink things before checking in any exploratory tests. Ask yourself if they are worth the time to maintain, what behavior they are testing and if that is a be.