Pytest fails to retrieve environment variables in Azure DevOps because the pipeline task that sets them does not export them into the shell session accessible by the test runner. This article explains how to correctly configure YAML pipelines and task groups to ensure variables are visible during test execution, preventing common "None" or missing key errors.
Understanding Variable Scope in Pipelines
The most frequent cause of this issue is a misunderstanding of how Azure DevOps handles variable scope. When you define a variable in the pipeline settings or inject it via aVariableGroup, it is available to tasks but not automatically exported as a system environment variable for scripts running inside those tasks unless explicitly configured. Pytest, when invoked via a Python script or command line, relies on the standard operating system environment. If the variable was only set within the Azure DevOps context, the underlying process cannot see it.
To resolve this, you must distinguish between pipeline variables and runtime environment variables. Pipeline variables are managed by the agent's job context. Environment variables are part of the OS session. You need a bridge between these two states. The simplest way to create this bridge is using the env: mapping in your YAML steps or by explicitly writing to the environment file.
Configuring YAML Steps Correctly
When defining a step in your.yml file, you can pass variables directly to the script block. This ensures that the subprocess spawned by pytest inherits these values. Do not rely solely on the global variable definition at the top of your pipeline if the variable is dynamic or secret. Instead, map it explicitly in the task definition.
Consider the following structure for a Python script task:
steps:
- script: |
echo "Testing with value: $MY_VAR"
python -m pytest tests/
env:
MY_VAR: $(MyPipelineVariable)
ANOTHER_KEY: $(SecretVariable)
In this example, $(MyPipelineVariable) is resolved by Azure DevOps before the script runs. The env: block then exports these values into the current shell environment. This is the most reliable method for simple scenarios. It guarantees that any child process, including pytest, sees the variable.
If you are using a PythonScript task instead of a generic script task, the syntax changes slightly. The PythonScript task has its own parameter for arguments, but passing environment variables still requires the env property. Always verify that the variable name in the env block matches exactly what your pytest code expects. Case sensitivity matters here.
Handling Secrets and Secure Variables
Secure variables require special handling. You cannot display them in logs, which makes debugging difficult. However, they are still passed to the environment correctly if mapped via theenv block. To verify their presence without exposing the value, use a dummy check in your code rather than printing the variable itself.
For example, in your pytest conftest.py, you might add a debug log that prints the length of the string or a boolean existence check. This confirms the variable is present without violating security protocols. If the variable is missing, the check will fail, indicating a configuration error in the pipeline rather than a logic error in the test.
Using Task Groups for Reusability
For larger projects, hardcoding environment mappings in every step becomes unwieldy. Task groups allow you to define a reusable unit of work that includes specific environment configurations. When you attach a task group to your pipeline, all variables defined within that group are injected into the environment of the tasks contained within it. This approach is particularly useful when multiple jobs share the same testing requirements. Define the required variables once in the task group settings. Ensure that the task group is linked to the correct variable group containing the secrets or static values. This centralizes management and reduces the risk of typos in variable names across different stages of your CI/CD pipeline. Related articles on the same site: Visual Studio Solution: Only One Project Published in Build Pipeline provides insights into build context issues that may affect variable availability in complex solutions. Additionally, VSCode unable to sign commits discusses authentication contexts that often overlap with environment variable management in local development versus pipeline execution.Debugging Missing Variables
If pytest still cannot find the variables after applying the above fixes, perform a diagnostic step. Add a preliminary script task that echoes all environment variables to the console. Use a command likeprintenv on Linux agents or set on Windows agents. Search the output for your specific variable name.
If the variable appears in the list, the issue lies within pytest or your Python code, not the pipeline configuration. Check your os.environ calls or pytest.ini configurations. If the variable is absent from the printout, the problem is upstream. Verify that the variable group is associated with the pipeline. Check for typos in the variable name. Ensure that the stage where pytest runs has access to the variable group; sometimes, restrictions in later stages prevent access to earlier-defined variables.
Another common pitfall is the order of operations. If you are setting a variable dynamically in one step and expecting it in the next, ensure you are using the correct syntax for cross-job variable sharing. Standard variables do not persist across jobs by default. You must use write to file mechanisms or pipeline logging commands to propagate values between distinct job scopes.
Common Pitfalls and Best Practices
Always prefer explicit environment injection over relying on implicit inheritance. Implicit behavior can change with agent updates or different OS versions. Explicit mapping is deterministic and easier to audit. Avoid using hardcoded values in your test scripts. Even for non-secret variables, pulling them from the pipeline allows for easy switching between staging and production environments. This practice aligns with the twelve-factor app methodology and ensures consistency. Finally, document your required environment variables in the README of your repository. List the expected keys and their purpose. This helps new team members understand why certain pipeline variables are necessary and reduces confusion when troubleshooting similar issues in the future.Frequently Asked Questions
Why does pytest see the variable in one job but not another?
Variables do not automatically persist across different jobs in an Azure DevOps pipeline. Each job runs in a fresh context. You must explicitly pass variables using theenv block or use pipeline logging commands to write values to files that subsequent jobs can read.
Can I use pytest markers to handle different environment variables?
Yes, you can use pytest markers to skip tests based on the presence of specific environment variables. For example, you can checkos.getenv('MY_VAR') in a fixture and apply a skip marker if the value is missing. This prevents test failures due to missing configuration in environments where the variable is not required.
How do I debug environment variables without revealing secrets?
Do not print the actual value of secure variables. Instead, print the length of the string or a hash of the first few characters. This confirms that the variable exists and has content without exposing sensitive data in the build logs.What is the difference between a pipeline variable and an environment variable?
A pipeline variable is managed by the Azure DevOps service and is available to tasks within the pipeline context. An environment variable is part of the operating system session. Tasks must explicitly map pipeline variables to environment variables using theenv property for scripts and processes like pytest to access them.
Does the type of agent (Linux vs Windows) affect variable retrieval?
Yes, the syntax for accessing environment variables differs. Linux uses$VAR_NAME while Windows uses %VAR_NAME% in batch scripts or $env:VAR_NAME in PowerShell. Ensure your pytest invocation script uses the correct syntax for the agent OS you are targeting.