My vision about outside-in testing as a Backend Developer

Jponzvan
Mercadona Tech
Published in
7 min readJan 26, 2022

Introduction

Hi everyone, I am Juanjo and I am working as a senior backend developer at Mercadona tech, currently in the Supply team. I’m here to tell you about my vision of outside-inside testing, and how I use this type of testing to build solutions. The examples I will use are based on Django, but you don't need to know this framework to follow the article. Also, in case you are a front-end developer, my colleague Alex Torres wrote a fantastic article about how they use this same approach on the front-end.

What is outside-in testing, and how do I see it

This type of testing tests the code from it’s highest entry point. From my point of view, it is the interaction of the user with our system. If our system is a blog, each of the actions that the user can perform — that call an endpoint (shared maybe in some cases)- are each of the entry points to my system:

  • Create an entry
  • Add a comment
  • List entries

And how do we develop according to this flow? This kind of test help us to make a decoupled architecture; step by step of doing this (being backend) would be:

  • test the view for each status_code that can be returned asserting its responses and mocking the action
  • test the action and mock the dependencies
  • test each dependency we have

It doesn’t mean that we can’t do this kind of test if we entirely use a framework like Django. We could make only the first point without mocking anything else.

Why do we only test the first level of the application layer? We are testing the flow from the input to our system. So, if that test works, all the internal pieces of our code will work, like a puzzle! Actually, these tests are like assembling a puzzle, you have the big picture next to it (the test), and you are creating and assembling the pieces that will make up the puzzle using only the big picture of the test because it is the final result that user expects… it seems easy 😂😂, and we can check that all the code works as expected. We haven’t got a typical problem that we all have faced.

By using outside-in, we decrease the number of test files to maintain and the number of test files.

But, all that glitters is not gold:

  • the arrange of the tests are becoming more and more complicated
  • the number of tests per suite tends to grow each time we extend it
  • you handle more complexity in your mind since you are focused on the final solution of an iteration

In Mercadona Tech, most of the work we do in the backend is for internal clients, so we trust that clients’ requests comply with the agreed contract. That is why we delegate the data validations to Django and Django REST Framework (DRF). In the case of developing public APIs, the game’s rules would change, and the tests would change as well.

Each problem has its own tool.

A brief explanation about architecture

We try to use a hexagonal architecture using IDD as a driver (this is the Sandro Mancuso post about it), so our views call an action, where all the business logic is performed and then serialized in the view window itself to return the response. By doing this, we have a clear separation between what our service should do and what we delegate to the framework. This is very useful when we want to extend our service or when we want to solve any of the incidents reported by our stakeholders. Another advantage of this type of architecture is the ease and flexibility it gives you when you need to divide your system into two or more systems to, for example, move to events (event-driven design) and services.

This image belongs to a Sandro Mancuso’s post

Let’s see a practical example

Let’s suppose that Product creates the US in which it requests that a manager can create a user that can be assigned a position or job title, storing the creation date. Once the registration is completed, an email will be sent to boss@company.com with all this data. To achieve this, we will create the following endpoint:

POST https://some_cool_internal_domain.extension/api/workers/register/data = { "name": some_name, "title": "some_job_title"}

I will show you the tests I would write but not the business logic; we will leave that for another time. Stay tuned! 😆

⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️

Warning Warning!!!: In the article, we use IDD as a driver to show you the tests, but we don’t perform the repository pattern for database access. We make use of the Django ORM also in the tests. If Martin Fowler, Sandro Mancuso, the codely guys, or any DDD or IDD expert read this article, remember that this is a testing article, and forgive me for using an active record!

⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️

Tests, tests, tests!!!!!

The following examples show how tests would be done starting from a traditional Django-rest application and how it would evolve to an IDD architecture.

In Django way

With this approach, we only have one file; in an application tested traditionally, we would have these files:

  • Mandatory View test
  • Mandatory Serializer test
  • Optional model test, depending on where we have put the date injection
  • Optional module/class/public function test, depending on how we have implemented the email sent.

Can I show you my emotion?

  • from almost 2 files to 1, knowing what the user is going to do
  • I go from green to green when refactoring without changing the “more internal” tests (they do not exist!)
  • The documentation of what my application can do is in the tests themselves.
  • I let the code work from the beginning (the call) to the end of its life, seeing all the possible side effects that it can have.
  • Regression tests are caused by the users, not by the implementation.

This can be maintained for a long time, even for the entire life of the project itself. You are to pay for the request/response lifecycle time along with the whole framework.

Evolving tests towards Interaction Driven Design

If you decide to evolve to IDD, the views test will look like this:

As we can see, we will only check in the view what we will return after the client’s request. There are two paths at this point: one is mocking the action/service, and the other is letting the code work, as I have shown you in the previous example.

And then, we have the test_create_worker.py with the business logic tests:

In these tests, we see some exciting things… As in the view tests, we only have to assert the status code and the expected returned values. We will see a better performance in the execution of the tests.

All the possible interactions between the views and the actions, we test them in the actions.

What about external clients? In this scenario, I prefer to exploit the advantages of python; the classic way of testing would be to mock the MailClient and test it unitary. I like to let the code run and, at most, mock the most internal function of the external library (I would configure the django backend email to the console in tests).

And that’s it. In my opinion, we have all the necessary coverage to ensure a great solution. With this approach and using TDD, you can quickly get a more decoupled architecture, with all the benefits (and disadvantages) that this brings us.

Conclusion

Despite having some points against, I see a lot of clarity when reading these kinds of tests, and it helps me to extend the programs in a much more agile way because I see the big picture, and not just a fragment, which is the feeling I have when reading unit tests.

--

--