2

I was exploring TDD, specifically the Outside-In TDD pattern, where we need to write the acceptance (integration) test and then jump on to granular unit tests to implement the feature and make the acceptance test pass at the end.

This all looks good with a monolithic application where all the logic for the feature resides in a single codebase. But how do we implement an Outside-In TDD pattern with Micro-services and Micro-frontends, where a single feature can span across several Micro-services?

Consider the following scenario/feature:

  • We have 3 Micro-services and 1 Frontend
    • Micro-service[A]
    • Micro-service[B]
    • Micro-service[C]
  • Frontend contains a form that needs to be filled by the user and when the user submits the form, the details are sent to Micro-service[A] to be persisted in the database.
  • Micro-service[A] internally calls Micro-service[B] for some operations and further Micro-service[B] calls Micro-service[C] for some other operations.
  • Once these operations are done, Micro-service[A] finally persists the data in the database and then triggers an email to the user for confirmation.

How can we follow the Outside-In TDD approach in this case for this feature? Is it even possible in this case?

8
  • 1
    From the outside, there is a system with a frontend, a database and an emailing functionality. Why do you think does it matter for an acceptance test how the system is implemented internally (by microservices or as a monolith)?
    – Doc Brown
    Commented Aug 14 at 5:51
  • 1
    Obviously not - assume your test does not know how the system is decomposed into micro services, hence it does not know there are services A, B, and C. The test needs to by a process or service on its own.
    – Doc Brown
    Commented Aug 14 at 6:10
  • 1
    Moreover, I think you should decide first what kind of integration test you want here - one which tests through the frontend (with some framework like Selenium), or one which excludes the frontend and simulates the form submission by itself.
    – Doc Brown
    Commented Aug 14 at 6:16
  • 1
    "Should we have a separate Micro-service" - yes, I think so. "which would just be calling different Micro-services" - no, it does not know there are different Micro-services. The acceptance test just calls the frontend's API, and whatever happens behind the scenes internally isn't the test's concern. Additionally, you will probably want to have individual tests for individual microservices, to test them in isolation. So a test for Microservice A, which does not need B and C to be installed and running.
    – Doc Brown
    Commented Aug 14 at 8:18
  • 1
    See my answer below. I think most other answers currently missed the point that you have 3 different levels of abstraction for which you can write tests.
    – Doc Brown
    Commented Aug 14 at 8:31

5 Answers 5

4

There's not just one feature

where a single feature can span across several Micro-services?

For all intents and purposes, this isn't one feature spanning across several microservices, this is a collection of features (one per microservice, or more) which together achieve a desirable behavior.

When working with microservices, abandon every intention you have of treating work as if it spans across several microservices. Doing so inherently infringes on the independent nature of microservices.

The short answer here is that when you consider features in scope of a specific microservice, not a collection of microservices; then you effectively have the same situation as you have for a monolith, which you indicated you're already familiar with.

After all, a microservice is really just a miniature monolith (in a field of other miniature monoliths).

While you can definitely write an integration test that spans across multiple microservices in order to check if they interact correctly with one another, TDD guidelines generally focus on a per-codebase scope, not that of an entire ecosystem of codebases and how to manage expectations with different owners, tech stacks or development schedules.
Feel free to write that service-spanning test, but accept that it's probably not going to tell you much until you really know how that test should consume all of its constituent components.


How to structure the work for these separate features?

This is where the next part of the advice can diverge: do you want the theoretically elegant approach, or the more complex but realistic example?

Theoretically, if microservice B depends on something in microservice A that doesn't exist yet, then you should develop A's feature and release it before starting work on B's feature.

This is the simplest way to structure the work. First build the core, and then build the things that depend on it. It's simple. It will probably take the least amount of overall manhours to develop. But you lose out on the ability to do things concurrently, which might cost a bit more manhours but does mean that you can deliver the whole package at an earlier calendar date. Whether you value total hours works, or calendar date of delivery, depends on your company.

If you want to develop these concurrently, A and B's developers have to sit down with one another and agree on the interface (i.e. A's API), expressing the endpoints and models used in those endpoints.

Once you've established the service interface; A's development can start (like it would have even in the first scenario), and B's development is able to already start, as it can be built against a mocked version of A's API.

If you accurately agreed on a working interface, then at the end of the process you should be able to swap out the mocked API for A's real API. Obviously, there's a decent chance that some things have changed since then, but that's the nature of trying to build against A's API before the real thing exists. This is why I said that it probably is going to take more overall manhours, as you both have to spend time building a mocked version of A's API and you increase the odds of needing to revisit A's interface based on things you realize during the development process.

