DEV Community

Dharam Ghevariya
Dharam Ghevariya

Posted on

Building Test Suite for "repo-contextr" using "pytest"

When building software, testing often gets pushed to the end (or forgotten completely!). But for my project repo-contextr, I decided to do testing properly from the start. This post shares my experience writing tests and usage of the tool throughout the development process. If you are reading my blog for the first time, please visit my previous posts for a better understanding of the repo-contextr CLI tool.

As this tool was built using Python, I looked for industry standard frameworks for testing within the Python ecosystem. After spending some time comparing different libraries, I selected pytest. This testing framework is straightforward to use, offers a rich feature set, and is widely adopted by the Python community.

Along with the testing framework, I also integrated pytest-cov to measure how much of my code is actually executed by tests. Running pytest alone only tells you whether tests passed or failed, but it does not reveal how much of the logic those tests are exercising. pytest-cov provides a coverage report with detailed visibility into which modules, functions and even specific lines are executed during testing. The coverage insights were especially useful, and additional command examples for usage can be found in the project documentation.

Later, I explored the concept of pytest Fixtures. These allow the reusable creation of setup environments and test data in a clean manner. Instead of repeating the same setup code inside every test, a fixture defines setup once and then makes it available across multiple tests. This improves readability and consistency. If you want to see how fixtures are used in this project, you can refer to the conftest.py file in the test directory.

Setting Up Tests

For test organization, I created a dedicated tests folder separate from the main src code, with conftest.py containing shared fixtures and unit tests stored inside a unit subfolder. This structure keeps test logic isolated from production code and is a pattern many production Python projects follow.

To configure pytest to work the way I expected, I added configuration settings inside pyproject.toml. These settings define where tests are located, how pytest should discover them, and enable strict configuration behaviour. For coverage, I configured coverage to scan only the src directory, ignore test directories and cache folders, and also enabled branch coverage. I generated HTML coverage output through the htmlcov folder, which visually highlighted which lines and branches were tested.

Fixtures in conftest.py helped significantly in avoiding duplicate setup steps. For example, I created a temp_dir fixture to produce a temporary folder for each test. Another fixture, mock_files_dir, automatically placed a small collection of different file types inside the temporary directory. These reusable fixtures improved the clarity of tests, reduced duplication and made the suite easier to maintain over time. The testing documentation inside the repo explains this structure in further detail.

What I Learned

While writing tests, I realized it is not enough to only test normal cases. Empty strings, Unicode content, unexpected edge cases and unusual inputs can reveal subtle bugs, so I made sure to test those early. Coverage reports showed that many untested lines were inside exception handling blocks. After adding tests that simulated permission errors and I/O failures (using unittest.mock), overall coverage increased and the tool became more reliable. This experience reinforced the idea that testing is not only about verifying successful behaviour but also ensuring that failure paths behave safely.

Useful Testing Features

During the process, I discovered several practical pytest options that made the workflow faster. Being able to run just a single test file, a single test class, or even a specific test function helped a lot while iterating and debugging. Coverage visualisation was another valuable tool. Seeing which parts of the code were marked in red encouraged me to target those sections with improved test coverage. The HTML report was especially helpful because it presented code with colour indicators highlighting tested, partially tested and untested lines.

For debugging, pytest allowed me to display variable values at the moment of failure, drop into an interactive debugger and reveal printed output during execution. These features helped make the debugging loop shorter and more interactive.

To keep everything structured, I grouped related test functions into classes. This kept similar tests together, provided a natural grouping for running specific sections of the test suite, and helped improve discoverability. After completing my tests, the total coverage of the project was around ninety five percent, which I find ideal for this stage. The remaining areas that are not covered mostly relate to small display-oriented functions, print messages and type checking logic. These do not directly affect core behaviour and are validated in other ways.

Important Lessons

One of the biggest takeaways from this experience is that writing good tests is not about achieving one hundred percent coverage. The goal should be to achieve the right coverage. I started with basic tests, then gradually expanded into unusual input cases such as empty strings, Unicode content, extremely long inputs and invalid file scenarios. I made sure to test error paths and exceptional behaviour, since those were the most frequently missed lines in coverage reports. Reusing fixtures greatly reduced duplicated setup code, resulting in cleaner and more maintainable test files. Clear and descriptive test names also helped a lot because they communicate exactly what behaviour is being verified, which benefits both future contributors and my future self.

Top comments (0)