< Back

Building a test framework: A journey from zero to hero

Author

Mats Van Audenaeren

Date

17/07/2024

Share this article

Hey There, fellow coder! Buckle up because we’re about to embark on an epic journey. I’d love for it to be to the planet Naboo, but it isn’t... We’re going to build some testing frameworks! Yes, the overlooked heroes of our development world. Now, before you roll your eyes and think: “Oh no, not another dry, technical snooze fest”, let me assure you, it’ll be fun. I won’t go into too much technical stuff. This blog will be a top-level blueprint on what you should think about when building a testing framework.

Why bother?

Imagine building a spaceship without checking if the foundation can hold. Chaos, right? That’s your code without tests. We would be throwing code into production without knowing if it’s safe, high-quality, or even if it does what it’s supposed to do. Hoping for the best but preparing for the worst is a recipe for disaster. The goal of a testing framework is to ensure your code isn’t just a pile of spaceship parts waiting to break. It’s about confidence. When your tests pass, you can deploy your application, or launch your spaceship without breaking a sweat. And later, when some functionalities of your spaceship changed, your framework will pay–off big time. One Git command and your whole codebase is tested in minutes. Trust me, your future self will thank you.

Your testing arsenal

Alright, engineer, let’s talk about the different ways of how you can test your spaceship. Let me tell you straight ahead: there are many. For the sake of you not losing focus, I’ll provide you with the most important ones. We dive into the essential tests that will ensure your spacecraft is ready for the interstellar journey.

  • Component testing: Assembling smaller sections of your spaceship to ensure they work well on their own. It’s like testing the wings of your ship. In the codebase it would resemble the testing of a single class or module in isolation, without any dependencies on the rest of the system.

  • Integration testing: Now that you’ve tested the wings, it’s time to attach them to the main body of the spaceship. Integration testing ensures that different sections of your code connect and work together as they should. This step involves combining various components or modules and testing how they interact with each other.

  • Functional testing: With the spaceship assembled, it’s time for a test flight. Functional testing is all about verifying that your spaceship looks and functions just like you intended.

  • End-to-End testing: Finally, it’s time to take your spaceship on a full-scale mission to the outer galaxy. End-to-end testing is about making sure that your entire spaceship, from takeoff to landing, works as expected. You test entire journeys from start, whether UI or API, all the way to the database.

Mission control commander

As you’re writing your tests for that state-of-the-art spaceship of yours, you’re going to need a trusty sidekick to help you manage and execute those tests. For that, you need a test runner. Think of this role as your mission control commander. Welcome aboard, Commander! The test runner runs your tests. Any test that is assigned with a certain test recognizance, can be run. You can ask the commander to run all tests, a specific subset, or a single test. You need to have the test runner in your team, or you won’t be testing much. Some widely used examples are: Jest-Vitest, JUnit, Pytest and TestNG.

The assertions

Your tests are running, great! But, in your code, you actually need to test or verify something. It’s time to meet another essential crew member: The assertion library! Its job is to verify and tell you that every test has passed, or that something is wrong. The assertion library checks whether the code’s output matches what is expected. They help you validate if your spaceship’s engine is working correctly, or if an alarm goes off when an asteroid is near. Now you know if the functionality is working correctly. It’s either true or false, it works, or it doesn’t. Popular assertion library choices are: JUnit, AssertJ, Hamcrest, Jest, Selenium Asserts, …

Mocking and Stubbing for replacements

Are you still here, explorer? Great! Through experience in building test frameworks, you’ll learn that the components you need to test might be unstable or even broken. Or, a part of the application is external, out of your control. You will need to, somehow, “fake” these parts. It’s time to equip your spaceship with advanced tools for creating simulations and replacements, introducing Mocking and Stubbing. What is it? These are techniques used to simulate the behaviour of real objects and functions in your tests. They help you test specific parts of your code without relying on the actual implementation. This way your tests can be very isolated. Now, when to mock and when to stub? Use mocking to verify interactions between components, and stubbing to control method responses.

Best practices

So you’re building your framework and it’s almost done. As we continue our journey through the cosmos of testing frameworks, it's time to lay down some tips & tricks. These best practices are your guidance that will keep your testing framework robust and maintainable:

  1. Keep tests isolated
    Each test should be like its own isolated module in the framework, unaffected by others. Isolation ensures that tests are independent and reliable. If a test fails, you know exactly what’s going on within that specific functionality, not because another test changed a commonly used value in the database, or modifies a certain state that is necessary for the test.

  2. Name tests clearly

    Imagine finding a test labeled "test123" six months from now. What does it do? Who knows! Naming your tests clearly is like labelling the controls in your spaceship cockpit, essential!

  3. Run tests often

    Frequent testing is like checking up on your framework. The sooner you catch an issue, the easier it is to fix.

  4. Avoid hardcoding values

    Make your tests flexible. By using hardcoded values, you take away this powerful characteristic. Flexible tests have more possibilities and are easier to maintain. They allow you to adapt to changes in your codebase without rewriting tests.

Advanced control

Congratulations! You’ve mastered the basics of spaceship engineering. You are ready to explore some advanced testing features. Let’s turbocharge that space vessel, and make sure your code isn’t just working, but also efficient.

3…2…1, Going into hyperspace. Reach your destination superfast with parallelism. By running tests in parallel, you greatly reduce the overall testing time, allowing you to get feedback quicker.

Scan your code coverage. It tells you which parts of your code have been tested and which parts are forgotten. High code coverage gives you confidence that your codebase is tested in depth.

Mission accomplished

And there you have it. What a journey, right? We've travelled through the process of building a test framework. I hope I lived up to my word of not making a snooze fest out of it. By now, you should have a good grasp on the importance of testing and a framework. Remember, the goal of a test framework is to give you confidence in your code. It's about creating a back-up that allows you to add features, and refactor your code without fear. When your tests pass, you can deploy with assurance, knowing that your spaceship is ready for the next great adventure.

Happy testing, and to infinity and beyond!