Pytest — a beginner guide. 📄

Lalitha
DataDrivenInvestor
Published in
7 min readNov 2, 2020

--

Getting started with pytest framework…

Pytest is a open source framework to perform test automation for python. Most excited part for me to get started with pytest is, its so easy to start with and can be used for all types and levels of software testing. Pytest has bunch of libraries which are just like treats given for us. Some of these treats are like fixtures, parameterization, skipping tests and so on.

Photo by Marek Szturc on Unsplash

why pytest stands out

Best example I gave to my friends while talking about pytest is, a house with upgraded features, where as unit tests are like old house with outdated features.

Photo by Ярослав Алексеенко on Unsplash

Naming conventions of pytest -

  • Pytest file should start with either test_filename or filename_test
  • Naming convention for all functions should start with test_

Topics covered in this blog,

  • Creating a pytest file.
  • Basic pytest commands
  • Assert statement
  • Why naming conventions for functions are mandatory
  • Substring matching
  • Fixtures
  • conftest.py

Let’s see some of the commands to get started with pytest,

Pytest installation -

With python installation comes pip- a package management system used to install and manage software packages written in Python.

pip install pytest

Pytest version check ,

To check latest version of pytest, execute the following command prompt,

pytest --version

Help information,

To display help information such as reportings, pytest warnings,test seesion debugging and configuration etc… execute the following command prompt,

pytest -h

Creating a pytest file -

Now , lets start with our first pytest program.

First we have to create a directory and then, create a test file in the directory.

Here, Test is a package created under a python project then create a new python file.

Now lets see the operations in first test file — test_stringOps.py

Write following code to file, in which basic string operations are performed…

# Replacing String with another string
def test_strReplace():
string = "Hello, World!"
assert string.replace("H", "J") == "Jello, World!"
# String Split - Splits a string to two substrings
def test_strSplit():
string = "Hello,World"
assert string.split(",") == ["Hello", "World"]
# String Strip
def test_strStrip():
string = " Hello, World! "
assert string.strip() == "Hello, World!"
# String Concatenate
def test_strConcat():
string1 = "Hello"
string2 = "World"
assert string1 + string2 == "HelloWorld"

By seeing code, we can understand that everything should be in a function and each function is having an assert statement. Lets see what this assert statement does…

Assert statement

In each and every function, last statement seen is assert statement.

Depending upon the execution of test function , a value is returned. This return value can be either True or False.

In pytest, if an assertion fails in a test function, then that function execution is stopped and next statements in that test function are not executed, and continue to next function.

Now lets execute the above string operations file, this can be done in different ways.

Command that can be used to trigger all the files in the current directory and subdirectories is,

pytest

To execute a particular test file , syntax is

pytest filename.py

example -to execute tests in test_stringOps.py file, command should be

pytest  test_stringOps.py

If we would like to execute only a particular function, then mention function name as,

pytest filename.py::functionname

example — ro execute test_strConcat functionin test_stringOps.py file,

pytest test_stringOps.py::test_strConcat

Why naming convention to a function is mandatory

Here is the example in which we are going to see how naming convention for function works.

File name is test_arithmetic.py, in which four functions are with test_naming convention. Where as third function doesn’t follow the naming convention. If we try to execute this file, only three functions data was collected and the other function multiply() was not passed.

def test_subtract():
a = 6
b = 6
assert a - b == 0
def test_divide():
a = 6
b = 6
assert a / b == 1
def multiply():
a = 6
b = 6
assert a - b == 0
def test_multiply():
a = 6
b = 6
assert a * b == 36

Output,

collected 3 items                                                                                                                                                                                                                         test_arithmetic.py ...                                                                                                                                                                                                              [100%]==================================== 3 passed in 0.06s =======================================================================

Substring matching

Suppose, if we want to run only a specific set of tests, this can be done by marking the tests and run tests based on substring matching.

To execute the tests containing a string in its name we can use the following syntax −

pytest -k <substring> -v

for following tests, lets execute with substing “str”

