DEV Community

Cover image for Tangible Correctness
tyrael
tyrael

Posted on

Tangible Correctness

Testing code is one of the more overlooked yet polarizing aspects of modern software development. It's a pretty hand-wavy subject especially considering most educational material on creating software don't even cover writing effective tests, yet the importance of it is brought up time and time again. This article will try to get more in-depth with writing tests for your code, based on my recent experience as an intern backend developer.

When not to test

It might seem backward but the first important thing to consider about testing is whether or not you have to test the code in the first place. I simply can't agree with testing puritans that enforce 100% unit test coverage, as a lot of these tests end up as being nothing but smoke tests.

Only test code that is yours.

To be specific, only test logic that you yourself have implemented. Writing expectancy tests for libraries and modules that you have imported via your language's package manager is redundant and unnecessary. Even if your test does catch some bug with the library you are using, fixing it will be out of scope for your project (but you should probably file an issue in their GitHub repository). Use trusted external dependencies as trusted external dependencies.

It is very likely that external libraries already have their own tests for their code, so you should not be worrying about creating said tests. Writing tests is always going to be a balance between the cost of the bugs that your tests are checking for and the cost it takes to introduce the test to your code base, and if the bugs cannot be fixed by you, those tests are worthless.

Unless you get reviewed by the number of lines of code you've contributed then you really shouldn't be testing behavior of external dependencies.

Factories, fixtures, and faking

Always try to make your tests themselves modular and parameterized. By creating fixtures, you reduce redundant setup code (which overall lowers the noise and increases readability of your tests), and by parameterizing tests you increase your coverage without drastically increasing your code.

Moreover, factories fall under this same category of modularizing redundant setup code within your tests. Remember, your tests job is to test your code, not create more ceremony.

The factory library that I've had the pleasure of using was factory_boy, for a Django project. In combination with pytest fixtures, writing tests became pretty enjoyable and elegant. Not only can factories be used to generate Model instances for your unit tests, they can also be handy in creating entries in your database for end-to-end testing if you're using some form of Object-Relational Mapping (ORM) for your project.

Lastly, factory_boy interfaces very well with the existing faker library, which made mocking values very easy to do. Mocking data is also very handy as it, again, increases your test coverage without bloating your code. Having a good mechanism for mocking data, via external dependency or in-house generators, can prove to be very handy to make your tests even more reassuring.

Automate tests

Although it might be a bit daunting, it is very worth it to spend a couple of hours setting up some form of automated testing (in my case via GitHub actions). Remember, tests entire purpose is to ensure integrity within your code (especially when changes are introduced), and you simply just can't rely on remembering to run your tests every time you merge a pull request. Not only does this create a layer of security for your pull requests, it also frees you up to isolate your tests freely within your local development environment as you get the reassurance of your automated tests testing everything else.

Use AI

I personally don't use AI generative tools when I'm writing my code, with the sole exception of tests (and creating models, boilerplate-y stuff). The probabilistic properties of AI generated code plays very well into writing tests as it is not unlikely for a human developer to miss certain edge cases in their logic, but not for AI. I have often times found myself thinking "oh right" when reading Copilot's suggested code and that has done nothing but bolster my trust in my tests. Not to mention, it saves you time by letting you "lazily" use your testing suite, I only reach for certain assert methods when I need them so often times I would have Copilot suggest it to me and then that's when I'll read the documentation for it.

Conclusion

Testing is indeed important, but often times not discussed enough. Writing expectancy tests for code has honestly made me feel more confident in being a programmer; it made me tackle topics that I initially had not encountered- just because they were good solutions for testing (a notable example was me writing multi-threading code to create tests that handle race conditions)!

Moreover, there are plenty of ways to increase your test coverage without increasing your code size, namely factories, fixtures, and mocking; and this is effective testing.

You do not have to fully commit to a test-driven development style, as long as you have stable and rigid tests that you can rely on. Good tests serve as a foundation for the rest of your code, and having a good foundation is a sign of a good architecture. And who doesn't want well-architected code?

(This article hasn't even covered the dopamine surge you get from seeing tests go green!)

Top comments (0)