· nervico-team · software-development · 11 min read
Testing in Custom Projects: A Complete Strategy
Complete testing strategy for custom software projects: what types of tests you need, when to implement them, and how to balance coverage with delivery speed.
A bug in production costs between 10 and 100 times more than catching it during development. According to an IBM Systems Sciences Institute study, the cost of correcting a defect multiplies exponentially with each phase of the lifecycle in which it goes undetected.
Despite these numbers, many custom software projects treat testing as an optional luxury. Code is written, manually tested, delivered to the client, and fingers are crossed. When bugs appear (and they always appear), they get fixed on the fly with patches that generate more technical debt.
This article presents a complete and realistic testing strategy for custom projects: what types of tests you need, when it is worth implementing each one, and how to build a quality culture without paralyzing development.
Why Testing Is Critical in Custom Projects
The Real Cost of Not Testing
In your own product, you can afford some tolerance for bugs. You have support teams, you can deploy hotfixes quickly, and your users (usually) give you the benefit of the doubt.
In a custom project, the dynamic is different:
The client pays for quality. Every bug that reaches production erodes client trust and can generate contractual claims.
There is no second chance for a first impression. If the first delivery is full of errors, the client will question all your technical competence. Recovering that trust is extremely difficult.
Bugs have direct cost. Every bug that needs fixing after delivery consumes hours that are not budgeted. If the contract is fixed price, those hours come out of your margin.
Team rotation is a real risk. In consulting, teams change. Without tests, every new team member is afraid to touch the code because they do not know what might break.
The Fallacy of “We Do Not Have Time for Tests”
“We do not have time to write tests” is probably the most expensive phrase in software development. It seems reasonable in the short term: writing tests takes time, and the client wants to see features, not tests.
But the data tells a different story. A Microsoft Research study on teams that implemented TDD showed that, although the initial development phase was 15% to 35% slower, production defects were reduced by 40% to 90%.
In custom projects, where fixing a bug after delivery can cost 5-10 times more than preventing it, the investment in testing pays for itself in the first few weeks.
The right question is not “do we have time for tests?” but “what level of testing do we need for this project?”
Types of Tests and When to Use Them
The Testing Pyramid
Mike Cohn popularized the testing pyramid, which establishes a hierarchy:
Base: Unit tests. Fast, cheap, many. They test individual functions and methods in isolation.
Middle: Integration tests. They test that several components work correctly together. Slower and more expensive than unit tests, but they detect interaction problems.
Top: End-to-end tests (E2E). They test complete user flows. The slowest and most expensive, but the ones that most closely resemble real usage.
The pyramid suggests having many unit tests, fewer integration tests, and few E2E tests. It is a good reference model, but it is not dogma.
Unit Tests: The Foundation of Everything
Unit tests test a function, method, or class in isolation. They are the fastest to execute (milliseconds) and the cheapest to write.
When to prioritize them:
- Complex business logic (calculations, validations, data transformations)
- Pure functions without side effects
- Critical algorithms where an error has serious consequences
- Code that will be reused in multiple parts of the system
When not to obsess:
- Infrastructure code (configuration, database connections)
- Simple controllers that only delegate to services
- Trivial getters and setters
- Code that will change drastically in the next iterations
Realistic coverage target: 70-80% in business logic, 40-60% overall. 100% coverage is a counterproductive goal that leads to writing tests without value.
Integration Tests: Where Real Bugs Live
Integration tests verify that different components work correctly when combined. For example: that your user service communicates correctly with the database and the authentication system.
Where they add the most value:
- Database interactions (queries, transactions, migrations)
- External API calls
- Authentication and authorization flows
- Message processing between services
- Third-party service integrations (payment gateways, email services)
Recommended tools:
- Testcontainers: For spinning up databases, message queues, and other services in Docker containers during tests.
- WireMock or similar: For simulating external APIs with predefined responses.
- Data factories/fixtures: For creating consistent and maintainable test data.
Practical rule: If your system interacts with external components (databases, APIs, queues), integration tests will probably catch more real bugs than unit tests.
End-to-End Tests: The User as Reference
E2E tests simulate real user behavior. They open a browser (or simulate HTTP calls), navigate through the application, fill out forms, click buttons, and verify that the result is correct.
When to implement them:
- Critical business flows (registration, payment, main processes)
- Features that directly generate revenue
- Flows involving multiple services that need to work in coordination
- Regression in features already delivered to the client
When to avoid them:
- Features that change constantly (tests break with every change)
- Non-critical flows where a bug would be annoying but not serious
- When you do not have CI/CD infrastructure to run them automatically
Recommended tools for 2025:
- Playwright: The most complete option. Supports multiple browsers, good API, excellent debugging.
- Cypress: Good developer experience, but limited to a single browser per execution.
Recommended number of E2E tests: Between 10 and 30 for a typical medium-sized project. They cover the critical happy paths and the most important error cases.
Contract Tests: For Service Architectures
If your project has multiple services that communicate with each other (microservices, APIs between frontend and backend), contract tests verify that both parties agree on the communication format.
How they work:
- The API consumer defines a contract: “I expect that when I call GET /users/1, you return a JSON with name and email fields.”
- The API provider verifies that it fulfills that contract.
- If either side changes the contract without updating it, the test fails.
Tools: Pact is the de facto standard for contract tests.
When to use them: When frontend and backend are developed by different teams, or when you have multiple services that need to stay synchronized.
Testing Strategy by Project Phase
Discovery and Prototyping Phase (Weeks 1-4)
In this phase, the goal is to validate ideas quickly. The code will change drastically. It does not make sense to invest heavily in automated tests.
The minimum necessary:
- Unit tests for critical business logic (calculations, validations)
- Structured manual tests (QA checklists for each delivery)
- Linting and static analysis configured from day one
What you do not need yet:
- Complete E2E tests
- High code coverage
- Exhaustive integration tests
Construction Phase (Weeks 4-12)
This is the phase where the bulk of the system is built. This is where testing investment pays dividends.
Recommended strategy:
- Unit tests for all new business logic (70-80% coverage in domain services)
- Integration tests for database interactions and external APIs
- 5-10 E2E tests for the main critical flows
- CI/CD configured to run tests on every push
- Code review with verification that new code has tests
The 80/20 rule: 80% of your tests’ value comes from 20% of the code. Identify the critical parts and focus there.
Stabilization and Delivery Phase (Weeks 12+)
Before final delivery, the focus shifts from building features to ensuring everything works correctly.
Key actions:
- Expand E2E tests to cover secondary flows
- Regression tests for previously delivered features
- Basic performance tests (load times, slow queries)
- Basic security tests (SQL injection, XSS, authentication)
- User acceptance testing with the client (UAT)
Maintenance Phase
After delivery, the system enters maintenance. Here, existing tests are your safety net.
Recommended practice: Every reported bug becomes an automated test before being fixed. This ensures the bug never reappears and that the test suite improves over time.
TDD in Custom Projects: Yes or No
What TDD Actually Is
Test-Driven Development is a development discipline where you write the test before the code:
- Write a test that fails (red)
- Write the minimum code to make it pass (green)
- Refactor the code while keeping tests green (refactor)
This red-green-refactor cycle repeats continuously.
When TDD Adds Value in Custom Projects
Yes, use TDD for:
- Complex business logic with many rules and edge cases
- Critical calculation algorithms (prices, taxes, commissions)
- Features with complex validation rules
- Code that will be maintained by other developers
No, do not use TDD for:
- Prototypes and proofs of concept
- User interfaces that change constantly
- Infrastructure configuration
- Code that connects components without its own logic (trivial controllers, adapters)
The Pragmatic Approach
In custom projects, reality usually falls between pure TDD and no tests at all. A pragmatic approach:
Tests-first for critical logic. When working on calculations, validations, or complex business rules, write tests first. It forces you to think about edge cases before writing the code.
Tests-after for the rest. For controllers, orchestration services, and infrastructure code, write tests after implementation. Still better than having no tests.
No tests for the trivial. Do not write tests for code that is obviously correct or that is easier to verify manually.
Automation and CI/CD
The Minimum Viable Pipeline
Every custom project should have, at minimum, a CI/CD pipeline that runs:
- Linting and formatting: ESLint, Prettier, or equivalents. Detect trivial errors and maintain code consistency.
- Unit tests: Run in seconds. If they fail, the merge is blocked.
- Integration tests: Run in minutes. If they fail, the merge is blocked.
- Build: Verify that the project compiles correctly.
Recommended tools: GitHub Actions for most projects. It is free for private repositories with generous limits and its configuration is straightforward.
E2E Tests in CI/CD
E2E tests are the slowest and most fragile. Running them on every push can slow down the development cycle.
Recommended approach:
- Run E2E tests before merge to the main branch (not on every push to feature branches)
- Run E2E tests nightly as a regression check
- Keep the number of E2E tests below 30 so execution completes in under 15 minutes
Testing Environments
Local development environment: Each developer can run unit and integration tests on their machine. Use Docker Compose to spin up dependencies (database, external services).
Staging environment: Production replica where E2E tests are run and manual tests are performed before deployment.
Production environment: Monitoring and alerts, not active tests. Optionally, post-deployment smoke tests that verify critical endpoints respond correctly.
Common Testing Mistakes
Writing Tests That Add No Value
Not all tests are useful. Tests that verify implementation instead of behavior break with every refactoring without catching real bugs.
Example of a valueless test: Verifying that a method calls another specific method with specific parameters. If you change the internal implementation (but the result is the same), the test fails.
Example of a valuable test: Verifying that, given a specific input, the output is as expected. It does not matter how it calculates internally, what matters is that the result is correct.
Ignoring Flaky Tests
Tests that fail intermittently are a cancer. If your team starts ignoring test failures because “they sometimes fail,” you have lost confidence in your test suite.
Strict rule: Every flaky test is investigated and fixed within a maximum of 48 hours. If it cannot be fixed, it is temporarily removed and a task is created to rewrite it.
Not Maintaining Tests
Tests are code. They need the same practices as production code: refactoring, updating, and deletion when no longer relevant.
Dedicate 10% of each sprint’s time to test maintenance. Update fixtures, remove obsolete tests, refactor complex tests, and improve error messages.
Measuring Coverage as a Quality Metric
Code coverage measures how much code is executed during tests. It does not measure test quality.
You can have 100% coverage with tests that verify nothing useful. And you can have 50% coverage with tests that catch all critical bugs.
Use coverage as an indicator, not a goal. If coverage drops below a threshold (for example, 60%), investigate. But do not optimize to raise coverage.
Decision Framework for Teams
How Much to Invest in Testing
The answer depends on three factors:
System criticality. A financial system needs more testing than an internal CMS. An error in a payroll calculation is much more serious than an error in a dashboard widget.
Maintenance duration. If the system will be maintained for years, the testing investment amortizes over time. If it is a 3-month prototype, invest less.
Team size. With more people touching the code, you need tests more as a safety net. A 2-person team that communicates well can rely more on shared knowledge.
Recommended Investment Table
| Project type | Unit tests | Integration tests | E2E tests | Testing investment |
|---|---|---|---|---|
| Prototype/MVP | Critical logic only | Minimal | None | 5-10% of time |
| Medium project | Business logic | Main flows | 5-10 critical | 15-20% of time |
| Critical project | Exhaustive | Exhaustive | 20-30+ | 25-30% of time |
Conclusion: Testing as Investment, Not Cost
Testing in custom projects is not a luxury or an insurance policy against disasters. It is an investment with measurable returns.
Three principles for a pragmatic testing strategy:
- Start with the critical. Identify the flows that generate revenue or where a failure would have serious consequences. Those get tested first.
- Automate the repetitive. Any test you will run more than 3 times deserves to be automated.
- Measure results, not coverage. Count the bugs that reach production. If they decrease, your strategy is working.
If you need help defining the testing strategy for your custom software projects, at NERVICO we integrate testing as a fundamental part of the development process, not as a separate phase at the end.