From Zero to Hero: What I Learned from Achieving 100% Code Coverage

From Zero to Hero: What I Learned from Achieving 100% Code Coverage

In the past few weeks, I've been dedicating some of my free time to a new project. As someone who enjoys tinkering with code and building small-scale applications, I'm always looking for new challenges to push my skills to the limit. This time around, I decided to focus on something that has always been a bit of a struggle for me: achieving 100% code coverage. While I've always understood the importance of thorough testing and documentation, actually putting it into practice has been a different story. But with this project, I was determined to do things differently. I set out to create a codebase that was not just functional, but completely covered - and in doing so, I learned some valuable lessons that I believe are worth sharing. In this article, I'll be discussing my experiences and insights from building a 100% covered codebase, and how it impacted my approach to software development.

What is 100% code coverage?

It's a term that's often thrown around in the software development world, but what does it actually mean? Put simply, 100% code coverage refers to the percentage of a codebase that is executed by automated tests. This means that not only must all tests pass, but also that all lines of code and all branches of logic (such as conditional if/else statements) have been run through by the test suite. In other words, achieving 100% code coverage means that every single aspect of the code has been tested and verified to be working as intended. While this may seem like a lofty goal, it's an important one - and one that can have a significant impact on the quality and reliability of a software application.

Perfectionism can allow Flexibility

Perfectionism vs Flexibility - it's a classic debate in the world of software development. On one hand, there's the argument that perfectionism is idealistic and ultimately does more harm than good, e.g. making things more rigid and less flexible. And after all, it's impossible to create a completely perfect codebase - there will always be bugs and issues that need to be addressed. On the other hand, there's the argument that a little bit of perfectionism can actually make us more flexible in other areas. This is something I discovered while building my 100% covered codebase. By focusing on achieving 100% coverage as the one area to be disciplined with, I found that I was able to be more flexible in other areas of the project. I could implement imperfect ideas or implementations, knowing that the discipline of code coverage would help minimize the potential for damage. Because I had such a high degree of test coverage, I could be confident that these imperfect ideas would minimally compromise or break the project behavior, making it easier to experiment and iterate without worrying about introducing major issues. Later on, I could revisit these ideas and perfect them as needed - all without sacrificing the overall quality and stability of the codebase. In other words, a little bit of perfectionism and discipline can actually make us more flexible and adaptable in the long run.

We don't need to do TEST-FIRST for everything

This was a lesson I learned while building my 100% covered codebase. Before this project, I had worked on other projects where test-first discipline was applied. While this approach did result in high-quality codebases, I found that it could be exhausting in the long run. Even for small and trivial functions, I had to figure out their proper test cases before using them in bigger and more critical functions. This could be time-consuming and sometimes even unnecessary. Additionally, some of these small and trivial functions were better off not being exposed to the public - but in order to do test-first discipline, I would have to bring them out into the open, potentially confusing users and developers due to the large amount of public interfaces the codebase provided.

With the 100% code coverage discipline, I only need to focus on doing test-first on critical functions. For trivial and obvious functions (like the supporting extension classes), I don't need to follow test-first. That's because most of their codebase is already covered in the critical functions' test cases. And if these trivial functions don't have 100% coverage (but still have a high coverage rate of 80-95%), I can simply create some simple tests directly to these functions (if they are public) or indirectly to the public functions using the trivial functions (if they are not public). This approach allows me to strike a balance between test coverage and practicality, focusing on the most important and critical parts of the codebase while still ensuring that everything is thoroughly tested and reliable.

Uncovering Better Design Ideas

Achieving 100% coverage doesn't always mean creating more tests, but it can lead to better design ideas as well. By identifying the code portion that is not covered, I often find opportunities to optimize the design. For instance, I encountered a private code that needed to retrieve an object from a collection using a key for processing. The LINQ method used was myCollection.First(o => o.Key == key), and the code coverage picked up on the lack of false scenarios of o.Key == key (for branch coverage). While attempting to create a test case for this scenario, I realized that the condition was always true, and the key always existed in the collection. To address this, I changed the collection from a list to a dictionary, i.e. calling myCollection[key] instead, resulting in not only 100% coverage but also an improved time complexity due to the use of the hash-map dictionary. This is just one example of how 100% coverage can lead to discovering better design ideas.

DRY and YAGNI to the max

DRY (Don't Repeat Yourself) and YAGNI (You Ain't Gonna Need It) are two well-known principles in software development that aim to improve the quality and maintainability of code. DRY suggests that you should avoid duplicating code and keep your codebase concise, while YAGNI suggests that you should only implement functionality that you need right now. Adhering to these principles can be challenging, especially when it's not clear what functionality you may need in the future.

However, I found that the 100% code coverage discipline made it easier for me to follow these principles. Whenever I was about to break these principles, I felt a sense of unease when I saw the test scripts starting to look more complex and the code coverage dropping fast. This feeling acted as a reminder to me to reconsider my approach and see if there was a way to achieve the desired functionality without breaking DRY or YAGNI. As a result, I was able to keep the codebase lean and efficient while maintaining its reliability and maintainability.

