Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 33 additions & 10 deletions recipes/automated-testing.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Automated Testing
## Glossary
**Confidence** - describes a degree to which passing tests guarantee that the app is working
**Determinism** - describes how easy it is to determine where the problem is based on the failing test
**Confidence** - describes a degree to which passing tests guarantee that the app is working.
**Determinism** - describes how easy it is to determine where the problem is based on the failing test.
**Use Case** - a potential scenario in which a system receives external input and responds to it. It defines the interactions between a role (user or another system) and a system to achieve a goal.
**Combinatiorial Explosion** - the fast growth in the number of combinations that need to be tested when multiple business rules are involved.

## Testing best practices

Expand Down Expand Up @@ -86,10 +88,26 @@ changes, we can make minimal changes to the test suite and/or mocked data.

### Integration Tests

With these tests, we test the application API endpoints and assert that they are
actually working as expected.
With these tests, we test how multiple components of the system behave together.

**TODO**: do we want to add that we should run full backend for these type of tests?
#### Infrastructure

Running the tests on test infrastructure should be preferred to mocking, unlike in unit tests. Ideally, a full application instance would be run, to mimic real application behavior as close as possible.
This usually includes running the application connected to a test database, inserting fake data into it during the test setup, and doing assertions on the current state of the database. This also means integration test code should have full access to the test infrastructure for querying.
> [!NOTE]
> Regardless of whether using raw queries or the ORM, simple queries should be used to avoid introducing business logic within tests.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dbettini I don't understand what's meant by this. Can you elaborate a bit, please (in reply to this comment)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing you're asking about the note about simple queries? If yes, I'm talking about using simple SELECTs with WHERE, instead of using more complex custom queries, since that could result in the test giving a false positive/negative. E.g. query being too specific, making it impossible to realize that something else exists in the database which would fail the test.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understood it as "don't repeat business logic in tests". So using simple queries to check if something is changed (or unchanged) is preferred over repeating a business model query and asserting the output is correct.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dbettini, do you mean queries used to perform assertions at the end of the test?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MiroDojkic queries used to fetch the data that will be asserted on


However, mocking can still be used when needed, for example when expecting side-effects that call third party services.

#### Entry points

Integration test entry points can vary depending on the application use cases. These include services, controllers, or the API. These are not set in stone and should be taken into account when making a decision. For example:
- A use case that can be invoked through multiple different protocols can be tested separately from them, to avoid duplication. A tradeoff in this case is the need to write some basic tests for each of the protocols.
- A use case that will always be invokeable through a single protocol might benefit enough from only being tested using that protocol. E.g. a HTTP API route test might eliminate the need for a lower level, controller/service level test. This would also enable testing the auth layer integration within these tests, which might not have been possible otherwise depending on the technology used.

Multiple approaches can be used within the same application depending on the requirements, to provide sufficient coverage.

#### Testing surface

**TODO**: do we want to write anything about mocking the DB data/seeds?

Expand All @@ -111,13 +129,16 @@ time decoupling logic testing and endpoint testing.
- To verify the API endpoint performs authentication and authorization.
- To verify user permissions for that endpoint.
- To verify that invalid input is correctly handled.
- To verify the basic business logic is handled correctly, both in expected success and failure cases.
- To verify infrastructure related side-effects, e.g. database changes or calls to third party services.

#### When **not** to use
- For testing of specific function logic. We should use unit tests for those.
- For extensive testing of business logic permutations beyond fundamental scenarios. Integration tests contain more overhead to write compared to unit tests and can easily lead to a combinatorial explosion. Instead, unit tests should be used for thorough coverage of these permutations.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would combinatorial explosion mean, and how would it look like?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is the fast growth in the number of combinations that need to be tested when multiple business rules are involved. At least that's the term I've seen sometimes used in the context of writing tests 😄 It's especially important in integration tests since they have a higher chance of invoking multiple different business rules at the same time. Didn't explain the term because I guessed it could be googled. What do you think? Should a different term be used?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It falls in line with what was previously written that unit tests should be preferred way of testing each unit logic instead of testing multiple units and trying to add enough permutations/combinations to cover all use cases.

Copy link
Member

@kronicker kronicker Oct 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dbettini Another candidate for the glossary?

- For testing third party services. We should assume they work as expected.

#### Best practices
- Test basic API functionality and keep the tests simple.
- Test basic functionality and keep the tests simple.
- Prefer test infrastructure over mocking.
- If the tested endpoint makes database changes, verify that the changes were
actually made.
- Assert that output data is correct.
Expand All @@ -132,9 +153,11 @@ With these tests, we want to make sure our API contract is valid and the API
returns the expected data. That means we write tests for the publically
available endpoints.

Depending on the project setup, API tests can be covered with integration tests.
For example, if the application only has public APIs and more devs than QAs, it
might be a better option to add API testing in integration tests.
> [!NOTE]
> As mentioned in the Integration Tests section, API can be the entry point to the integration tests, meaning API tests are a subtype of integration tests. However, when we talk about API tests here, we are specifically referring to the public API contract tests, which don't have access to the internals of the application.

In the cases where API routes are covered extensively with integration tests, API tests might not be needed, leaving more time for QA to focus on E2E tests.
However, in more complex architectures (e.g. integration tested microservices behind an API gateway), API tests can be very useful.

#### When to use
- To make sure the API signature is valid.
Expand Down