Skip to content

Commit 5cd858c

Browse files
authored
Updates to tests.md (Azure#23071)
1 parent 803e9e4 commit 5cd858c

File tree

1 file changed

+109
-21
lines changed

1 file changed

+109
-21
lines changed

doc/dev/tests.md

Lines changed: 109 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,19 @@ testing infrastructure, and demonstrates how to write and run tests for a servic
88
- [Set up your development environment](#set-up-your-development-environment)
99
- [Integrate with pytest](#integrate-with-the-pytest-test-framework)
1010
- [Use Tox](#tox)
11-
- [The `devtools_testutils` package](#the-devtoolstestutils-package)
11+
- [The `devtools_testutils` package](#the-devtools_testutils-package)
1212
- [Write or run tests](#write-or-run-tests)
1313
- [Set up the test proxy](#perform-one-time-test-proxy-setup)
1414
- [Set up test resources](#set-up-test-resources)
1515
- [Configure credentials](#configure-credentials)
16+
- [Start the test proxy server](#start-the-test-proxy-server)
1617
- [Deliver environment variables to tests](#deliver-environment-variables-to-tests)
17-
- [Configure live or playback testing mode](#configure-live-or-playback-testing-mode)
1818
- [Write your tests](#write-your-tests)
19+
- [Configure live or playback testing mode](#configure-live-or-playback-testing-mode)
1920
- [Run and record tests](#run-and-record-tests)
21+
- [Sanitize secrets](#sanitize-secrets)
2022
- [Functional vs. unit tests](#functional-vs-unit-tests)
23+
- [Further reading](#further-reading)
2124
- [Deprecated testing instructions](#deprecated-testing-instructions)
2225

2326
## Set up your development environment
@@ -87,13 +90,13 @@ Open the directory for your package in your preferred editor, for example VSCode
8790

8891
As a quick background, the Azure SDK uses the [pytest](https://docs.pytest.org/en/latest/) test runner to support creating unit and functional tests for Track 2 Azure libraries. To intall `pytest` run `pip install pytest` from your virtual environment, you can confirm the installation was successful by running `pytest -V`. The commands will run all files of the form `test_*.py` or `*_test.py` in the provided directory and its subdirectories; for more information check out the [docs][pytest_invocation].
8992

90-
With the pytest test suite you can provide directories or specific tests to run rather than running the entire test suite:
93+
With `pytest` you can provide a either a directory or a specific test file to run:
9194
```cmd
9295
(env) azure-sdk-for-python\sdk\my-service\my-package> pytest tests
9396
(env) azure-sdk-for-python\sdk\my-service\my-package> pytest tests\<test_file.py>
9497
```
9598

96-
In addition you can provide keywords to run specific tests within the suite or within a specific file
99+
In addition you can provide keywords to run specific tests within the suite or within a specific file:
97100
```cmd
98101
(env) azure-sdk-for-python\sdk\my-service\my-package> pytest tests -k <keyword>
99102
(env) azure-sdk-for-python\sdk\my-service\my-package> pytest <test_file.py> -k <keyword>
@@ -202,6 +205,25 @@ az ad sp create-for-rbac --name "{your alias}-tests" --role Contributor
202205
The command will output a set of credentials. Set `AZURE_TENANT_ID` to the value of `"tenant"`, `AZURE_CLIENT_ID` to the
203206
value of `"appId"`, and `AZURE_CLIENT_SECRET` to the value of `"password"`.
204207

208+
### Start the test proxy server
209+
210+
The test proxy has to be available in order for tests to work; this is done automatically with a `pytest` fixture.
211+
212+
Create a `conftest.py` file within your package's test directory (`sdk/{service}/{package}/tests`), and inside it add a
213+
session-level fixture that accepts `devtools_testutils.test_proxy` as a parameter (and has `autouse` set to `True`):
214+
215+
```python
216+
from devtools_testutils import test_proxy
217+
218+
# autouse=True will trigger this fixture on each pytest run, even if it's not explicitly used by a test method
219+
@pytest.fixture(scope="session", autouse=True)
220+
def start_proxy(test_proxy):
221+
return
222+
```
223+
224+
For more details about how this fixture starts up the test proxy, or the test proxy itself, refer to the
225+
[test proxy migration guide][test_proxy_startup].
226+
205227
### Deliver environment variables to tests
206228

207229
To target the correct resources in tests, use the [EnvironmentVariableLoader][env_var_loader] from `devtools_testutils`
@@ -240,21 +262,11 @@ values in live mode, and the fake values specified in the decorator in playback
240262
> `{SERVICE}_TENANT_ID`, `{SERVICE}_CLIENT_ID`, and `{SERVICE}_CLIENT_SECRET` for a service principal when using this
241263
> class.
242264
243-
### Configure live or playback testing mode
244-
245-
"Live" tests refer to tests that make requests to actual Azure resources. "Playback" tests require a recording for each
246-
test; the test proxy will compare the requests/responses that would be made during each test with requests/responses in
247-
the recording.
248-
249-
To run live tests, set the environment variable `AZURE_TEST_RUN_LIVE` to "true" in your environment or `.env` file.
250-
Live test runs will produce recordings unless the environment variable `AZURE_SKIP_LIVE_RECORDING` is set to "true" as
251-
well. To run tests in playback, either set `AZURE_TEST_RUN_LIVE` to "false" or leave it unset.
252-
253265
### Write your tests
254266

255-
In the `tests` directory at the root of your package (`sdk/{service}/{package}/tests`), create a file with the naming
256-
pattern `test_<what_you_are_testing>.py`. The base of each testing file will be roughly the same (in this example we use
257-
Schema Registry for the sake of demonstration):
267+
In your package's `tests` directory (`sdk/{service}/{package}/tests`), create a file with the naming pattern
268+
`test_<what_you_are_testing>.py`. The base of each testing file will be roughly the same (in this example we use Schema
269+
Registry for the sake of demonstration):
258270

259271
```python
260272
import functools
@@ -312,6 +324,16 @@ AzureRecordedTestCase, EnvironmentVariableLoader, recorded_by_proxy`.
312324
If you need logging functionality for your testing, pytest also offers [logging][pytest_logging] capabilities either
313325
inline through the `caplog` fixture or with command line flags.
314326

327+
### Configure live or playback testing mode
328+
329+
"Live" tests refer to tests that make requests to actual Azure resources. "Playback" tests require a recording for each
330+
test; the test proxy will compare the requests/responses that would be made during each test with requests/responses in
331+
the recording.
332+
333+
To run live tests, set the environment variable `AZURE_TEST_RUN_LIVE` to "true" in your environment or `.env` file.
334+
Live test runs will produce recordings unless the environment variable `AZURE_SKIP_LIVE_RECORDING` is set to "true" as
335+
well. To run tests in playback, either set `AZURE_TEST_RUN_LIVE` to "false" or leave it unset.
336+
315337
### Run and record tests
316338

317339
With the `AZURE_TEST_RUN_LIVE` environment variable set to "true", use `pytest` to run your test(s) in live mode.
@@ -325,6 +347,65 @@ recording in this folder will be a `.json` file that captures the HTTP traffic t
325347
matching the file's name. If you set the `AZURE_TEST_RUN_LIVE` environment variable to "false" and re-run tests, they
326348
should pass again -- this time, in playback mode (i.e. without making actual HTTP requests).
327349

350+
### Sanitize secrets
351+
352+
The `.json` files created from running tests in live mode can include authorization details, account names, shared
353+
access signatures, and other secrets. The recordings are included in our public GitHub repository, making it important
354+
for us to remove any secrets from these recordings before committing them to the repository.
355+
356+
There are two primary ways to keep secrets from being written into recordings:
357+
358+
1. The `EnvironmentVariableLoader` will automatically sanitize the values of captured environment variables with the
359+
provided fake values.
360+
2. Sanitizers can be registered via `add_*_sanitizer` methods in `devtools_testutils`. For example, the general-use
361+
method for sanitizing recording bodies, headers, and URIs is `add_general_regex_sanitizer`. Other sanitizers are
362+
available for more specific scenarios and can be found at [devtools_testutils/sanitizers.py][py_sanitizers].
363+
364+
As a simple example of registering a sanitizer, you can provide the exact value you want to sanitize from recordings as
365+
the `regex` in the general regex sanitizer. To replace all instances of the string "my-key-vault" with "fake-vault" in
366+
recordings, you could add something like the following in the package's `conftest.py` file:
367+
368+
```python
369+
from devtools_testutils import add_general_regex_sanitizer, test_proxy
370+
371+
# autouse=True will trigger this fixture on each pytest run, even if it's not explicitly used by a test method
372+
@pytest.fixture(scope="session", autouse=True)
373+
def add_sanitizers(test_proxy):
374+
add_general_regex_sanitizer(regex="my-key-vault", value="fake-vault")
375+
```
376+
377+
Note that the sanitizer fixture accepts the `test_proxy` fixture as a parameter to ensure the proxy is started
378+
beforehand (see [Start the test proxy server](#start-the-test-proxy-server)).
379+
380+
For a more advanced scenario, where we want to sanitize the account names of all storage endpoints in recordings, we
381+
could instead call
382+
383+
```python
384+
add_general_regex_sanitizer(
385+
regex="(?<=\\/\\/)[a-z]+(?=(?:|-secondary)\\.(?:table|blob|queue)\\.core\\.windows\\.net)",
386+
value="fakeendpoint",
387+
)
388+
```
389+
390+
`add_general_regex_sanitizer` accepts a regex, replacement value, and capture group as keyword-only arguments. In the
391+
snippet above, any storage endpoint URIs that match the specified URI regex will have their account name replaced with
392+
"fakeendpoint". A request made to `https://tableaccount-secondary.table.core.windows.net` will be recorded as being
393+
made to `https://fakeendpoint-secondary.table.core.windows.net`, and URIs will also be sanitized in bodies and headers.
394+
395+
For more details about sanitizers and their options, please refer to [devtools_testutils/sanitizers.py][py_sanitizers].
396+
397+
#### Special case: SAS tokens
398+
399+
Tests that use a Shared Access Signature (SAS) token to authenticate a client should use the
400+
[`AzureRecordedTestCase.generate_sas`][generate_sas] method to generate the token. This will automatically register a
401+
sanitizer to keep this token out of test recordings. An example of using this method can be found
402+
[here][generate_sas_example].
403+
404+
`generate_sas` accepts any number of positional arguments: the first being the method that creates the SAS, and the
405+
remaining positional arguments being positional arguments for the SAS-generating method. Any keyword arguments given to
406+
`generate_sas` will be passed to the SAS-generating method as well. The generated token will be returned and its value
407+
will be sanitized.
408+
328409
## Functional vs. unit tests
329410

330411
The tests written above are functional tests: they generate HTTP traffic and send data to the service. For tests that
@@ -383,15 +464,17 @@ class TestTablesUnitTest(object):
383464
```
384465

385466

386-
## More test examples
467+
## Further reading
387468

388-
This section will demonstrate how to write tests with the `devtools_testutils` package with a few samples to showcase the features of the test framework.
389-
390-
For more information, refer to the [advanced tests notes][advanced_tests_notes] on more advanced scenarios and additional information.
469+
For information about more advanced testing scenarios, refer to the [advanced tests notes][advanced_tests_notes].
391470

392471

393472
## Deprecated testing instructions
394473

474+
> The testing framework described in this section was used before today's test proxy was adopted. These instructions are
475+
> deprecated and shouldn't be used to write new tests, but may be helpful in understanding and working with test suites
476+
> that haven't migrated to the new system.
477+
395478
Older SDK tests are based on the `scenario_tests` subpackage located in [`azure-sdk-for-python/tools/azure-devtools/src/azure_devtools`](https://pypi.org/project/azure-devtools/). `scenario_tests` is a general, mostly abstracted framework which provides several useful features for writing SDK tests, ie:
396479
* HTTP interaction recording and playback using [vcrpy](https://pypi.python.org/pypi/vcrpy)
397480
* Creation and cleanup of helper resources, such as resource groups, storage accounts, etc. which can be used in order to test services
@@ -556,6 +639,9 @@ Tests that use the Shared Access Signature (SAS) to authenticate a client should
556639
[engsys_wiki]: https://dev.azure.com/azure-sdk/internal/_wiki/wikis/internal.wiki/48/Create-a-new-Live-Test-pipeline?anchor=test-resources.json
557640
[env_var_loader]: https://github.com/Azure/azure-sdk-for-python/blob/main/tools/azure-sdk-tools/devtools_testutils/envvariable_loader.py
558641

642+
[generate_sas]: https://github.com/Azure/azure-sdk-for-python/blob/6e1f7c02af0c28d5725a532ebe4fc7125256858c/tools/azure-sdk-tools/devtools_testutils/azure_recorded_testcase.py#L200
643+
[generate_sas_example]: https://github.com/Azure/azure-sdk-for-python/blob/3e3fbe818eb3c80ffdf6f9f1a86affd7e879b6ce/sdk/tables/azure-data-tables/tests/test_table_entity.py#L1691
644+
559645
[kv_test_resources]: https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/keyvault/test-resources.json
560646
[kv_test_resources_outputs]: https://github.com/Azure/azure-sdk-for-python/blob/fbdb860630bcc13c1e355828231161849a9bd5a4/sdk/keyvault/test-resources.json#L255
561647
[kv_test_resources_resources]: https://github.com/Azure/azure-sdk-for-python/blob/fbdb860630bcc13c1e355828231161849a9bd5a4/sdk/keyvault/test-resources.json#L116
@@ -566,8 +652,10 @@ Tests that use the Shared Access Signature (SAS) to authenticate a client should
566652
[proxy_cert_docs]: https://github.com/Azure/azure-sdk-tools/blob/main/tools/test-proxy/documentation/trusting-cert-per-language.md
567653
[proxy_general_docs]: https://github.com/Azure/azure-sdk-tools/blob/main/tools/test-proxy/README.md
568654
[proxy_migration_guide]: https://github.com/Azure/azure-sdk-for-python/blob/main/doc/dev/test_proxy_migration_guide.md
655+
[py_sanitizers]: https://github.com/Azure/azure-sdk-for-python/blob/main/tools/azure-sdk-tools/devtools_testutils/sanitizers.py
569656
[pytest_invocation]: https://pytest.org/latest/how-to/usage.html
570657
[pytest_logging]: https://docs.pytest.org/en/stable/logging.html
571658
[python-dotenv_readme]:https://github.com/theskumar/python-dotenv
572659

660+
[test_proxy_startup]: https://github.com/Azure/azure-sdk-for-python/blob/main/doc/dev/test_proxy_migration_guide.md#start-the-proxy-server
573661
[test_resources]: https://github.com/Azure/azure-sdk-for-python/tree/main/eng/common/TestResources#readme

0 commit comments

Comments
 (0)