An established application can run a business successfully for years while having very few automated tests. The problem becomes visible when a developer must change a discount calculation, a stock reservation or an approval rule and cannot explain what else might be affected. The team then relies on a long manual checking routine that nobody wants to shorten.
The first testing project should protect important behaviour and make the next change safer. Trying to cover every class at once can consume time without creating equivalent confidence. A smaller set of meaningful checks, selected around business consequences, provides a more useful foundation for an application that must continue evolving.
Find the behaviours people are afraid to change
Ask developers, support staff and business owners which changes make them nervous. Compare their answers with recent incidents and recurring manual checks. A calculation that is rarely changed but financially significant may deserve attention before a frequently used screen whose failures are easy to spot and correct.
For a hypothetical equipment-hire business, the first candidates might be overlapping reservations, partial returns and discounts applied across several hire periods. Write down concrete inputs and expected outcomes. Include ordinary cases, boundaries and the exceptions that experienced staff know from memory.
Rank candidates using consequence, likelihood of change and difficulty of detecting a failure. The ranking does not need false numerical precision. It needs enough explanation that the team can agree why one behaviour deserves protection now and another can wait until relevant code changes.
Capture current behaviour without declaring it correct
Where the intended rule is unclear, begin by observing representative examples. Characterisation checks can record what the application currently does and reveal unexpected changes during refactoring. Label them honestly: they preserve an observed result until the business decides whether that result should remain.
Do not silently turn a known defect into an acceptance requirement. If a historical calculation produces an incorrect outcome, record the disagreement separately and obtain a clear decision about the desired rule. A test should help resolve uncertainty, not hide it behind a green result.
Keep examples understandable outside the code. A short table of dates, quantities and expected charges can support a productive discussion with an operations owner. Once the rule is agreed, translate those examples into executable checks that retain the business meaning in their names and assertions.
Choose the test boundary that can catch the failure
A focused unit test suits a calculation or state transition that can be exercised without infrastructure. An integration test is more appropriate when the risk concerns routing, persistence or several components working together. A browser journey can verify a small number of critical interactions that lower-level tests cannot demonstrate.
Microsoft's unit-testing guidance emphasises readable, resilient tests, while its ASP.NET Core integration-testing documentation describes testing through an application host. Use those approaches according to the behaviour being protected, rather than selecting one test type for every concern. Unit-testing guidance and ASP.NET Core integration tests.
For reservation overlap, test the date rule directly and add an integration check for competing stored reservations where necessary. Mocking every database interaction may miss the exact behaviour that matters. Conversely, launching a browser for every arithmetic case makes feedback slower without necessarily increasing confidence.
Create small seams around difficult dependencies
Legacy code often reads the current time, calls an external service or opens a database connection inside the rule being changed. Introduce the smallest practical boundary that lets the relevant behaviour be exercised deliberately. Avoid turning the testing task into an unrelated architectural rewrite.
For the hire example, supplying the effective date to a charge calculation may be enough to make several important cases deterministic. A controlled interface around a payment provider can allow tests to represent rejection or an uncertain response. The seam should expose meaningful choices rather than mirror every implementation detail.
Review the production change as carefully as the tests. A refactor intended to improve testability can still alter behaviour. Keep the step small, compare the agreed examples and use integration checks where the dependency boundary affects the actual application workflow.
Make failures useful to the next developer
Name checks around the condition and expected outcome. When a test fails, the developer should understand the business issue without inspecting a long arrangement of unrelated objects. Use representative values and minimise setup that distracts from the behaviour under review.
Control sources of inconsistency such as time, ordering and shared state. Tests that fail unpredictably quickly lose authority, especially in a team already under delivery pressure. Investigate repeated instability rather than normalising reruns until the build happens to pass.
Ensure important assertions can detect a wrong implementation. A test that merely checks the same calculation written a second time may reproduce the same mistake. Use agreed expected examples, boundary cases and explicit outcomes. For significant rules, reviewing the test with someone who understands the business can expose a mistaken assumption early.
Grow coverage through real maintenance work
Add protection when the team changes an untested rule or fixes a meaningful defect. A regression case should demonstrate the failure and verify the intended correction. Over time, this builds a suite connected to actual business risks rather than a disconnected campaign to reach a percentage.
Review the suite's operating cost. Remove redundant checks, improve slow feedback and keep the most useful failures visible in the delivery process. Coverage measures can identify unexercised code, but they do not establish that the right business outcomes were checked or that the examples are representative.
Worktechlabs can help strengthen existing .NET applications through focused testing and small, reviewable changes. Start with the rule that makes the next release uncertain, connect its examples to the delivery pipeline, and expand from evidence about where the application most needs protection.
Official sources and further reading
- Microsoft: unit-testing best practices — readable tests and maintainable feedback.
- Microsoft: integration tests in ASP.NET Core — testing application behaviour through the framework host.

