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!

Link To Example Code

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
2
def 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:
ParameterDescription
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_constStores a constant value when the option is specified
appendAppends each argument to the list
append_constAppends a constant value to a list when the option is specified.
countcounts 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:

  1. 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.

  1. Custom Test Configuration: You have the freedom to define your own custom test configurations, tailoring them to your specific testing requirements.

  2. Integration with Pytest Fixtures: When combined with Pytest Fixtures, it becomes a power tool for testing diverse data inputs or configurations.

  3. Seamless CI/CD Pipeline Integration: pytest addoption facilitates seamless integration with automated CI/CD Pipelines, allowing you to automate your testing processes efficiently.

  4. 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
17
import 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,

argsparse example help

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,

argparse example result

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
2
def 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
3
def 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

pytest addoption 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
39
import 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
10
def 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
37
import pytest
from src.pass_gen import generate_password

@pytest.fixture
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:

pytest addoption test result

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:

pytest addoption test result

Let’s try another one,

1
pytest --length=12 --no_of_alphanum=8

Output:

pytest addoption test result

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

pytest cli option

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
17
import 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
@pytest.fixture
def user_input_01(request):
return request.config.getoption("--user_input_01")


# Pytest fixture to get the value of user_input_02
@pytest.fixture
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
6
import pytest

@pytest.mark.unit
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,

pytest not recognizing

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
2
def 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