|
| 1 | +# pytest-sentry |
| 2 | + |
| 3 | +[](https://pypi.org/project/pytest-sentry/) |
| 4 | +[](https://pypi.org/project/pytest-sentry/) |
| 5 | + |
| 6 | +`pytest-sentry` is a [pytest](https://pytest.org>) plugin that uses [Sentry](https://sentry.io/) to store and aggregate information about your testruns. |
| 7 | + |
| 8 | +> [!IMPORTANT] |
| 9 | +> **This is not an official Sentry product.** |
| 10 | +
|
| 11 | + |
| 12 | + |
| 13 | +This is what a test run will look like in Sentry. |
| 14 | + |
| 15 | +## Quickstart |
| 16 | +### Prerequisites |
| 17 | + |
| 18 | +* You are using [pytest](https://pytest.org) to run your tests. |
| 19 | +* You are using [pytest-rerunfailures](https://github.com/pytest-dev/pytest-rerunfailures) to rerun flaky tests. |
| 20 | + |
| 21 | +### Configuration |
| 22 | + |
| 23 | +You can configure `pytest-sentry` with environment variables: |
| 24 | + |
| 25 | +* `PYTEST_SENTRY_DSN`: The Sentry DSN to send the data to. |
| 26 | + |
| 27 | +* `PYTEST_SENTRY_ALWAYS_REPORT`: If not set, only flaky tests are reported as errors. If set to `1` all test failures are reported. If set to `0` no test failures are reported at all. |
| 28 | + |
| 29 | +* `PYTEST_SENTRY_TRACES_SAMPLE_RATE`: The sample rate for tracing data. See https://docs.sentry.io/platforms/python/configuration/options/#traces_sample_rate |
| 30 | + |
| 31 | +* `PYTEST_SENTRY_PROFILES_SAMPLE_RATE`: The sample rate for profiling data. |
| 32 | + |
| 33 | +* `PYTEST_SENTRY_DEBUG`: Set to `1` to display Sentry debug output. See https://docs.sentry.io/platforms/python/configuration/options/#debug |
| 34 | + |
| 35 | +### Running |
| 36 | + |
| 37 | +If you have `pytest-rerunfailures` plugin enabled you set the environment variables and run `pytest` as usual: |
| 38 | +```bash |
| 39 | +export PYTEST_SENTRY_DSN= "https://[email protected]/xxx" # your DSN |
| 40 | +export PYTEST_SENTRY_TRACES_SAMPLE_RATE=1 |
| 41 | +export PYTEST_SENTRY_PROFILES_SAMPLE_RATE=1 |
| 42 | +export SENTRY_ENVIRONMENT="test-suite" |
| 43 | + |
| 44 | +pytest --reruns=5 |
| 45 | +``` |
| 46 | + |
| 47 | +Now all flaky tests will report to the configured DSN in Sentry.io including trace information and profiles of your tests and test fixtures in an Sentry environment calld `test-suite`. |
| 48 | + |
| 49 | +# Tracking flaky tests as errors |
| 50 | + |
| 51 | +Let's say you have a testsuite with some flaky tests that randomly break your |
| 52 | +CI build due to network issues, race conditions or other stuff that you don't |
| 53 | +want to fix immediately. The known workaround is to retry those tests |
| 54 | +automatically, for example using [pytest-rerunfailures](https://github.com/pytest-dev/pytest-rerunfailures). |
| 55 | + |
| 56 | +One concern against plugins like this is that they just hide the bugs in your |
| 57 | +testsuite or even other code. After all your CI build is green and your code |
| 58 | +probably works most of the time. |
| 59 | + |
| 60 | +`pytest-sentry` tries to make that choice a bit easier by tracking flaky test |
| 61 | +failures in a place separate from your build status. Sentry is already a |
| 62 | +good choice for keeping tabs on all kinds of errors, important or not, in |
| 63 | +production, so let's try to use it in testsuites too. |
| 64 | + |
| 65 | +The prerequisite is that you already make use of `pytest` and |
| 66 | +`pytest-rerunfailures` in CI. Now install `pytest-sentry` and set the |
| 67 | +`PYTEST_SENTRY_DSN` environment variable to the DSN of a new Sentry project. |
| 68 | + |
| 69 | +Now every test failure that is "fixed" by retrying the test is reported to |
| 70 | +Sentry, but still does not break CI. Tests that consistently fail will not be |
| 71 | +reported. |
| 72 | + |
| 73 | +# Tracking the performance of your testsuite |
| 74 | + |
| 75 | +By default `pytest-sentry` will send [Performance](https://sentry.io/for/performance/) data to Sentry: |
| 76 | + |
| 77 | +* Fixture setup is reported as "transaction" to Sentry, such that you can |
| 78 | + answer questions like "what is my slowest test fixture" and "what is my most |
| 79 | + used test fixture". |
| 80 | + |
| 81 | +* Calls to the test function itself are reported as separate transaction such |
| 82 | + that you can find large, slow tests as well. |
| 83 | + |
| 84 | +* Fixture setup related to a particular test item will be in the same trace, |
| 85 | + i.e. will have same trace ID. There is no common parent transaction though. |
| 86 | + It is purposefully dropped to spare quota as it does not contain interesting |
| 87 | + information:: |
| 88 | + |
| 89 | + pytest.runtest.protocol [one time, not sent] |
| 90 | + pytest.fixture.setup [multiple times, sent] |
| 91 | + pytest.runtest.call [one time, sent] |
| 92 | + |
| 93 | + The trace is per-test-item. For correlating transactions across an entire |
| 94 | + test run, use the automatically attached CI tags or attach some tag on your |
| 95 | + own. |
| 96 | + |
| 97 | +To measure performance data, install `pytest-sentry` and set |
| 98 | +`PYTEST_SENTRY_DSN`, like with errors. By default, the extension will send all |
| 99 | +performance data to Sentry. If you want to limit the amount of data sent, you |
| 100 | +can set the `PYTEST_SENTRY_TRACES_SAMPLE_RATE` environment variable to a float |
| 101 | +between `0` and `1`. This will cause only a random sample of transactions to |
| 102 | +be sent to Sentry. |
| 103 | + |
| 104 | +Transactions can have noticeable runtime overhead over just reporting errors. |
| 105 | +To disable, use a marker:: |
| 106 | + |
| 107 | + import pytest |
| 108 | + import pytest_sentry |
| 109 | + |
| 110 | + pytestmarker = pytest.mark.sentry_client({"traces_sample_rate": 0.0}) |
| 111 | + |
| 112 | +# Advanced Options |
| 113 | + |
| 114 | +`pytest-sentry` supports marking your tests to use a different DSN, client or |
| 115 | +scope per-test. You can use this to provide custom options to the `Client` |
| 116 | +object from the [Sentry SDK for Python](https://github.com/getsentry/sentry-python): |
| 117 | + |
| 118 | +```python |
| 119 | +import random |
| 120 | +import pytest |
| 121 | + |
| 122 | +from sentry_sdk import Scope |
| 123 | +from pytest_sentry import Client |
| 124 | + |
| 125 | +@pytest.mark.sentry_client(None) |
| 126 | +def test_no_sentry(): |
| 127 | + # Even though flaky, this test never gets reported to sentry |
| 128 | + assert random.random() > 0.5 |
| 129 | + |
| 130 | +@pytest.mark.sentry_client("MY NEW DSN") |
| 131 | +def test_custom_dsn(): |
| 132 | + # Use a different DSN to report errors for this one |
| 133 | + assert random.random() > 0.5 |
| 134 | + |
| 135 | +# Other invocations: |
| 136 | + |
| 137 | +@pytest.mark.sentry_client(Client("CUSTOM DSN")) |
| 138 | +@pytest.mark.sentry_client(lambda: Client("CUSTOM DSN")) |
| 139 | +@pytest.mark.sentry_client(Scope(Client("CUSTOM DSN"))) |
| 140 | +@pytest.mark.sentry_client({"dsn": ..., "debug": True}) |
| 141 | +``` |
| 142 | + |
| 143 | +The `Client` class exposed by `pytest-sentry` only has different default |
| 144 | +integrations. It disables some of the error-capturing integrations to avoid |
| 145 | +sending random expected errors into your project. |
| 146 | + |
| 147 | +# Accessing the used Sentry client |
| 148 | + |
| 149 | +You will notice that the global functions such as |
| 150 | +`sentry_sdk.capture_message` will not actually send events into the same DSN |
| 151 | +you configured this plugin with. That's because `pytest-sentry` goes to |
| 152 | +extreme lenghts to keep its own SDK setup separate from the SDK setup of the |
| 153 | +tested code. |
| 154 | + |
| 155 | +`pytest-sentry` exposes the `sentry_test_scope` fixture whose return value is |
| 156 | +the `Scope` being used to send events to Sentry. Use `with use_scope(entry_test_scope):` |
| 157 | +to temporarily switch context. You can use this to set custom tags like so:: |
| 158 | + |
| 159 | +```python |
| 160 | + def test_foo(sentry_test_scope): |
| 161 | + with use_scope(sentry_test_scope): |
| 162 | + sentry_sdk.set_tag("pull_request", os.environ['EXAMPLE_CI_PULL_REQUEST']) |
| 163 | +``` |
| 164 | + |
| 165 | +Why all the hassle with the context manager? Just imagine if your tested |
| 166 | +application would start to log some (expected) errors on its own. You would |
| 167 | +immediately exceed your quota! |
| 168 | + |
| 169 | +# Always reporting test failures |
| 170 | + |
| 171 | +You can always report all test failures to Sentry by setting the environment |
| 172 | +variable `PYTEST_SENTRY_ALWAYS_REPORT=1`. |
| 173 | + |
| 174 | +This can be enabled for builds on the `main` or release branch, to catch |
| 175 | +certain kinds of tests that are flaky across builds, but consistently fail or |
| 176 | +pass within one testrun. |
| 177 | + |
| 178 | +# License |
| 179 | + |
| 180 | +Licensed under 2-clause BSD, see [LICENSE](LICENSE). |
0 commit comments