How to test your code 🧑‍💻

Amitosh Swain Mahapatra
5 min readSep 16, 2024

--

It’s easy to forget about testing when first learning to code. It’s not like anyone will grade you on your test-writing skills, right 👀? But if something goes wrong with your code in production, it can be a real pain in the ass to figure out why.

We’ve all been there: You’re trying to find an error that doesn’t make sense! You changed this one thing last week, but now it’s broken? 🤬WHAT IS GOING ON?!

Testing is an integral part of developing software

Testing enables you to make sure that your code works as expected, and it can even help with regression testing ✅ — a process where you test existing features to ensure they still work after adding new ones or modifying existing ones. Ultimately, you catch bugs 🐞 before they reach production.

Testing early and often is the best practice for writing software because it allows you to find problems before they become too large or complex for quick fixes later in development.

🔵 Unit testing — test code in isolation

te working on a piece of code, you write a unit test for it. It checks the correctness of that particular piece of logic you wrote. Or even better, come up with test cases before writing code, and make sure they pass!

🤔 But code is rarely independent. Therefore in unit tests, you employ mocks and stubs to “fix” the behaviour of other parts of your code and focus on the behaviour of the code-under-test. Your language of choice would have a framework that would do it, like Mockito for Java, Jest and Sinon for JS etc.

👉 You should write unit tests to provide quick feedback. They should be fast ⚡️ and avoid expensive/external operations (using mocks and stubs).

Example of a unit test

You wrote some code that sorts a list of records based on the alphabetical order of first and last names in case of a tie. Your unit tests should contain cases for — empty, few records without a common first name, few records with the same first name etc. and then assert the output.

🟡 Integration testing — test code that interacts with other modules

Integration testing is a form of black box testing that involves testing code against real-world scenarios. The right time to write integration tests is when you have just completed a feature with APIs and interactions.

👉 You should perform integration tests at the module level (micro-service level if you follow the micro-services pattern), where you validate the contract between components. You should validate your code with all its dependencies to start, run and serve your test expectations.

⚠️ You should use real servers for internal dependencies, like 🗄️ databases and 🗃️ caches. You can use a mock server like Wiremock or point them against an actual deployment for external services. Using a mocking library to mock external interactions does not serve the purpose of an integration test.

🐳 Docker and test containers help in spinning up servers for integration testing.

Also, as your code matures, you should build a test harness to test your application against real deployments automatically.

Example of an integration test

You call an API that stores some data in the DB, publishes several events to a queue and returns an HTTP 200 response. You write a test to assert these behaviours.

End-to-end testing and manual testing

👉 You perform end-to-end testing at your system’s outermost, user-accessible part. Suppose your system has multiple microservices powering a single dashboard. In that case, you should concentrate the bulk of your end-to-end testing efforts on testing the behaviour of the dashboard as a whole when deployed with other modules in a test environment.

🔧 Manual testing is the most common type of software testing. It’s also the most expensive and time-consuming but also the most effective. As part of manual testing, you must manually test each feature in your application and ensure it works as expected. Lots of 🖱️ clicking around and ⌨️ entering data, just like the end user would. But those few clicks would touch lots of layers of code at the same time.

The pyramid of testing

The pyramid of testing visually represents the order in which you should run tests. You can also use it to help you decide what tests are appropriate for your codebase. The bottom level represents unit tests, followed by integration tests and manual (UI) testing at the top.

The bottom levels are narrow and cover a limited amount of code at a time, but it is easy to write and debug failures. As you move up, the code under test increases with lots of logic stacking over each other. A single run covers a lot of code at a time, but debugging failures become proportionately 🌵 tricky due to many more ⚙️ moving parts.

How to assert the quality of your test suite

You must design your tests according to the specification of your software. You should easily understand what each test is doing and why it’s essential. A wrong test is a source of bugs if not reviewed correctly.

⚠️ If a test has any dependencies on external systems (such as databases), you should document or mock them to ensure they don’t affect the rest of your tests.

❗️Your test should also be easy to write and read so that if you need to change something in your codebase, it’s easy for someone else (or yourself!) who isn’t familiar with all your classes right away can understand what’s going on in each unit test file.

If you have a piece of code which is complicated to test, there is probably a code-smell hiding.

⚡️ Finally, all tests must run quickly — if a single test takes too long, developers will avoid running it frequently because it adds time to their build process and slows them down when making changes elsewhere in the codebase.

👀 Use code coverage to measure the quality of your test suite objectively. A high code coverage indicates that you have accounted for the various conditions and control flow. Test frameworks usually come with a bundled coverage tool like JaCoCo for Java, coverage.py for Python and Istanbul for JS.

While testing is essential to developing software, it can be challenging to get it right. There are many different types of tests, each serving another purpose. The most important thing is to understand what kind of testing is appropriate at the moment so that you can make sure that tests cover all of your code before deploying it into production and squash those 🐞 bugs!

Originally published at https://recursivefunction.blog.

--

--