|
| 1 | +# Guide for migrating to the test proxy from vcrpy |
| 2 | + |
| 3 | +This guide describes the changes that service SDKs should make to their test frameworks in order to take advantage of |
| 4 | +the Azure SDK test proxy. |
| 5 | + |
| 6 | +Documentation of the motivations and goals of the test proxy can be found [here][general_docs] in the azure-sdk-tools |
| 7 | +GitHub repository, and documentation of how to set up and use the proxy can be found [here][detailed_docs]. |
| 8 | + |
| 9 | +## Update existing tests |
| 10 | + |
| 11 | +### Current test structure |
| 12 | + |
| 13 | +Test classes currently inherit from AzureTestCase, and test methods can optionally use decorators: |
| 14 | + |
| 15 | +```py |
| 16 | +from devtools_testutils import AzureTestCase |
| 17 | + |
| 18 | +class TestExample(AzureTestCase): |
| 19 | + |
| 20 | + def test_example(self): |
| 21 | + ... |
| 22 | + |
| 23 | + @ExamplePreparer() |
| 24 | + def test_example_with_preparer(self): |
| 25 | + ... |
| 26 | +``` |
| 27 | + |
| 28 | +### New test structure |
| 29 | + |
| 30 | +To use the proxy, test classes should inherit from AzureRecordedTestCase and recorded test methods should use a |
| 31 | +RecordedByProxy decorator: |
| 32 | + |
| 33 | +```py |
| 34 | +from devtools_testutils import AzureRecordedTestCase, RecordedByProxy |
| 35 | + |
| 36 | +class TestExample(AzureRecordedTestCase): |
| 37 | + |
| 38 | + @RecordedByProxy |
| 39 | + def test_example(self): |
| 40 | + ... |
| 41 | + |
| 42 | + @ExamplePreparer() |
| 43 | + @RecordedByProxy |
| 44 | + def test_example_with_preparer(self): |
| 45 | + ... |
| 46 | +``` |
| 47 | + |
| 48 | +For async tests, import the RecordedByProxyAsync decorator from `devtools_testutils.aio` and use it in the same |
| 49 | +way as RecordedByProxy. |
| 50 | + |
| 51 | +> **Note:** since AzureRecordedTestCase doesn't inherit from `unittest.TestCase`, test class names need to start |
| 52 | +> with "Test" in order to be properly collected by pytest by default. For more information, please refer to |
| 53 | +> [pytest's documentation][pytest_collection]. |
| 54 | +
|
| 55 | +## Run the tests |
| 56 | + |
| 57 | +### Perform one-time setup |
| 58 | + |
| 59 | +The test proxy is made available for your tests via a Docker container. Some tests require an SSL connection to work, so |
| 60 | +the Docker image used for the container has a certificate imported that you need to trust on your machine. Instructions |
| 61 | +on how to do so can be found [here][proxy_cert_docs]. |
| 62 | + |
| 63 | +### Start the proxy server |
| 64 | + |
| 65 | +There is a [PowerShell script][docker_start_proxy] in `eng/common/testproxy` that will fetch the proxy Docker image if |
| 66 | +you don't already have it, and will start or stop a container running the image for you. You can run the following |
| 67 | +command from the root of the `azure-sdk-for-python` directory to start the container whenever you want to make the test |
| 68 | +proxy available for running tests: |
| 69 | + |
| 70 | +```powershell |
| 71 | +.\eng\common\testproxy\docker-start-proxy.ps1 "start" |
| 72 | +``` |
| 73 | + |
| 74 | +Note that the proxy is available as long as the container is running. In other words, you don't need to start and |
| 75 | +stop the container for each test run or between tests for different SDKs. You can run the above command in the morning |
| 76 | +and just stop the container whenever you'd like. To stop the container, run the same command but with `"stop"` in place |
| 77 | +of `"start"`. In the future, the proxy container will be set up and started automatically when tests are run, and |
| 78 | +starting it manually will be optional. |
| 79 | + |
| 80 | +For more details on proxy startup, please refer to the [proxy documentation][detailed_docs]. |
| 81 | + |
| 82 | +### Record or play back tests |
| 83 | + |
| 84 | +Configuring live and playback tests is done with the `AZURE_TEST_RUN_LIVE` environment variable. When this variable is |
| 85 | +set to "true" or "yes", live tests will run and produce recordings. When this variable is set to "false" or "no", or |
| 86 | +not set at all, tests will run in playback mode and attempt to match existing recordings. |
| 87 | + |
| 88 | +Recordings for a given package will end up in that package's `/tests/recordings` directory, just like they currently |
| 89 | +do. |
| 90 | + |
| 91 | +> **Note:** at this time, support for configuring live or playback tests with a `testsettings_local.cfg` file has been |
| 92 | +> deprecated in favor of using just `AZURE_TEST_RUN_LIVE`. |
| 93 | +
|
| 94 | +### Register sanitizers |
| 95 | + |
| 96 | +Since the test proxy doesn't use [`vcrpy`][vcrpy], tests don't use a scrubber to sanitize values in recordings. |
| 97 | +Instead, sanitizers (as well as matchers and transforms) can be registered on the proxy as detailed in |
| 98 | +[this][sanitizers] section of the proxy documentation. At the time of writing, sanitizers can be registered via the |
| 99 | +`add_sanitizer` method in `devtools_testutils`. |
| 100 | + |
| 101 | +Sanitizers, matchers, and transforms remain registered until the proxy container is stopped, so for any sanitizers that |
| 102 | +are shared by different tests, using a session fixture declared in a `conftest.py` file is recommended. Please refer to |
| 103 | +[pytest's scoped fixture documentation][pytest_fixtures] for more details. |
| 104 | + |
| 105 | +For example, to sanitize URIs in recordings, you can set up a URI sanitizer for all tests in the pytest session by |
| 106 | +adding something like the following in the package's `conftest.py` file: |
| 107 | + |
| 108 | +```python |
| 109 | +from devtools_testutils import add_sanitizer |
| 110 | + |
| 111 | +@pytest.fixture(scope="session") |
| 112 | +def sanitize_uris(): |
| 113 | + add_sanitizer(ProxyRecordingSanitizer.URI, value="fakeendpoint") |
| 114 | +``` |
| 115 | + |
| 116 | +`add_sanitizer` accepts a sanitizer, matcher, or transform type from the ProxyRecordingSanitizer enum as a required |
| 117 | +parameter. Keyword-only arguments can be provided to customize the sanitizer; for example, in the snippet above, any |
| 118 | +request URIs that match the default URI regular expression will have their domain name replaced with "fakeendpoint". A |
| 119 | +request made to `https://tableaccount.table.core.windows.net` will be recorded as being made to |
| 120 | +`https://fakeendpoint.table.core.windows.net`. |
| 121 | + |
| 122 | +## Implementation details |
| 123 | + |
| 124 | +### What does the test proxy do? |
| 125 | + |
| 126 | +The gist of the test proxy is that it stands in between your tests and the service. What this means is that test |
| 127 | +requests which would usually go straight to the service should instead point to the locally-hosted test proxy. |
| 128 | + |
| 129 | +For example, if an operation would typically make a GET request to |
| 130 | +`https://fakeazsdktestaccount.table.core.windows.net/Tables`, that operation should now be sent to |
| 131 | +`https://localhost:5001/Tables` instead. The original endpoint should be stored in an `x-recording-upstream-base-uri` -- |
| 132 | +the proxy will send the original request and record the result. |
| 133 | + |
| 134 | +The RecordedByProxy and RecordedByProxyAsync decorators patch test requests to do this for you. |
| 135 | + |
| 136 | +### How does the test proxy know when and what to record or play back? |
| 137 | + |
| 138 | +This is achieved by making POST requests to the proxy server that say whether to start or stop recording or playing |
| 139 | +back, as well as what test is being run. |
| 140 | + |
| 141 | +To start recording a test, the server should be primed with a POST request: |
| 142 | + |
| 143 | +``` |
| 144 | +URL: https://localhost:5001/record/start |
| 145 | +headers { |
| 146 | + "x-recording-file": "<path-to-test>/recordings/<testfile>.<testname>" |
| 147 | +} |
| 148 | +``` |
| 149 | + |
| 150 | +This will return a recording ID in an `x-recording-id` header. This ID should be sent as an `x-recording-id` header in |
| 151 | +all further requests during the test. |
| 152 | + |
| 153 | +After the test has finished, a POST request should be sent to indicate that recording is complete: |
| 154 | + |
| 155 | +``` |
| 156 | +URL: https://localhost:5001/record/stop |
| 157 | +headers { |
| 158 | + "x-recording-id": "<x-recording-id>" |
| 159 | +} |
| 160 | +``` |
| 161 | + |
| 162 | +Running tests in playback follows the same pattern, except that requests will be sent to `/playback/start` and |
| 163 | +`/playback/stop` instead. A header, `x-recording-mode`, should be set to `record` for all requests when recording and |
| 164 | +`playback` when playing recordings back. More details can be found [here][detailed_docs]. |
| 165 | + |
| 166 | +The RecordedByProxy and RecordedByProxyAsync decorators send the appropriate requests at the start and end of each test |
| 167 | +case. |
| 168 | + |
| 169 | +[detailed_docs]: https://github.com/Azure/azure-sdk-tools/tree/main/tools/test-proxy/Azure.Sdk.Tools.TestProxy/README.md |
| 170 | +[docker_start_proxy]: https://github.com/Azure/azure-sdk-for-python/blob/main/eng/common/testproxy/docker-start-proxy.ps1 |
| 171 | +[general_docs]: https://github.com/Azure/azure-sdk-tools/blob/main/tools/test-proxy/README.md |
| 172 | +[proxy_cert_docs]: https://github.com/Azure/azure-sdk-tools/blob/main/tools/test-proxy/documentation/trusting-cert-per-language.md |
| 173 | +[pytest_collection]: https://docs.pytest.org/latest/goodpractices.html#test-discovery |
| 174 | +[pytest_fixtures]: https://docs.pytest.org/latest/fixture.html#scope-sharing-fixtures-across-classes-modules-packages-or-session |
| 175 | +[sanitizers]: https://github.com/Azure/azure-sdk-tools/blob/main/tools/test-proxy/Azure.Sdk.Tools.TestProxy/README.md#session-and-test-level-transforms-sanitiziers-and-matchers |
| 176 | +[vcrpy]: https://vcrpy.readthedocs.io |
0 commit comments