You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Many of our test suites use a mixin class to reduce re-writing code in multiple test files. For example, in the Tables test suite there is a `_shared` directory containing two of these mixin classes, a [sync one](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/tables/azure-data-tables/tests/_shared/testcase.py) and an [async version](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/tables/azure-data-tables/tests/_shared/asynctestcase.py). These classes will often have ways to create connection strings from an account name and key, formulate the account url, configure logging, or validate service responses. In order for these mixin classes to be used by both the functional and unit tests they should inherit from `object`. For example:
6
+
-[Mixin classes](#test-mixin-classes)
7
+
-[Pre-test setup](#pre-test-setup)
8
+
-[xunit-style setup](#xunit-style-setup)
9
+
-[Fixture setup](#fixture-setup)
14
10
15
-
```python
11
+
## Mixin classes
12
+
Many of our test suites use a base/mixin class to consolidate shared test logic. Mixin classes can define instance attributes to handle environment variables, make complex assertions, and more. By inheriting from these mixins, test classes can then share this logic throughout multiple files.
13
+
14
+
For example, in the Tables test suite there is a `_shared` directory containing two of these mixin classes: a [sync version](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/tables/azure-data-tables/tests/_shared/testcase.py) and an [async version](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/tables/azure-data-tables/tests/_shared/asynctestcase.py).
The Azure SDK team has created some in house tools to help with easier testing. These additional tools are located in the `devtools_testutils` package that was installed with your `dev_requirements.txt`. In this package are the preparers that will be commonly used throughout the repository to test various resources. A preparer is a way to programmatically create fresh resources to run our tests against and then deleting them after running a test suite. These help guarantee standardized behavior by starting each test group from a fresh resource and account.
84
+
## Pre-test setup
85
+
Tests will often use shared resources that make sense to set up before tests execute. There are two recommended
86
+
approaches for this kind of setup, with each having benefits and drawbacks.
83
87
84
-
If this situation is a requirement for your tests, you can opt to create a new preparer for your service from the management plane library for a service. There are already a few preparers built in the [devtools_testutils](https://github.com/Azure/azure-sdk-for-python/tree/main/tools/azure-sdk-tools/devtools_testutils). Most prepares will start with the [`ResourceGroupPreparer`](https://github.com/Azure/azure-sdk-for-python/blob/main/tools/azure-sdk-tools/devtools_testutils/resource_testcase.py#L29-L99) to first create a resource group for your service.
85
-
86
-
To build your own preparer you will need to use the management plane library to create a service and pass the credentials you need into your tests. The two important methods for a preparer are the `create_resource` and `remove_resource` methods. In the `create_resource` method you will use the management client to create the resource and return a dictionary of key-value pairs. The keys will be matched with the test method parameters and passed in as positional arguments to the test. The `remove_resource` method will clean up and remove the resource to prevent a backlog of unused resources in your subscription. For examples of each of these methods, check out these examples:
87
-
88
-
| Preparer |`create_resource`|`remove_resource`|
89
-
|-|-|-|
90
-
| Resource Group |[link](https://github.com/Azure/azure-sdk-for-python/blob/main/tools/azure-sdk-tools/devtools_testutils/resource_testcase.py#L57-L85)|[link](https://github.com/Azure/azure-sdk-for-python/blob/main/tools/azure-sdk-tools/devtools_testutils/resource_testcase.py#L87-L99)|
Alternatively, the class-level `setup_class` method runs once before all tests, but doesn't give you access to all
114
+
instance attributes on the class. You can still set attributes on the test class to reference from tests, and
115
+
module-level utilities can be used in place of instance attributes, as shown in the example above.
115
116
116
-
valid_table_name ="validtablename"
117
-
table = client.create_table(valid_table_name)
117
+
### Fixture setup
118
+
Pytest has documentation explaining how to implement and use fixtures:
119
+
https://docs.pytest.org/en/latest/how-to/fixtures.html. For example, in a library's `conftest.py`:
118
120
119
-
assert valid_table_name == table.table_name
121
+
```python
122
+
from devtools_testutils.azure_recorded_testcase import get_credential
123
+
124
+
@pytest.fixture(scope="session")
125
+
defsetup_teardown_fixture():
126
+
# Note that we can't reference AzureRecordedTestCase.get_credential but can use the module-level function
127
+
client = ServiceClient("...", get_credential())
128
+
client.set_up_resource()
129
+
yield# <-- Tests run here, and execution resumes after they finish
130
+
client.tear_down_resources()
120
131
```
121
132
122
-
This test uses preparers to create resources, then creates a table, and finally verifies the name is correct.
123
-
124
-
Notes:
125
-
1. This test is aiming to create a new Table, which requires a storage account, which in hand requires a resource group. The first decorator (`@ResourceGroupPreparer()`) creates a new resource group, and passes the parameters of this resource group into the `@StorageAccountPreparer()` which creates the storage account. The parameters from the storage account creation is passed into the signature of `test_create_table` .
126
-
2. The `create_client_from_credential` is used again but this time with `storage_account_key` instead of getting a credential from the `self.get_credential` method showed in the previous section. The storage account preparer returns the key for the account which is a valid credential.
127
-
133
+
We can then request the fixture from a test class:
128
134
129
-
### Example 3: Cached Preparer Usage
130
135
```python
131
-
import os
132
-
import pytest
133
-
134
-
from azure.core.exceptions import ResourceExistsError
The first test is the same as above, the second test tries to create a table that already exists and asserts that the correct type of error is raised in response. These tests use cached preparers unlike the previous example.
167
-
168
-
Notes:
169
-
1. The cached preparers here will first look to see if an existing resource group or storage account exists with the given parameters, in this case the `name_prefix`. For more information on what parameters differentiate a new resource group or storage account look for the `self.set_cache()` method in the preparer source code [here](https://github.com/Azure/azure-sdk-for-python/blob/main/tools/azure-sdk-tools/devtools_testutils/storage_testcase.py#L49). The advantage to using a cached preparer is the time saver to re-using the same resource instead of creating a new resource for each test. However, this can increase the possibility that you have to be more exact about cleaning up the entities created in between test runs.
141
+
By requesting a fixture from the test class, the fixture will execute before any tests in the class do. Fixtures are the
142
+
preferred solution from pytest's perspective and offer a great deal of modular functionality.
170
143
171
-
## mgmt_settings_real file
144
+
As shown in the example above, the
145
+
[`yield`](https://docs.pytest.org/latest/how-to/fixtures.html#yield-fixtures-recommended) command will defer to test
146
+
execution -- after tests finish running, the fixture code after `yield` will execute. This enables the use of a fixture
147
+
for both setup and teardown.
172
148
173
-
A `mgmt_settings_real.py` can be used in place of a `.env` file by copying `sdk/tools/azure-sdk-tools/devtools_testutils/mgmt_settings_fake.py` to `sdk/tools/azure-sdk-tools/devtools_testutils/mgmt_settings_real.py` and providing real credentials to it. The following changes need to be made to the `mgmt_settings_real.py` file:
149
+
However, fixtures in this context have similar drawbacks to the `setup_class` method described in
150
+
[xunit-style setup](#xunit-style-setup). Since their scope is outside of the test class, test class instance utilities
151
+
can't be accessed and class state can't be modified.
174
152
175
-
1. Change the value of the `SUBSCRIPTION_ID` variable to your organizations subscription ID, which can be found in the "Overview" section of the "Subscriptions" blade in the [Azure portal](https://portal.azure.com/).
176
-
2. Define `TENANT_ID`, `CLIENT_ID`, and `CLIENT_SECRET`, which are available after creating a Service Principal or can be retrieved from the Azure Portal after creating a Service Principal. Check out the [Azure docs](https://docs.microsoft.com/cli/azure/ad/sp?view=azure-cli-latest#az_ad_sp_create_for_rbac) to create a Service Principal with a simple one line command to create one. The recommended practice is to include your alias or name in the Service Principal name.
177
-
3. Change the [`get_azure_core_credentials(**kwargs):`](https://github.com/Azure/azure-sdk-for-python/blob/main/tools/azure-sdk-tools/devtools_testutils/mgmt_settings_fake.py#L39-L53) function in the `mgmt_settings_real.py` file to construct and return a `ClientSecretCredential` object. Pass in the `CLIENT_ID`, `CLIENT_SECRET`, and `TENANT_ID` values to the `ClientSecretCredential` object. This method should look like this:
178
-
```python
179
-
defget_azure_core_credentials(**kwargs):
180
-
from azure.identity import ClientSecretCredential
181
-
import os
182
-
return ClientSecretCredential(
183
-
client_id=CLIENT_ID,
184
-
client_secret=CLIENT_SECRET,
185
-
tenant_id=TENANT_ID
186
-
)
187
-
```
153
+
By convention, fixtures should be defined in a library's `tests/conftest.py` file. This will provide access to the
154
+
fixture across test files, and the fixture can be requested without having to manually import it.
0 commit comments