Zhus on First

[learning cs] testing

Testing: Lessons from Building a Resume Generator1

Through the process of building an HTML resume generator, I learned firsthand why multiple levels of testing are essential.

TLDR: Proper testing is important and complex. From deciding what to test, how to test, what to test again, and when to test, etc., TDD is a important tool that is both challenging to orchestrate and rewarding. Needless to say, I'm curious how large projects coordinate testing and iteration.


The Four Pillars of Testing


1. Unit Testing: Testing Individual Components

Unit tests verify that individual functions work correctly in isolation. They're fast, focused, and catch bugs early in the development process.

Example from the resume generator:

What I learned: Unit tests can give you false confidence. In my project, I had a unit test called test_build_complete_html_exact_structure that used hardcoded HTML strings. It passed and was certainly useful—but it was testing in a vacuum.

The hidden danger: My unit test used hardcoded HTML while my actual application used builder functions. The unit test missed that email addresses were being obfuscated from user@domain.com to user[aT]domain.com by the builders. This formatting change was intentional in the real code but absent in my unit test's expectations. And this makes sense, my extractor's role is not to format and validate.

Key takeaway: Unit tests are excellent for catching logic errors and edge cases, but they can miss integration issues when they don't use the same code paths as production. The expectations from separation of concerns need to be tested a various levels.


2. Integration Testing: Testing Component Interactions

Integration tests verify that different parts of your system work together correctly. They catch bugs that only appear when components interact.

Example from the resume generator: The test_build_complete_html_integration_with_section_builders test used actual section builder functions (build_basic_info_section, build_projects_section, build_courses_section) and then called build_complete_html_string() to assemble the complete page.

What this caught: This integration test immediately failed because it revealed the email obfuscation issue that the unit test missed. The test expected user@domain.com but got user[aT]domain.com because it was actually calling the section builders that performed this transformation.

Additional integration discoveries:

Key takeaway: Integration tests reveal the messy reality of how your components actually interact. They catch the bugs that happen when real data flows through multiple functions.


3. Full Pipeline Testing: Testing the Complete System

Full pipeline tests (also called end-to-end tests) verify that the entire system works from start to finish, just as a user would experience it.

Example from the resume generator: The main() function provided a complete pipeline test:

  1. Read a resume text file
  2. Extract all information (name, email, courses, projects)
  3. Build HTML sections
  4. Assemble complete HTML
  5. Write to an output file
  6. Verify the file renders correctly in a browser

What this caught: Running the full pipeline through various "real world" test cases in the TestResumes directory exposed issues that neither unit nor integration tests caught:


4. Manual Check in Browser


Best Practices Learned.

1. Keep Accumulating Tests (Regression Testing)

As I fixed bugs, I kept previous full pipeline test cases running. This caught regressions—when a fix for one test case broke a previously passing test.

2. Test Data Matters


The Bottom Line

I enjoyed TDD because it's one to think through a function first before just madly typing out code.

Building the resume generator taught me that testing is not optional, and one level is not enough:

Each level complements the others.


  1. I gave Claude Sonnet 4.5 a draft of the write-up and asked for it convert it to Markdown format and for suggestions to connect my thoughts. Then I edited Claude's feedback.

#learning_cs