What’s a Property?

Learn about property-based testing and how to leverage it.

You can make an argument against testing every last bit of code you write. Tests are useful, but they come at a cost. Tests are code that needs to be maintained, aligned with the rest of the codebase, and refactored. The more tests we run, the more time it takes for them to run and for us to make sure our code behaves as expected. Moreover, there’s no guarantee that our code is bulletproof just because we ran tons of tests on it.

A problem with tests and inputs

Manually choosing input for our tests can be challenging. Here are some of the main arguments against it.

Cons of running tests

Problem

Description

Lack of test data

Coming up with actual data for test cases involves identifying reasonable values for different fields or parameters of the components being tested. This task becomes more complex when dealing with a large domain of possible input values.

Trust in code

Relying solely on manually crafted test inputs may lead to limited trust in the code. It’s challenging to consider all possible inputs for a given test, and this increases the risk of overlooking important cases. The human mind may struggle to cover the entire domain of input values.

Selective unit testing

It’s acceptable to omit unit tests for certain components, particularly those containing trivial logic. Similar to excessive comments, excessive tests can become outdated and burdensome to maintain. Well-designed tests and integration tests can cover some portions that are not unit tested.

Importance of comprehensive tests

While some components can be left without unit tests, trusting the tests that are written is crucial. Merely providing a few possible inputs for each test case is insufficient. Testing only a subset of input classes increases the risk of missing edge cases: for example, forgetting to consider the maximum integer value in a summing method.

Usercentric inputs

It’s impossible for software engineers to anticipate all possible user inputs. No matter how much thought is put into generating test inputs, users will always find unexpected scenarios. This highlights the importance of comprehensive testing approaches that can handle diverse inputs.

Property-based testing

Property-based testing can solve this problem. We can use it to improve the way we write both unit and integration tests. The main idea behind property-based testing is that our code should abide by some properties. This shifts our thinking process from “What input might break our program?” to “How should our program actually behave?” This shift is extremely important because it forces us to think about the expected outcome of the test.

Properties are normally stated using a for all-based syntax.

for all {input classes}
such that {conditions}
then {property} holds
...