Development Practices

Testing

Automated tests protect projects over time. They make it easier to refactor, add features, and fix bugs without being afraid of breaking something else. Tests also help new developers understand the system by showing how the code is expected to behave.

Unit tests

Unit tests focus on the smallest pieces of code, usually a single function or class. They should be fast, isolated, and run entirely in memory without depending on external systems like a database or API. This makes them reliable and quick to run on every commit.

When writing unit tests, focus on business rules and logic. A good practice is to always add a test when fixing a bug to make sure it never sneaks back in again. Keep tests short and clear, so that they are easy to read and understand even years later.

Could not fetch repo for this block.

Integration tests

Integration tests cover how different parts of the system work together. They might check that a controller talks correctly to a service, that a repository can read and write to a real database, or that caching behaves as expected. Unlike unit tests, integration tests often use real infrastructure, which makes them slower but also more realistic.

The main challenge with integration tests is stability. It is important to reset the test environment between runs so that data does not leak from one test to another. For databases, this usually means running migrations and seeding data before each test suite.

End-to-end tests

End-to-end tests simulate how a real user interacts with the system. They might click through a web page, sign up for an account, or place an order. These tests are the slowest and most fragile, but they are valuable because they confirm that everything works together as expected.

Since they are heavy to run, end-to-end tests should focus on the most important flows: sign-in, checkout, or other business-critical actions. Keeping them stable is often a matter of preparing good test data and waiting for conditions rather than relying on random timeouts.

Could not fetch repo for this block.

Running tests in projects

Different frameworks and stacks have their own testing tools. In Laravel projects, PHPUnit or Pest are used for unit and integration testing, while Laravel Dusk or Playwright can run end-to-end browser tests. In .NET, xUnit is common, often combined with Testcontainers for integration.

All tests should be part of the CI pipeline. Unit and integration tests should run on every push and pull request. End-to-end tests may run on pull requests or nightly, depending on performance requirements. This balance ensures fast feedback while keeping confidence high.

Testing practices

Tests should always be deterministic and give the same result every time. They should not depend on network availability, current time, or global state. Test data is best created with factories or builders, since this makes tests easier to understand and maintain. The test environment should be reset between runs to avoid one test influencing another.

Coverage can be used as a guideline, but it should never be the main goal. A smaller number of meaningful tests is better than a large number of shallow ones. When a bug is discovered, start by writing a test that exposes the bug, and then fix the code. This ensures that the bug will never return unnoticed.

Tests are part of the codebase and should be treated with the same care as production code. They need to be clear, reliable, and easy to maintain. A flaky test is just as much of a problem as a broken feature.