Targeted property-based testing
Schemathesis supports targeted property-based testing via utilizing hypothesis.target
inside its runner and provides
an API to guide data generation towards certain pre-defined goals:
response_time
. Hypothesis will try to generate input that will more likely to have higher response time;
To illustrate this feature, consider the following AioHTTP request handler, that contains a hidden performance problem - the more zeroes are in the input number, the slower it works, and if there are more than 10 zeroes, it will cause an internal server error:
import asyncio
from aiohttp import web
async def performance(request: web.Request) -> web.Response:
decoded = await request.json()
number = str(decoded).count("0")
if number > 0:
# emulate hard work
await asyncio.sleep(0.01 * number)
if number > 10:
raise web.HTTPInternalServerError
return web.json_response({"slow": True})
Let’s take a look if Schemathesis can discover this issue and how much time it will take:
$ st run --hypothesis-max-examples=100000 http://127.0.0.1:8081/schema.yaml
...
1. Received a response with 5xx status code: 500
Check : not_a_server_error
Body : 58150920460703009030426716484679203200
Run this Python code to reproduce this failure:
requests.post('http://127.0.0.1:8081/api/performance', json=58150920460703009030426716484679203200)
Or add this option to your command line parameters: --hypothesis-seed=240368931405400688094965957483327791742
================================================== SUMMARY ==================================================
Performed checks:
not_a_server_error 67993 / 68041 passed FAILED
============================================ 1 failed in 662.16s ===========================================
And with targeted testing (.hypothesis
directory was removed between these test runs to avoid reusing results):
$ st run --target=response_time --hypothesis-max-examples=100000 http://127.0.0.1:8081/schema.yaml
...
1. Received a response with 5xx status code: 500
Check : not_a_server_error
Body : 2600050604444474172950385505254500000
Run this Python code to reproduce this failure:
requests.post('http://127.0.0.1:8081/api/performance', json=2600050604444474172950385505254500000)
Or add this option to your command line parameters: --hypothesis-seed=340229547842147149729957578683815058325
================================================== SUMMARY ==================================================
Performed checks:
not_a_server_error 22039 / 22254 passed FAILED
============================================ 1 failed in 305.50s ===========================================
This behavior is reproducible in general, but not guaranteed due to the randomness of data generation. However, it shows a significant testing time reduction, especially on a large number of examples.
Hypothesis documentation provides a detailed explanation of what targeted property-based testing is.
Custom targets
You can register your own targets with the schemathesis.target
decorator. The function should accept schemathesis.targets.TargetContext
and return a float value:
import schemathesis
@schemathesis.target
def new_target(context) -> float:
return float(len(context.response.content))
Such a function will cause Hypothesis to generate input that is more likely to produce larger responses.