Testing Behavior, Not Implementation: Building Better Software Faster

Focusing on behavior-driven testing ensures your software meets requirements while saving time and effort. This approach eliminates unnecessary comple...

  • Home
  • Blog
  • Testing Behavior, Not Implemen...

Testing Behavior, Not Implementation: Building Better Software Faster

image

Focusing on behavior-driven testing ensures your software meets requirements while saving time and effort. This approach eliminates unnecessary complexity, reduces maintenance overhead, and creates more reliable outcomes. Discover how shifting from implementation-based tests to behavior-driven testing can streamline workflows and improve product quality.

The last couple of years we spent a lot of time with writing and also practicing how to write good unit/integration tests in AEM (Adobe Experience Manager). Now I would like to share with you what I have learned so far. What I have learned is not only AEM related, you can apply it to any programming language or framework.

Before that time I had some "experience" with unit testing. I wrote several unit tests so that I can say "I have experience with it". But to be honest I didn't like to write it.

Even though I knew what benefits tests bring to the product that we are building, what brings to the team members and to me, I didn't care. Typical excuses were:

  • "I don't have time or we don't have time for it"
  • "I can't test or mock that"
  • "You can't write the test, pick up some new feature, or fix a bug"

and in the end, nobody pushed me to write it. Sadly, writing tests wasn't part of the development process.

Now when I'm thinking a little bit, I don't know how to write tests. Let's face it, writing tests is not easy, like many other things when you don't have experience with it.

Luckily that kind of thing has changed at some point, and I would like to try to convince all of you who are still thinking like "old" me.

I would like that we all to start thinking more about Quality and not Quantity.

Writing tests as part of the development process

Most of us work in an "Agile" way (whatever that means) and use DDD (Deadline-driven development) methodology (I could write about that in a separate post). This usually means that there is no time for writing tests. This needs to be changed, developers and all other technical team members should convince all other team members that writing tests should be part of the development process. Writing tests should be part of any estimation. Period.

Why?

There are a lot of benefits, but I will point out the most important of them:

  • Bugs prevention
  • Better code quality
  • Provides some kind of documentation
  • Time-saving
  • Money saving
  • Feeling "safe"

Now let's see typical "disadvantages":

  • Time-consuming
  • Money consuming
  • Tests are slow to write
  • Tests are slow to run
  • Changing implementation requires changing tests

Probably you have noticed that I have mentioned time and money as advantages and disadvantagess. Depending on if you are thinking in the short term, then yes it's a waist of time and money, but If you think in the long term then it's not true. Actually in the end it saves your time and money.

A lot of people think that they are a waste of time and money. I think because they don't see some visual outcome of them, like how they see it when you build some feature. Try to think like this, with tests you can prevent a lot of bugs and a lot of ping pongs between Developers and QAs. Very often we have change requests during development and it happens that we implemented something in the wrong way. The new request just doesn't fit anymore to the existing implementation. That means we need to refactor our old code or reimplement it from scratch. Here tests provides you a safe feeling because you know if you broke behavior or not. Another example could be big, never-ending projects where several different teams have worked before you. Probably that project has poorly written documentation, you need to deal with legacy code and implement new features on top of it. Having tests is gold here. Also, a lot of projects starts like MVP which turns out to some core/base projects with several subprojects. Not having test coverage here is total nonsense.

The last 3 disadvantages are also not true.

  • Tests are slow to write. Yes, If you don't know how to write it and if you don't have experience. So practice.
  • Tests are slow to run. Again yes, if you don't know how to write it.
  • Changing implementation requires changing tests. Yes, because you are testing the wrong things. Test behavior, not implementation

You don't believe me? Take 1 hour of your time and watch the talk "TDD, Where Did It All Go Wrong" by Ian Cooper. For me, this was an eye opener. Before this talk, I read a few books about testing and I was not so convinced. In my opinion, this is definitely the best talk about it.

TL; DR; of the talk:

  • Test behavior/requirements, not implementation. With this kind of approach, you will eliminate the previously mentioned disadvantages
  • Test the public API of a module, not classes, methods, or technical details
  • Unit tests shouldn't be focused on classes or methods they should be focused on modules, user stories
  • The test gives you the promise of what should be the expected result/behavior, so when you are refactoring an implementation, use tests to make sure that the implementation still yields the expected results
  • Write tests to cover the use cases or stories
  • Use the "Given When Then" model
  • Avoid mocks

This testing approach helps you to build the right product. But the negative point could be that it doesn't help you to build the product right. Another downside is that you don't see exactly what is wrong when the test is failing.

