Skip to content

About Testing

samuelgfeller edited this page Apr 15, 2024 · 2 revisions

Introduction

Automated testing is essential when developing and maintaining software because it allows catching bugs early and greatly increase the confidence that the application works as intended after doing changes in the code.

The code is tested with test cases that execute a function or action and then compare the actual result with the expected result.

Two main types of functional white-box software testing are unit and integration tests.

Unit and Integration testing

Unit tests

Unit tests focus on testing individual units of code in isolation, such as functions, classes, or modules.
The goal is to ensure that each unit operates as expected and produces the correct results independently of other parts of the software.

Integration tests

Integration tests focus on testing how different units or modules of code work together as a cohesive system.
The interaction between components is evaluated to ensure that they communicate and function correctly as a whole.

Integration tests involve simulating user interactions by creating HTTP requests that traverse all the layers of the backend, communicate with the database and other adapters, and return a response.

That way, the overall behavior of the application can be tested efficiently and easily.

Unit vs. Integration tests

For unit tests, all dependencies surrounding this unit must be replaced by test doubles (mocks, stubs, etc.) with predefined return values.
This takes time and effort to set up and maintain.
If a dependency changes, for example, a function name, a parameter, or a return value type, the test double has to be updated in each case.

Unit tests are very precise but not flexible, and they're tightly coupled to the implementation.
This makes sense in some cases, for e.g. when developing a library or when a complex critical function needs to be tested thoroughly to ensure it works as expected.
But in a lot of cases in real-world web applications, it's not worth the effort to test every single component individually when they can be tested together with few lines of code with integration tests.

It can even be counterproductive. If the code is refactored, the unit tests have to be refactored as well, which not only increases the workload but also makes them not a good indicator if the application still works like before.

Ideally, a test should be green before and after refactoring.
This is when writing tests actually makes sense and is worth the extra effort.
The tests make sure that the application works as intended, they're relatively easy to write and robust to changes in the code, meaning they don't have to be re-written all the time.

Test environment

The tests are located in the tests directory with the following structure:

├── Tests
    ├── Integration          # integration tests
    ├── Unit                 # unit tests
    ├── Fixture              # database content to be added as preparation in test db for integration tests
    ├── Provider             # data provider to run the same test cases with different data
    └── Traits               # utility traits (test setup, database connection, helpers)

Documentation about configuration and test setup can be found in Writing Tests.

Running tests

Most IDEs have built-in support for running tests, but they can also be run from the command line. The following command shortcut is defined in the scripts section of the composer.json file:

composer test

Continuous Integration

To run the tests automatically when pushing, GitHub Actions can be used in combination with tools like Scrutinizer or Codecov to get insights into the code quality.

For more information, see GitHub Actions.

Clone this wiki locally