Skip to content
Notifications
Clear all

Migrated from Claude to Gemini for a 200-user shop - test generation failures

3 Posts
3 Users
0 Reactions
1 Views
(@isabell4)
Eminent Member
Joined: 5 days ago
Posts: 22
Topic starter   [#14287]

We recently completed a migration of our primary AI-assisted development contract from Claude (specifically, Claude 3 Opus via the API) to Google's Gemini 1.5 Pro. Our organization is a 200-person software shop, and the decision was driven by a combination of licensing cost predictability and our broader strategic alignment with the Google Cloud ecosystem. The migration was largely smooth for boilerplate generation and documentation tasks. However, we have encountered a significant and reproducible degradation in the quality of unit test generation, which directly impacts our team's velocity and code reliability.

The core failure mode is not a simple hallucination but a systematic misunderstanding of the scope and isolation required for a proper unit test. Gemini consistently generates tests that are, in essence, integration tests—they invoke large sections of the dependency tree rather than mocking or stubbing external services. This defeats the purpose of a fast, isolated unit test suite.

**Example Failure Case:**

* **Prompt:** "Generate a comprehensive Jest unit test for the following TypeScript function. Mock all external dependencies, including the `apiClient` and `logger` modules."

* **Function Under Test:**
```typescript
import { apiClient } from '../lib/api-client';
import { logger } from '../utils/logger';

export async function processUserTransaction(userId: string, amount: number): Promise {
try {
const user = await apiClient.fetchUser(userId);
if (!user || user.balance {
it('should return false for invalid user', async () => {
(apiClient.fetchUser as jest.Mock).mockResolvedValue(null);
// ... rest of test
});
});
```

Beyond this structural issue, we've documented other consistent failure patterns in test generation:

* **Incorrect Mock Assertions:** Gemini frequently suggests asserting on the mocked function's *return value* rather than using `toHaveBeenCalledWith` matchers to verify the interaction contract.
* **Poor Edge Case Coverage:** When prompted to "generate tests for edge cases," it produces variations of the same logical path (e.g., different numeric values) rather than true edge cases like network timeouts, malformed API responses, or `null` vs. `undefined` distinctions.
* **Async Test Flakiness:** Suggested implementations often neglect to handle async/await properly in test setup or assertions, leading to false passes.

From a product management and ROI perspective, this is a critical deficiency. The time saved by generating boilerplate tests is now consumed by senior engineers reviewing and rewriting these tests to meet our quality gates. The reliability of the suggested code is low, which increases rather than decreases cognitive load. We are currently evaluating whether to:
1. Invest in creating extensive, internal prompt engineering guides specifically for test generation with Gemini.
2. Re-architect our approach to use a different model (potentially a fine-tuned, smaller model) exclusively for test generation.
3. Revisit the cost-benefit analysis of maintaining a multi-vendor approach for different development tasks.

I am interested in whether other mid-size shops have encountered similar regressions in specific task categories during model migrations. More specifically, has anyone developed a robust prompting strategy or template that forces Gemini to adhere to proper unit testing isolation principles? Vendor stability and predictable performance across task types are becoming as critical as raw benchmark scores.


PM by day, reviewer by night.


   
Quote
(@cloud_cost_fighter)
Estimable Member
Joined: 2 months ago
Posts: 123
 

I'm a FinOps lead at a 250-dev agency, and our main bread-and-butter is generating and maintaining code for client projects. We've run Claude 3 Sonnet, Opus, and Gemini 1.5 Pro/Flash in production for different tasks over the last year, so I've seen the billing and the output gaps firsthand.

* **Real Unit Cost:** Claude Opus API is roughly $0.03 per test generation for our average prompt. Gemini 1.5 Pro is about $0.007 for the same. That 4x cost difference is real, but it's a trap if quality tanks.
* **Context Window Tax:** Gemini's 1M token context feels like a win, but for unit test generation, it's overkill. What happens is the model tends to over-engineer, pulling in too many inferred dependencies from your provided codebase snippets. Opus, with its smaller standard window, often produces a more focused, mocked test by necessity.
* **The Fine-Tuning Gap:** This is the hidden cost. With Claude, we spent about 40 hours crafting a prompt library and fine-tuning a smaller model for test generation that works. With Gemini, the same effort didn't yield the same ROI; its behavior around mocking is just more stubborn. You'll likely burn 1-2 sprint cycles tuning prompts to get 80% there.
* **Ecosystem Lock-In:** You mentioned GCP alignment. The hidden cost here is latency and egress if you're not already on GCP. Gemini calls from our Azure-hosted services added about 90-120ms on average vs. Claude. It's small per call, but it adds up in developer flow.

If unit test quality is a primary metric for velocity, I'd recommend sticking with Claude for that specific workload and using Gemini for boilerplate and doc tasks. To make a clean call, tell us your tolerance for per-test manual review and whether your team already has a mature prompt library you can port.


Cloud costs are not destiny.


   
ReplyQuote
(@code_reviewer_anna)
Estimable Member
Joined: 3 months ago
Posts: 122
 

Yep, we've seen this exact pattern with test generation. It's frustrating because the tests *look* correct at first glance - they run, they often pass. The failure is in the design.

That systematic misunderstanding of scope is spot on. We got around it by being hyper-explicit in our system prompt. Instead of "Mock all external dependencies," we now have to list the mocking library and specify the boundaries: "Use jest.mock for any module import that isn't the function under test. Do not test the behavior of the apiClient, only that it was called with the expected arguments."

Even with that, we sometimes have to append a reminder to the user prompt: "Remember, this is a unit test. Isolate the function." The cost saving isn't worth it if engineers spend 15 minutes rewriting every generated test.


Clean code is not an option, it's a sanity measure.


   
ReplyQuote