So classic unit testing approach pushes you to write more clean and quality code than "behavior testing". In my opinion, strict code reviews and static code analysis tools are better approaches to achieving the same result. The second downside for me is a really minor thing, since with debugging you can quickly find out what is happening.

I hope that you are still follow me and that I'm start changing a little bit your thinking about testing.

Now let's stop with theory and let's see how it works in practice.

Testing in Adobe Experience Manager (AEM)

Because last few years I have been working with AEM, I will show you how to test behaviors in your AEM projects. The same things you can apply in any other programming language or framework. Depending on testing library support, this can be easier or harder to achieve.

As an example let's say we need to implement Product Details API which is consumed by the client side. To build Product Details API let's say in Spring you will probably create several classes like Product Controller, Service, Repository, DTO, and so on. In the AEM world, this means you need to create Sling Servlet, OSGi Service, Sling Model, and some DTO classes.

Product Details acceptance criteria:

  • show product details information (ID, name, description, category ID, images, and variants)
  • product variants need to be available for a specific country
  • product variants are available from specific date
  • product variants need to be sorted by sort order
  • name and description of the product need to be localized (depending on the market), the fallback is English

Implementation what you will see here is not perfect, it's simplified and hardcoded. In the real world, this is more complex. But here implementation is not important, instead, we should focus on how to test the requirements of this API.

I will add here just 3 most important classes, other implementations you can see on Github

ProductDetails Sling Servlet

  • for handling request
  • it does some request validation
  • it uses ProductDetailsService to get all information about the requested product


ProductDetails OSGi Service

  • it's searching for the requested product in the repository/database
  • it's doing some product validation
  • maps product resources to the ProductDetails model
  • returns product details


ProductDetails Sling Model

  • representation of product resources in the repository/database
  • used as a response in JSON format

Except for those 3 classes, I need to create several more:

  • ImageModel, VariantsModel
  • BlobStorageService, ProductValidatorService, ProductLocalizationService, ResourceResolverService, ResponseService
  • Response and Status records

You saw that we have a lot of classes to build this user story. Usually, what developers test here are OSGi services. I'm not saying this is a bad approach, but for that, you will need more time, and every time when you refactor your code or add some new stuff, it's very likely that you will need to change your tests as well.

Instead of that let's test only Servlet because this is the public API of this user story. So what do we need to test in Servlet? First of all, we need to cover all requirements from acceptance criteria, additionally, we can cover some technical details of servlet implementation.

Test libraries in AEM

At the moment in my opinion the best library that you can use is AEM Mocks. AEM Mocks supports the most common mock implementations of AEM APIs + contains Apache Sling and OSGi mock implementations. For other not-implemented mocks, you will need to implement it by yourself or use Mockito. Besides those two I will use Junit 5.

Some tips before we start:

  • Try to have Test classes as clean as possible, they should contain just tests.
  • Move mocks in separate classes
  • Create some Util classes with common helper methods, if you repeat yourself in multiple places
  • Use @BeforeAll / AfterAll, @BeforeEach / AfterEach, Junit 5 annotations to not repeat yourself in every test method and to speed up your tests
  • Create a common AEM context in separate classes if you repeat yourself in several test classes
  • Don't programmatically create complex resources in the AEM context, instead export it from a real AEM instance as a JSON resource and load it into the AEM context.
  • Use ResourceResolverMock type whenever possible to speed up your tests

ProductDetailsServletTest

You will see that this test class is more or less clean and it focuses only on tests. There is no mocking here, separated mock example you can see here. I'm using @BeforeAll and @BeforeEach to do some common setups, like setting up market pages/resources and common request information. Also, I needed some helper classes to easier register all necessary classes into AEM context. All resources are exported as JSON from real AEM instances and imported into the AEM context so that we test on real data.

In this test class, I'm testing technical details and requirements

  • technical details
    • request validation
  • requirements
    • response for non-existing product ID
    • product details in different markets to cover localization
    • product variants validation for specific markets
    • product variants availability from a specific date
    • product variants sorting

Blog image | Smitheo

With this testing approach, I have covered 87% of lines of code. The other 13% that is not covered is catching exceptions.

import Image from 'next/image'

Other good examples for testing in AEM would be components. For every component you have requirements. To achieve those requirements you will probably create several classes like OSGi service, some Utils, and Records, and those requirements you will publicly expose through the Sling model to the view layer. Ideal candidates for testing.

Sum up

  • If you don't write tests, start writing it
  • Test requirements not implementation
  • Developers should have time to write tests

Want to apply these best practices to your project?

Leverage best practices and tailored solutions to drive success. Contact us today to learn how we can help optimize your Adobe Experience Manager implementation.