How To Use Pytest With Command Line Options (Easy To Follow Guide)
Have you explored passing command line arguments in Python? If you have, you’re likely familiar with the convenience it offers.
The capability to define and modify runtime behavior through command line arguments is a potent tool, and this is the reason, why it is widely used in the design of various libraries.
However, a question arises: How can you extend this versatility to your unit tests when using Pytest? How can you dynamically adapt the behavior of your tests during their execution?
Let’s say you’re working on a simple Password Generator, and you want to give your users the ability to define runtime options like the length of the password, the number of alphanumeric characters, and the number of special characters.
In Python, can easily implement this using the argparse
or standard input
library, but how do you do pass these to Pytest?
The answer lies in the pytest addoption
feature of Pytest. This feature allows you to define custom command-line options for your tests.
This article will show you how to use the inbuilt pytest addoption
feature to pass command line arguments to your tests.
We’ll start with exploring the need and basic uses of pytest addoption
and how to dynamically pass command line arguments to our Unit Tests.
Then we’ll dive into a practical example to understand how to apply it to your Python applications. We’ll then compare the popular Python library argparse
and pytest addoption
.
Lastly, we’ll address some common issues with pytest addoption
and how to solve or go around them.
Let’s get started!
What You’ll Learn
By the end of this article you should be able to:
- Run tests for programs that expect command line inputs or options.
- Pass arguments from the CLI during test run using
pytest addoption
. - Solve commonly occurring issues when passing arguments to your unit tests.
User Input Arguments For Python Applications
Python stands out as a versatile high-level programming language, widely favored for crafting cutting-edge applications and it’s extensive library base.
Consider a program to register on a server. To run it, you pass in user input details like your username, password, email address, and so on.
These elements represent the means by which you engage with the system, interactively.
The same goes for when running applications interactively from your terminal. You pass in flags that potentially direct or modify the behavior of your program.
What Is Pytest Addoption?
When you’re dealing with intricate code that operates in multiple modes or necessitates user input, it’s helpful to provide users with options, often referred to as CLI arguments or flags.
Python simplifies this process with libraries like Argparse, making it relatively straightforward. But what about achieving the same functionality in Pytest?
Believe it or not, it’s simpler than you might imagine, thanks to the pytest.addoption
feature.
Let’s take a look at the general syntax for pytest addoption
,1
2def pytest_addoption(parser):
parser.addoption("--custom-option", action="store", default="default_value", help="Description of the custom option.")
Let’s break it up,
action
= indicates an action with the given value. This parameter takes one of the following values:
Parameter | Description |
---|---|
store | (Default) Stores the option’s argument as a string |
store_true | (Boolean option) sets the option’s value to True. |
store_false | (Boolean option) sets the option’s value to False. |
store_const | Stores a constant value when the option is specified |
append | Appends each argument to the list |
append_const | Appends a constant value to a list when the option is specified. |
count | counts the number of times the option is specified on the command line |
default
= The default value that will be used if there is no value provided through the command line.help
= Provide the user a helpful description of what the option does
Benefits Of Using Pytest Addoption
The pytest addoption feature is a powerful tool that allows you to dynamically tailor your test behavior, giving you the flexibility to build a more efficient, adaptable, and easily maintainable test suite.
Let’s delve into some key attributes of pytest addoption that make it a valuable asset for Python testing:
- Environment-specific Settings: It enables you to pass environment-specific settings through CLI options. For instance, you can configure different settings for development, staging, user acceptance testing (UAT), and production environments.
Perhaps it’s a logging flag (i.e. ERROR
on dev and INFO
on prod), or type of database (i.e. smaller machine for dev and larger for prod) and so on. You can easily configure these settings using pytest addoption
.
Custom Test Configuration: You have the freedom to define your own custom test configurations, tailoring them to your specific testing requirements.
Integration with Pytest Fixtures: When combined with Pytest Fixtures, it becomes a power tool for testing diverse data inputs or configurations.
Seamless CI/CD Pipeline Integration: pytest addoption facilitates seamless integration with automated CI/CD Pipelines, allowing you to automate your testing processes efficiently.
Configuration Isolation: It aids in pinpointing problems to specific configurations without the need to modify your test code extensively.
Furthermore, you have the option to set default values for these parameters that ensures your tests can run smoothly even if users don’t explicitly provide values for these options. We’ll explore this in more detail later in this article.
Similarities Between Argparse and Pytest Adoption
If you’re familiar with Python’s argparse
, you’ll notice similarities with the pytest adoption
feature.
Both of these tools help you accept user inputs (flags) at runtime.
argparse
helps you to specify and interpret accepted arguments. It also generates documentation automatically.
Let’s take a quick refresher on Argparse to easily understand Pytest Addoption.
Argparse In Practice
Let’s dive into an example that illustrates how we can use the argparse
.
argparseExample.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import argparse
# Declaring a parser
parser = argparse.ArgumentParser()
# Add arguments to parser
parser.add_argument('-firstname', "--firstname", help="Your first name")
parser.add_argument('-lastname', "--lastname", help="Your last name")
# Get the value of the args
args = parser.parse_args()
# Read the value and perform operation
opts = args.firstname +" "+ args.lastname
# Print the result
print("Your full name: ",opts)
Now if you run the help command like the one below,1
python argparseexample.py -h
You’ll get the help text that you set to inform your users about the required value,
Now if you execute a command with a text value like the one below,1
python argparseexample.py -firstname "Alen" -lastname "Walker"
You’ll get,
How To Use Pytest Addoption?
Now let’s use the pytest addoption
feature to write some tests.
Step 1: Create a file named conftest.py
in the same directory as your tests. This file will hold all the options for your test.
Step 2: Now inside your conftest.py
file, create options for the command line like the below,1
2def pytest_addoption(parser):
parser.addoption("--custom-option", action="store", default="default_value", help="Description of the custom option.")
We have already described the parameters action
, default
, and help
at the beginning of our article.
Step 3: Now, access the custom option value in your test functions by using the request
fixture:1
2
3def test_custom_option(request):
custom_value = request.config.getoption("--custom-option")
print(f"Custom option value: {custom_value}")
In the above function request.config.getoption
is used to retrieve the value of the --custom-option
command line option.
Step 4: After you have done all the previous steps correctly and successfully, you can now run your test like the below,1
pytest --custom-option=new_value
Here, replace new_value
with the value that you want to pass in your test. custom-option
will now be available in your test with value new_value
.
Now that we’ve seen the syntax and procedure on how to use pytest addoption
, it’s time to dive into a practical example.
Project Setup
Pre-requisites
In order to run the example please ensure you have the below prerequisites.
- Python (Version: 3.11+)
Getting Started
Here’s our project structure
To get started. clone the Github Repo here, or you can create your own repo by creating a folder and running git init to initialize it.
In this project, we’ll be using Python 3.11.4.
Create a virtual environment and install any requirements (packages) using pip install -r requirements.txt
.
Example Code
Our example code is a simple password generator that generates passwords containing a combination of alphabets and digits.
We’ve deliberately kept it simple (so don’t hold me to it if the code isn’t perfect!)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39import string
import random
def generate_password(length: int, num_alphanumeric: int) -> tuple:
"""
Generate a password of a given length with a given number of alphanumeric characters.
:param length: Total number of characters in the password
:param num_alphanumeric: Number of alphanumeric characters in the password
:return: Password string
"""
lowercase_letters = string.ascii_lowercase
uppercase_letters = string.ascii_uppercase
digits = string.digits
# Ensure the number of alphanumeric characters is within bounds
# num_alphanumeric = max(0, min(num_alphanumeric, length))
length = max(0, max(0, num_alphanumeric))
# Calculate the number of remaining characters needed
num_remaining = length - num_alphanumeric
# Generate a list of alphanumeric characters
alphanumeric_chars = random.sample(
lowercase_letters + uppercase_letters, num_alphanumeric
)
# Generate a list of remaining characters
remaining_chars = random.choices(digits, k=num_remaining)
# Combine the two lists and shuffle
password_chars = alphanumeric_chars + remaining_chars
random.shuffle(password_chars)
# Create the password string
password = "".join(password_chars)
return password, num_remaining
Our function generate_password()
takes two arguments length
and num_alphanumeric
.
The length
argument is the total number of characters in the password and num_alphanumeric
is the number of alphanumeric characters in the password.
These will need to be provided when calling the function.
Test Code
Moving onto the testing, there are several ways to pass the input arguments.
We can pass them directly in the Unit test, but for this example, we’ll explicitly pass them as CLI arguments using the pytest addoption
feature.
Let’s start with defining the 2 options - length
and no_of_alphanum
.
tests/conftest.py
1
2
3
4
5
6
7
8
9
10def pytest_addoption(parser):
parser.addoption(
"--length", action="store", default="0", help="Total number of characters"
)
parser.addoption(
"--no_of_alphanum",
action="store",
default="0",
help="Number of alphanumeric characters",
)
We defined two custom options --length
and --no_of_alphanum
.
Now we can access these options in our test function using the request
fixture.
tests/test_pass_gen.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37import pytest
from src.pass_gen import generate_password
def pass_gen(request) -> tuple:
"""
Fixture to accept the length and number of characters input from the command line.
:param request: pytest request object
:return: Tuple of length and number of alpha num characters
"""
# Taking passed args for length and number of chars
length = int(request.config.getoption("--length"))
no_of_alphanum = int(request.config.getoption("--no_of_alphanum"))
yield length, no_of_alphanum
# Test function
def test_pass_gen(pass_gen) -> None:
"""
Test function to test the password generator.
:param pass_gen: Fixture to accept the length and number of characters input from the command line.
:return: None
"""
length, no_of_alphanum = pass_gen
# Generate the password and collect no of numeric chars
password, num_remaining = generate_password(length, no_of_alphanum)
# Find number of alphanumeric characters
num_of_alpha = sum(c.isalpha() for c in password)
# Check if the password matches the requirements
assert num_of_alpha == no_of_alphanum and len(password) == no_of_alphanum + num_remaining
Here the fixture pass_gen()
is used to retrieve the CLI input values which is then passed into the test_pass_gen
test function.
Running The Test - Default Options
Let’s run the test without specifying any CLI options first.1
pytest
Output:
Perhaps you’re wondering why it ran and what values it use for length
and no_of_alphanum
?
Simple answer, the default
values specified in the conftest.py
file.
Running The Test - Custom Options
To run the test with custom values, you can follow the below example commands,1
pytest --length=10 --no_of_alphanum=6
Output:
Let’s try another one,1
pytest --length=12 --no_of_alphanum=8
Output:
Running this code will pass the custom values for length
and no_of_alphanum
to the test which will then be used to generate the password.
How To Check Available Pytest CLI Options
What if you’re faced with a number of options that are hard to remember?
Alternatively, consider a scenario where another developer has created the code, and you need to review the syntax.
Of course, you can examine the code or expect some form of documentation.
But there’s another easy way.1
pytest --help
This will show you details of ALL options
You can see the details of any custom options added with pytest add_options
in the Custom options
section.
Frequently Asked Questions
How to pass multiple options in Pytest?
To pass multiple arguments in Pytest, first, you need to define the options inside the pytest_addoption()
function with a specific action, default value, and help text.
Then you can create a pytest fixture
for each of the options to retrieve their value.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import pytest
# Defining the options
def pytest_addoption(parser):
parser.addoption("--user_input_01", action="store", default="default value" help="Specify the option")
parser.addoption("--user_input_02", action="store", default="default value" help="Specify the option")
# Pytest fixture to get the value of user_input_01
def user_input_01(request):
return request.config.getoption("--user_input_01")
# Pytest fixture to get the value of user_input_02
def user_input_02(request):
return request.config.getoption("--user_input_02")
You can see that we defined two different pytest fixture to access the value of user_input_01
and user_input_02
.
Now you can use both of the options like the below,1
2
3
4
5
6import pytest
def test_print_name(user_input_01, user_input_02):
print (f"Displaying user_input_01: {user_input_01}")
print(f"Displaying user_input_02: {user_input_02}")
How to use pytest addoption with Pytest INI Config Files?
If you’re unfamilair with pytest ini
, toml
or Pytest config files, this article on Pytest INI covers why and how to use them.
By using the pytest.ini
, you can define the directory to run tests like the below and also available options.1
2
3[pytest]
testpaths="tests/unit/"
addopts= -option1 -option2
Error: Pytest not recognizing added options
Sometimes when you run tests using the Pytest, you may get this kind of error,
So, why did this error happen?
As we’ve already seen the conftest.py
holds the CLI arguments and the the test directory is defined in the pytest.ini
file.
Now when you’re trying to run a test with options, pytest looks for the definition of the arguments.
But if your project missing the pytest.ini
file or placed in the wrong directory, the test can’t find your conftest
file and options, so it shows an error.
Whats the solution? Create a pytest.ini
file and define the test directory or check if your pytest.ini
is placed in the root directory.
Make sure to basically specify the location of your conftest.py
file within your pytest.ini
file.
Should Pytest addoption be implemented in conftest.py?
Yes! The conftest.py
contains all the CLI options like the below,1
2def pytest_addoption(parser):
parser.addoption("--custom-option", action="store", default="default_value", help="Description of the custom option.")
How To Use Pytest addoption With Global Variables?
You can follow the below example to create a global variable using pytest addoption.1
2
3
4
5
6# you should be able to import that
username: str = None
def pytest_configure(config):
global username
username = config.getoption('--user')
Here the username
is a global variable that is used in pytest_configure()
function.
Conclusion
Let’s conclude this article by summarizing the key takeaways:
First of all, we’ve explored the significance of utilizing custom Command Line Interface (CLI) options to effectively manage our code’s functionality.
After that, we delved into the practical application (basic Password Generator) and how to use pytest addoption
to pass command line arguments to our tests.
Lastly, we’ve addressed a few of the most common issues in smoothly using pytest addoption
.
In the realm of software testing, pytest addoption
stands out as a robust and indispensable tool in customising Pytest.
It empowers you to fine-tune the execution and behavior of your program by harnessing the potential of command-line options within the Pytest framework.
This capability can be a game-changer for enhancing your testing processes and ensuring the robustness of your software.
If you have ideas for improvement or like for me to cover anything specific, please send me a message via Twitter, GitHub or Email.
Aditional Reading
- https://www.youtube.com/watch?v=IRp7GtEaJIo
- https://stackoverflow.com/search?q=pytest+addoption&s=7d2fa55f-3df9-4103-a3ef-0b5d0faaf1e5
- https://www.codementor.io/@adammertz/pytest-quick-tip-adding-cli-options-1fpqnnaokc
- https://www.ontestautomation.com/pytest-and-custom-command-line-arguments/
- https://stackoverflow.com/questions/54142179/how-can-we-accept-multiple-values-for-one-command-line-option-in-pytest