Learn and Implement Approval Tests

By striving for 100% code coverage, I had the opportunity to learn and get used to Approval Tests. Initially, creating tests to cover all code paths seemed daunting, especially when dealing with complex or long text results. However, I realized that using Approval Tests could greatly simplify the process. Approval Tests store the results in separate text files, which makes it easy to review in case of a test failure. This is especially helpful when dealing with large or complex outputs. Additionally, I learned how to use Combination Approval Tests, which allows me to test multiple scenarios with minimal code. For instance, using a method with three parameters, each with three different values, would require creating 27 tests. With Combination Approval Tests, I can put all 27 test cases into just one line of code. Through implementing 100% code coverage, I was able to not only improve the quality of my code, but also to learn valuable techniques that simplify testing processes.

Front-to-back or Back-to-front?

As a coder, I used to struggle with finding the right direction for my projects. I would often wonder whether I should start with the front-end or work my way from the back-end. However, through this project, I discovered a simple rule that has helped me streamline my coding process: when creating a new function, it's best to go front-to-back. This approach allows me to simplify the back-end interfaces and focus only on what's necessary. On the other hand, when refactoring or redesigning, I start at the back (the deeper the better) and work my way to the front. This approach ensures that any effects of my changes, both good and bad, can be easily managed in a 100% code coverage environment (and somehow it's a good feeling to see the effects ripple out).

Beware of the Front-end/UI code

It's easy to get caught up in the aesthetics and functionality of front-end/UI code (web application projects, mobile app UI projects, etc.) and neglect the back-end code that's powering it. However, this can lead to technical debt down the line, especially when it comes to automated testing. Since front-end/UI code is notoriously difficult to test, leaving crucial processing code in these projects can create blind spots that could come back to haunt you. It's best to keep front-end/UI code as simple as possible and let it assume what it's calling from the back-end code, rather than assuming it's doing the right thing. As a general rule, keep the front-end codebase lean and avoid putting any processing code in there that can be moved to the back-end, where it can be covered by tests.

End note - the article has been written by AI (and I'm feeling a little obsolete)

To express gratitude to ChatGPT for its speedy and efficient help in writing this article. As I reflect on the time and effort it would have taken me to write this on my own, I can't help but feel a little obsolete. I mean, why bother honing my writing skills and spending countless hours crafting the perfect article when I can just rely on an AI to do it for me? But hey, at least I can now spend more time on important things. So thanks, ChatGPT, for making me feel both grateful and a little lazy at the same time!

Chiku Gardiner

I help businesses build extraordinary workforces that prosper.

9 个月

The article I needed to read. Thanks for this. I 100% agree. See what I did there? ??

回复

要查看或添加评论,请登录

Peter Vo的更多文章

  • The Art of Dealing with Code Sh***: A Comprehensive Guide

    The Art of Dealing with Code Sh***: A Comprehensive Guide

    As one who has done a fair share of works in developing, reviewing, and maintaining code, both my own and others', I…

  • An approach to Single-responsibility principle

    An approach to Single-responsibility principle

    Any fan of Uncle Bob must know the 5 principles, forming the acronym SOLID, in designing software for the goal of…

    3 条评论
  • The 15 Minutes Drafts: 2 Dependency Flows

    The 15 Minutes Drafts: 2 Dependency Flows

    Most systems, usually small to medium, uses the N-tier architecture style. From this model, we can see the dependency…

  • Triangles in Software Development

    Triangles in Software Development

    The Iron Triangle: Time Quality Scope / Quantity There are many other variants, which I consider as the same type in…

  • Minesweeper Kata: Logic portion

    Minesweeper Kata: Logic portion

    I guess there is not much introduction on this, since most should know what is Minesweeper. Create and implement a Game…

  • Pagination Kata

    Pagination Kata

    Introduction Suppose a client sends a request to a web API server, and the web API server will have to return a message…

  • The Bank Account Kata - Clean Architecture experiment

    The Bank Account Kata - Clean Architecture experiment

    I have known Uncle Bob's Clean Architecture model for awhile, but did not understand it thoroughly and know how to…

  • The 15 Minute Drafts: The many faces of Software Development

    The 15 Minute Drafts: The many faces of Software Development

    As in my previous draft The 15 Minute Drafts: The Garden Industry, Gardening seems to be the closest analogy of…

  • The 15 Minute Drafts: The Garden Industry

    The 15 Minute Drafts: The Garden Industry

    Imagine a world where gardens become a popular commodity. Thanks to the advanced technologies of Time and Space, any…

  • The 15 Minute Drafts: What the heck is Agile?

    The 15 Minute Drafts: What the heck is Agile?

    VERY FIRST INTRO This is simply just a start of something. I'm not the type who solidifies my thoughts and reasoning…

社区洞察

其他会员也浏览了