Code testing strategies lead by examples

Saeid Saeidee
Insider Engineering
5 min readJan 21, 2021
image by ivanhoe.pro

When we are thinking about maintainable and bugless projects, the first thing which is coming to our mind is writing Tests.

When we say test we can see that there are lots of test types including Unit Test, Integration Test, UI Test, API Test, System Test, and many more.

So the question is which of these mentioned test types we need for our system, how to use them, and how to level them?

To answer this question we can use the Test Pyramid. It is a strategy to choose which type of testing will be good and fit for our application.

Basic Test Pyramid

Unit Tests are at the bottom. It has the maximum space in the pyramid and, within these tests, we are testing each unit of our codebase.

Integration Tests are in the middle and these are the tests that verify the integration of different components of the system together like filesystems, database, and 3rd party services.

E2E Tests are at the top, these tests are for the user interactions from the entry point of the system up to the ending of user interaction. End to end workflows of the codebase.

According to the image above, moving from the bottom to the top, our tests will be much slower and difficult, and moving from top to the bottom our tests will be much faster and easier to write. So that's why we need to have many lower-level tests other than higher-level tests.

And there is a famous anti-pattern which is called the Ice-cream cone. It has more high-level tests that are causing slowness and costly tests.

Ice-cream cone

After understanding the test pyramid, we need to avoid writing duplicated tests in each layer. To avoid such a case we need to consider these rules below;

  1. If a higher-level test spots an error and there is no lower-level test failing, you need to write a lower-level test.

2. Push your tests as far down to the test pyramid as you can.

In addition to these rules, we need to be sure that the higher-level test focuses on the part that the lower-level tests couldn’t cover. An example can be writing tests for controllers.

Should we write a Unit Test or Integration Test?

Writing Unit tests help to test the logic within the Controller itself. Still, this won’t tell you whether the REST endpoint which this Controller provides is responding to HTTP requests or not. So we need to add a test that checks exactly that, but nothing more.

After understanding brief details about tests, let's start by writing some real tests.

Unit Tests

We will use the Laravel framework and PHPUnit for our examples.

Let’s say we have a task table with the following columns.

Task Migration

We will create the Model as well.

Task Model

We will use the Repository Pattern for our database layer so we will create our Task Repository and its related Interface.

TaskRepository

And now we will create our Controller,

TaskController

So this was our example codebase (We didn’t include all of the methods here, please check the Github repository at the very bottom to check all the codebase). Let’s start writing our tests for TaskRepository.

TaskRepositoryTest

Let’s run the tests and check if it passes or not.

TaskRepositoryTest Coverage

All tests are passed and as well as it has 100% code coverage (for checking test coverage we are using PCOV over Xdebug which has more performance).

Now after we know how to write Unit Tests we can start writing Integration Tests.

We will start by writing the Integration Test for our Controllers.

TaskControllerTest

For each case that had a database operation, we included the database assert as well, of course, we can write our own Integration Test for Repositories to be sure everything works fine in the database layer but we can also check them like this.

TaskControllerTest Result

Now let’s run all of our tests.

All tests passed successfully.

All tests are successfully passing and working perfectly together.

Conclusion

These are the basic strategies we follow at Insider. Understanding the purpose and usage of different testing types, lets you understand the idea of “why we need” them. Writing tests also help you to write cleaner code and more maintainable projects in addition to reducing the risk of creating a bug. Also, makes you feel safe at the end of the day.

Please check the Github repository https://github.com/saeidee/cube to see how we implement the tests for Models, Requests, Resources, and other classes.

--

--