Skip to content
Notifications
Clear all

Walkthrough: Using Cursor to generate unit tests for a nasty, untested module.

4 Posts
4 Users
0 Reactions
1 Views
(@emilyk22)
Estimable Member
Joined: 1 week ago
Posts: 100
Topic starter   [#8969]

I've been tasked with modernizing a legacy customer support portal, and part of that process involves bringing a notoriously complex and untested module under test coverage. The module in question handles SLA (Service Level Agreement) breach calculation logic, with numerous branching paths based on priority, business hours, holidays, and agent workload. It's precisely the kind of tangled, business-critical code that keeps you up at night when you contemplate refactoring. I decided to use Cursor as an assistant to generate a foundational suite of unit tests, and I wanted to document my workflow and the practical outcomes.

My initial approach was to open the module in Cursor and use the `@` chat to provide context. I pasted the entire module's code (approximately 450 lines) and then wrote a detailed prompt outlining my goal: "Generate a comprehensive suite of pytest unit tests for this SLA calculator module. Focus on the core public methods, but pay special attention to the edge cases in the `calculate_breach_time` function, such as weekend handling, holiday overrides, and the 'carryover' logic for paused tickets. Assume we have fixtures for a mock holiday service and a mock agent roster service."

Cursor's initial output was surprisingly broad. It generated a test file with over 20 test functions. However, the first iteration had some significant issues:
* It made incorrect assumptions about the structure of external dependencies.
* Several tests were essentially duplicates, testing the same logical path with different, but inconsequential, input values.
* It missed a few truly obscure edge cases related to timezone conversion that are documented in old Jira comments.

I then engaged in a iterative refinement process. I would run the generated test suite, watch it fail (either due to import errors or incorrect assertions), and then feed those errors back into Cursor. For example, I prompted: "The test `test_calculate_breach_time_weekend` is failing because the mock for `HolidayService.is_holiday` is returning `None`, but the actual method returns a boolean. Also, we need a test for the scenario where a ticket is paused at 4:58 PM on a Friday and resumed Monday at 9:02 AM, accounting for the 'grace period' configuration which is set to 5 minutes." Cursor would then correct the mock and generate the new, specific test case.

The final, useful output was not the initial auto-generated file, but the product of this dialogue. Here is a summary of the tangible benefits and observed pitfalls:

**Benefits:**
* **Speed of Scaffolding:** Creating the basic skeleton of 20+ tests would have taken me an hour of boilerplate work. Cursor did it in under two minutes.
* **Idea Generation:** It suggested several test cases for parameter combinations I had not immediately considered, such as testing the interaction between a holiday and a weekend.
* **Documentation via Tests:** The generated test names and comments often served as a form of clarifying documentation, forcing a clearer understanding of the module's intended behavior.

**Pitfalls & Required Oversight:**
* **Hallucination of Dependencies:** Cursor frequently invented method signatures or module structures that did not exist. This required vigilant, domain-specific knowledge to catch and correct.
* **Logical Redundancy:** It often generated multiple tests that were logically identical, just with different string literals or numbers, which inflated the test suite without adding coverage value.
* **Missed Business Nuances:** The truly gnarly business rules, discovered through past production bugs, were not captured until I explicitly described the historical issue and asked for a test to replicate it.

In conclusion, Cursor proved to be a highly effective force multiplier for a tedious and critical task, but it functioned strictly as a *draft generator* under close supervision. The value came from the iterative cycle of generation, review, failure analysis, and targeted prompting. It did not autonomously produce a production-ready test suite, but it dramatically accelerated the process and helped ensure a more complete initial coverage map than I likely would have created alone. For anyone undertaking a similar "test the untestable" project, I recommend budgeting time for this iterative review and correction loop; it is where the real work and value lies.


Support is a product, not a department.


   
Quote
(@cloud_cost_hawk_new)
Estimable Member
Joined: 3 months ago
Posts: 98
 

Interesting project, but I can't help wondering about the runtime cost implications of suddenly executing hundreds of new unit tests in your CI/CD pipeline. Every test cycle against a cloud-hosted service is another line item on the bill.

You mentioned a 450-line module. If Cursor generates a "comprehensive" suite, you could easily end up with 800+ lines of test code. Run that ten times a day across several branches and you've just added a non-trivial, recurring compute cost for your test runners. The business might be thrilled about the test coverage until they see the bill from AWS/Azure/GCP for the extra compute minutes.

Did you factor in the infrastructure-as-code and execution time for this new test layer? That's the kind of hidden cost these modernization projects always seem to overlook.


-- cost first


   
ReplyQuote
(@devops_dad_v2)
Estimable Member
Joined: 4 months ago
Posts: 122
 

You raise a great point that often gets lost in the rush to increase coverage. The cloud compute cost for test runners can become a silent budget killer.

In our migration, we addressed this by shifting unit tests to a dedicated, self-hosted runner pool (k8s pods with resource limits) and kept the integration/e2e tests on the cloud runners. For a module like this SLA calculator, the unit tests should be cheap and fast - if they're taking minutes and costing real money, the test architecture might be wrong.

Also, a "comprehensive" suite doesn't mean testing every permutation through the cloud. The 800 lines of generated test code should run in milliseconds locally and in a few seconds in CI. If it doesn't, that's a signal to refactor the module for better testability, not to skip testing.



   
ReplyQuote
(@carlosm)
Estimable Member
Joined: 1 week ago
Posts: 103
 

That's a solid prompt structure. Feeding the entire module and then pinpointing the gnarly bits like the carryover logic gives the AI concrete hooks to work with.

One thing I've found super helpful: after it spits out that initial test suite, ask it to also generate a table of the test cases it created. Not the code, just a plain list like "Test case: weekend priority 1 ticket, no holiday." That becomes your living documentation and makes it way easier to spot missing edge cases in the generated suite. You can even paste that table back in and ask "what scenarios from this list would fail if the holiday service returned a 500 error?"

Great start. Looking forward to seeing what it churns out for that carryover logic. That's usually where the real bugs hide.


Keep automating!


   
ReplyQuote