# Replacing String with another string
def test_strReplace():
string = "Hello, World!"
assert string.replace("H", "J") == "Jello, World!"
# String Split - Splits a string to two substrings
def test_strSplit():
string = "Hello,World"
assert string.split(",") == ["Hello", "World"]
# String Strip
def test_strStrip():
string = " Hello, World! "
assert string.strip() == "Hello, World!"
# String Concatenate
def test_strConcat():
string1 = "Hello"
string2 = "World"
assert string1 + string2 == "HelloWorld"

to execute tests based on matching substring, execute with following command,

pytest -k str -v

This will execute all the test names having the word ‘str’ in its name. In this case, they are test_strReplace() ,test_strSplit() , test_strStrip() and test_strConcat().

test execution process,

test_stringOps.py::test_strReplace PASSED                                                                                                                                                                                           [ 25%]
test_stringOps.py::test_strSplit PASSED [ 50%]
test_stringOps.py::test_strStrip PASSED [ 75%]
test_stringOps.py::test_strConcat PASSED [100%]
============================= 4 passed, 10 deselected in 0.07s =======================================================================

Here comes another important concept of pytest. In the below code str variable is declared twice and what if we have more functions which uses this variable.

# Uppercase
def test_upper():
str ="python"
assert str.upper() == "PYTHON"
# Is Alpha
def test_isalpha():
str = "python"
assert str.isalpha() == True

In order to decrease the length of code, we can keep this input data in a fixture and use it when ever we need. This can be done using Fixtures.

What are Fixtures

Fixtures are functions which give data to other functions. This concept is simple yet most powerful in pytest framework.

These fixtures run before and then execution of test function follows. A function can declared as a fixture by,

@pytest.fixture

Lets, create a fixture for above string operations file and start using the string variable from calling fixture.

import pytest@pytest.fixture
def input_value():
return "python"

So, fixture function name is passed as arguments for corresponding test functions and used where ever needed. Here, in fixture function, string variable is returned. This is accessed by rest of the test functions, instead of repeating the same code again and again.

# Uppercase
def test_upper(input_value):
assert input_value.upper() == "PYTHON"
# Is Alpha
def test_isalpha(input_value):
assert input_value.isalpha() == True

The major advantage of using fixtures reduces the code complexity, length of code and cost as well. For example, while establishing database connection, we can make use of these fixtures to code data regarding setting up connection in one place and make use of it where ever needed.

This type of approach has again a drawback. If we want to use a fixture we can only use it with in a file as its scope is within the file.

Here comes another concept named as conftest.py.

conftest.py

We define a fixture function in a file named conftest.py in order to share the code to multiple test files.

Photo by Ekaterina Shevchenko on Unsplash

Now lets take above example and see how it works. Here we have two test files named as test_strBasicOps.py and test_strPalindrome.py. In both files we are doing some string operations to perform and for these input data is taken from a common fixture named conftest.py.

In conftest.py,

import pytest@pytest.fixture
def input_value():
return "python"

In test_strBasicOps.py,

# Uppercase
def test_upper(input_value):
assert input_value.upper() == "PYTHON"
# Length of a string
def test_len(input_value):
assert len(input_value) == 6

In test_strPalindrome.py,

# Palindrome
def test_isPalindrome(input_value):
assert input_value == input_value[::-1]

Fixture named, input_value is passed as an argument for the functions defined in both the files and executed tests.

Output from both files,

Executing file test_strBasicOps.py using command pytest test_strBasicOps.py

collected 2 items                                                                                                                                                                                                                         test_strBasicOps.py ..                                                                                                                                                                                                              [100%]==================================== 2 passed in 0.04s =======================================================================

Executing file test_strPalindrome.py using command pytest test_strPalindrome.py

__________________________________
test_isPalindrome
______________________________________
input_value = 'python' def test_isPalindrome(input_value):
if input_value == input_value[::-1]:
assert True
else:
> assert False
E
assert False
test_strPalindrome.py: 7: AssertionError
== == == == short
test
summary
info == == == == == == == == == == == == == == == == == == == == == ==
FAILED
test_strPalindrome.py::test_isPalindrome -
assert False
== == == == == = 1
failed in 0.14
s == == == == == == == == == == == == == == == == == == == == == == ==

here assertion failed as output doesn’t match.

Let’s discuss about other libraries and functionalities of pytest in my next blog.

Thanks for reading…!!

--

--