Software Testing Good Practices

As an effort to improve the overall quality and stability of LiteFarm platform, the team has created this guide to be followed by our engineers and contributors. 

General recommendations

As on the classic Clean Code book, the tests must follow the FIRST rules, which stand for:

Fast. Tests should run quickly, so that are run frequently (e.g. on every commit). Otherwise, the maintainers won’t want to run them, making the code unstable.

Independent. Tests should not depend on each other. One test should not set up the conditions for the next test. You (or the CI pipeline) should be able to run each test independently and run the tests in any order. Otherwise, it makes it really hard to maintain the state of the tests.

Repeatable. Tests should be repeatable in any environment. You should be able to run the tests in any environment, ideally with no dependencies on external factors (e.g. network connection). If your tests have environment dependencies, it makes it harder to execute them.

Self-Validating. The tests should be deterministic with a true/false result. Either they pass or fail, without any further validation to address.

Timely. The tests need to be written in a timely fashion. Unit tests should be written in advance. It can be even before the code they test, but no further than the release of the code. This in order to make the tests easier to maintain. Creating technical debt to create tests for deployed code is hard.

 

Additionally, follow the Testing Patterns from Test Driven Development: By Example, which are:

Child Test. Start with testing small pieces of your code. Then integrate them into larger tests as needed.

Mock Object. Do not test dependencies, prefer adding mocks of them. Write the tests to test your code, not the code from your dependencies. This also makes it clear if adding a dependency is just adding complexity. Quite useful specially when running tests that depend on external libraries or systems. 

Crash Test Dummy. Create tests not only for the happy paths. Make scenarios what-if something fails (badly). This provides insights on how the system may behave on error conditions.

Clean Check-In. Before pushing any new code, ensure all the code is updated and all the tests are passing. This is important for team collaboration and achieving a good development pace.

Testing the Backend

Unit test first

  • Any new code must have unit tests, either a service, a helper or a controller, must be accompanied with a test. The only exception must be any new model as long as no business logic is included there.

  • The test execution must be completely independent of the rest of the tests. Test must use mockups to avoid any call to external dependencies.

  • Unit Tests must be located on the same path where the original code is. Tests file must be named like: originalCode.tests.js.

  • Tests are build using Jest framework with Mocha and Chai assertion libraries plus Sinon or Jest mocks.

  • All unit tests must be executed locally and as part of the CI pipeline.

Automated API tests.

  • After creating the unit tests, create integration tests with full calls to the API server (no mocks).

  • Each test must be enclosed by a test case, covering a single endpoint (based on the routes). Test files must be named like: endpoint.tests.js, and reside within the path src/tests/endpointName.

  • To avoid test failures, each test suite must include within a mechanism to isolate and refresh the database being used to test.

  • Tests are build using Jest framework with Mocha and Chai assertion libraries.

  • Every user to run the tests must be created prior to executing a single test.

  • All API tests must be executed before deploying to an environment (beta.litefarm.org and litefarm.org) as part of a CI pipeline.

 

Testing the Frontend

Unit test of React Components.

  • Every new React Component must include tests using Enzyme and Jest, mocking any calls to the API.

  • Tests must cover: the visibility of a component, the load of nested components, the props that must be passed, any state changes based on user interaction and expected calls to downstream code must be mocked.

  • Tests must be within the same path where the component code is. Tests file must be named like: originalCode.tests.js.

  • All tests must be executed on every commit locally and as part of CI/CD piepline.

 

Integration tests to the UI

  • Integrations tests to the UI must include testing the overall functionality of litefarm.org website.

  • Tests of the UI must be done using Cypress framework.

  • The user to run the tests must be created prior to running the tests.

  • The data to perform the tests shall be based on fake scenarios, using data on the /fixtures path.

  • All integrations tests must be executed before deploying to a new environment (beta.litefarm.org and litefarm.org) as part of a CI pipeline

 

References

8 principles of better unit testing

Why good developers write bad tests

13 tips for writing useful unit tests

Test-Driven React by Trevor Burnham