7
  • So, basically, if the feature that I am talking about is mainly connecting with Micro-service[A] for its functioning, then Micro-service[A] connects with B, & C, then I need to write acceptance test only for A? Is that correct? Commented Aug 14 at 7:32
  • @JigneshM.Khatri: What tests to write is a really large topic. For the purpose of TDD, the kinds of tests it prescribes are scoped to individual services, not some service-spanning integration test. According to TDD, any service should have have a test suite backing it - exactly like you would for a monolith. This is no different for individual microservices. That's not to say you can't write such service-spanning integration tests on top of it all, it's just not what TDD particularly prescribes.
    – Flater
    Commented Aug 14 at 8:41
  • So here is the another case: Lets say we have User and Department microservices. Whenever new user is created, he/she is also assigned to a department. So user microservice internally calls the department microservice to assign the user to a department. So, for this user save feature, should we write acceptance test in user microservice and consider dept microservice in this acceptance test? Ofcource unit test cases will be different for different functions. Commented Aug 14 at 11:08
  • 1
    @JigneshM.Khatri: The question you're asking suggests you've not really taken in the information already provided in the answer and my comment. Rather than rephrase the same question over, revisit the answer and look for mentions of the structuring of tests as it relates to TDD. You already know the answer, since every microservice's test suite is effectively identical to how tests are structured for a monolith. You can think of every microservices as a (small) monolith for the purpose of your question.
    – Flater
    Commented Aug 14 at 14:49
  • I just have posted an answer on this thread with architecture diagram depicting these microservices with test cases. Do you think that is a correct implementation of Outside-In TDD pattern with Microservices? Commented Aug 14 at 19:00
2

Your case will allow it to write outside-in tests on three different levels (at minimum):

  • outside tests for the "system as a whole" - which are real integration tests, showing the different micro services work together as intended. Such a test isn't part of any other micro service, it might be a program a service on its own. It does not need need to know how the system is structured internally, whether it is based on microservices or designed as a monolith.

  • outside tests for each micro service on it's own. For this, you need to design, for example, micro service A in a way it can work without services B and C (maybe by providing "mock services" for B and C, maybe by providing some testing mode directly into A, whatever you think will be most economically feasible).

  • granular unit test's following TDD when developing each micro service

If you really need automated tests on each of these hierarchy level is up to you, but in case you have different teams for each micro service and also want your integration process to be supported by automated tests, then this can definitely make sense.

2

Your top level acceptance test just needs a stable API to act through. It doesn't matter how many micro-services are needed to support it. What matters is, as you flesh out the micro-services, you don't mess with that API. Least ways, not without updating the acceptance test if you do.

At this level it really doesn't mater if your app uses micro-services, monolith, or spaghetti code. What matters is if the code is testable.

You can also write tests focused at the micro-service level. These prove your micro-service does what you think it does. Yes, you get some testing overlap. That's fine.

You can keep drilling down with more overlapping tests that are more and more isolated. That's outside in.

The key thing to do when throwing this much testing at a problem is to make clear why each test exists. Document it's audience and it's subject. That way when it's outdated it will get deleted rather than drug around by clueless coders who don't know what it's for and are too afraid to kill it.

0

The main point of microservices is that they are separate. A microservice has some API, and there are consumers of that API. If another service needs some new API they make a feature request, you implement that using whatever development pattern you prefer, and release a new version of your service. So the Outside-In TDD pattern would only apply to a single service at a time.

If a new feature requires changes in many services you need to sit down with the teams responsible for each service and figure out how it should all work, and decide on exactly the API changes needed.

If you make mistakes in the design you may need more meetings to make more API change requests. This is by design, microservices make changes within a single service cheaper and simpler by making the APIs more rigid, and more resistant to change. If you are not sure what changes a new feature will require you may want to do some prototyping, i.e. build a monolith to gain a better understanding of the problem, and throw it away.

If you frequently need to modify multiple services to provide a new user facing feature you might want to consider if the boundaries between services are appropriate.

-2

As per all other answers on this post, here is the summarized version of how we can implement the Outside-In TDD for micro-services, where one micro-service is dependent on another to complete any feature.

Below is the high-level architecture diagram to implement a solution with an Outside-In TDD pattern for the user creation feature, where the user micro-service is dependent on the department micro-service to assign the user to the department: Image depicting high-level architecture diagram for TDD with Microservices

The implementation starts with the acceptance test case writing for the feature (user creation) as marked with step "1" in the above diagram. Subsequent steps are marked with respective step numbers.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.