To ensure a thoroughly tested code base, we must engage in different types of testing. As touched on already, the unit test enables us to test isolated parts, while the various combinations of parts can be tested via either integration, functional, or E2E tests. It's useful first to understand what we mean when we talk about a part or a unit.
When we talk about a unit of code, there is admittedly a fuzziness to the concept. Typically, it will be a piece of code that has a singular responsibility within a system. When a user wishes to perform an action via our software, they will, in fact, be activating a series of parts of our code, all working together to give the user the output they desire. Consider an app in which users can create and share images. A typical user experience (a flow or journey) may involve a few distinct steps that all involve different parts of the code base. Every action the User performs, often without them knowing, will encapsulate a series of code actions:
- (User) Create a new image by uploading a photo stored on the desktop:
- (Code) Upload the photo via <form>
- (Code) Save photo to a CDN
- (Code) Show the bitmap within <canvas> so that filters can be applied
- (User) Apply a filter to the image:
- (Code) Apply the filter via <canvas> pixel manipulation
- (Code) Update image stored on the CDN
- (Code) Re-download saved image
- (User) Share the image with friends:
- (Code) Find the user's friends in the database
- (Code) Add the image to each friend's feed
- (Code) Send the push notification to all friends
Together, all of these steps, combined with all other steps a user could potentially take, can be considered a system. And a fully-tested system might involve unit tests for each individual step, integration tests for each pair of steps, and functional or End-to-End (E2E) tests for every combination of steps that together form a user flow or user journey. We can visualize the types of tests that may need to exist as part of a system as follows:
Here, we can see one Start point and two End points, indicating two distinct user journeys. Each dot can be thought of as a single area of responsibility or unit that is activated as part of these journeys. As you can see, a unit test is only concerned with a single area of responsibility. The integration test is concerned with two (or more) neighboring areas that integrate. And an E2E or functional test is concerned with all of the areas involved in a singular user journey. In the former example of our image-sharing app, we can imagine that we may have specific unit tests for actions such as uploading a photo to the CDN or sending push notifications, an integration test that tests the integration of the friends database, and an E2E test that tests the entire flow from creating to sharing a new image. Each of these testing methodologies would be vital in ensuring a truly well-tested system, and each has its own unique benefits as well as pitfalls and challenges to overcome.