PyTest Unit Testing Framework with Example.
What is Pytest:
Pytest Benefits:
Pytest Installation:
This tutorial uses Python 3, and we will be working inside a virtualenv. Python 3 has inbuilt support for creating virtual environments. We can very easily create and use a virtual environment for this demo project. We need to run the following commands in the cmd.
mkdir pytest_demo # this will create directory.
cd pytest_demo # move to newly create directory
Python3 -m venv pytest-demo # this creates a virtual environment called pytest-env in our working directory. To see, Run command dir
[on Mac or Unix OS] source pytest-demo/bin/activate. # This activates virtual environment. As long as this virtualenv is active, any package we install will be installed in this new virtual environment.
[on Winodws OS] pytest-demo\Scripts\activate
pip install pytest # This installs pytest on virtual environment.
pytest -h # If pytest installed properly, this will show the help section.
First Unit Test Using Pytest:
What is test?
As per pytest, a test is meant to look at the result of a particular
behavior, and make sure that result aligns with what you would
expect. Behavior is not something that can be empirically measured,
which is why writing tests can be challenging.
You can think of a test as being broken down into four steps:
Arrange
Act
Assert
Cleanup
Arrange is where we prepare everything for our test. This means pretty much everything except for the “act”. It’s lining up the dominoes so that the act can do its thing in one, state-changing step. This can mean preparing objects, starting/killing services, entering records into a database, or even things like defining a URL to query, generating some credentials for a user that doesn’t exist yet, or just waiting for some process to finish.
Act is the singular, state-changing action that kicks off the behavior we want to test. This behavior is what carries out the changing of the state of the system under test (SUT), and it’s the resulting-changed state that we can look at to make a judgement about the behavior. This typically takes the form of a function/method call.
Assert is where we look at that resulting state and check if it looks how we’d expect after the dust has settled. It’s where we gather evidence to say the behavior does or does not align with what we expect. The assert in our test is where we take that measurement/observation and apply our judgement to it. If something should be green, we’d say assert thing == "green".
Cleanup is where the test picks up after itself, so other tests aren’t being accidentally influenced by it.
How Pytest identifies test or test files:
By default, pytest expects tests to be located in files whose names start with test_ or end with _test.py. Also, pytest requires the test function to start with test. Any function/definition which does not start with test will not be considered as test.
How to invoke pytest:
In general, pytest is executed with the command pytest. This will execute all tests in all files starting with test_*.py or ending with *_test.py in the current and its subdirectories.
pytest
Run tests in a bank module
pytest test_bank.py
Run tests in a directory
Pytest testing/
Run a specific test within a module:
Pytest test_bank.py:: test_func
Run tests from packages
Pytest –pyargs pkg.testing
Now, lets understand this with example.
Let's consider that we are crating the bank application where we care new bank account and initialize the bank balance. We will have few more methods to deposit or withdraw cash, Bank account holder name will be capitalized.
Create a source file bank.py under Src folder in your project and add following code into it.
class InSufficientBalance(Exception):
pass
class bank(object):
def __init_(self, name, amount):
self.balance = amount
self.name = name.capitalize()
def deposit(self, amount):
self.balance += amount
def withdraw(self, amount):
if self.balance < amount:
raise InSufficientBalance
self.balance-= amount
def get_balance(self) -> int :
return (self.balance)
def get_account_holder_name(self) -> str :
return(self.name)
We will write tests to test_check_name to check that function does what it says. Let’s create a file called test_bank_1.py under test folder and inside it, we will write a test functions.
# We have our source file in other folder so either we need to set PYTHONPATH or
#insert the parent folder in the path by following way.
import sys, os
myPath = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, myPath + '/../')
from Src.bank import Bank, InSufficientBalance
def test_account_holder_name():
b = Bank('py test', 10)
assert b.name == 'Py Test'
Now run the pytest and pytest -v commands in the CMD and you will see the following output.
pytest -v command will give use some more information about our tests. Pytest will execute all tests from the files If you have multiple test_*.py or *_test.py files,
Run Tests From specific file only.
pytest test_bank_1.py
We will get same output. As we have only one test in the test_bank_1 file, pytest will execute only one test.
How To Run Specific tests:
When we have multiple tests files that cover multiple module and functionalities, sometimes we need to run only specific tests only. Pytest provides two ways to run the subset of the tests suites.
By matching full name or partial matching of the test name.
By group. Applied marked to tests and run those tests.
To run the test by matching partial name of test, use k switch with python command.
pytest -k holder_name
pytest -k account_holder_name -v
Here -k <substring> represents the partial text to search in the test names available in the various test files.
Now create one more test file test_bank_2.py and add following tests.
import sys, os
myPath = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, myPath + '/../')
from Src.bank import Bank, InSufficientBalance
def test_holder_name():
b = Bank('py test', 10)
assert b.name == 'Py Test'
def test_initial_amount():
b = Bank('Xeevi Computer')
assert b.balance == 0
def test_set_inital_amount():
b =Bank('Xeevi Computer',100)
assert b.balance == 100
Run command pytest -k holder and you will get following output. Pytest check all test files having tests with sub string holder and executes.
Please see the above pytest output. It shows collected 4 items / 2 deselected / 2 selected. We have total 4 tests in two tests files. Two tests are having holder substring in the name, so only two are selected and executed. Rest, two tests deselected.
And you can also run all tests except the ones that match the keyword:
pytest -k “not holder” -v
Run tests with different substring/expr
pytest -k “account or holder” -v
pytest -k “account and holder” -v
How to Run a Group of tests:
As discussed in the previous topic, this is another method/way to run the specific tests. Pytest provides many inbuilt markers such as xfail, skip and parametrize. Users can create their own marker (user defined) names as well.
To use markers, we have to import pytest module in the test file and need to applied marker on the tests using the syntax @pytest.mark,<markernamk>
To run the marked tests, we can use the following syntax.
pytest -m <markername> -v where -m <markername> represents the marker name of the tests to be executed.
Now let’s create another test file test_marker.py and add few tests in the file.
import pytest
import sys, os
myPath = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, myPath + '/../')
from Src.bank import Bank, InSufficientBalance
@pytest.mark.positive
def test_deposit():
b = Bank("Test Account", 10)
assert b.balance == 10
bal = b.get_balance()
b.deposit(100)
assert b.balance == bal + 100
Run command pytest -m positive -v
Run all other tests except the positive ones. pytest -m “not positive”-v
Pytest Fixtures: Reference
In testing, a fixture provides a defined, reliable and consistent context for the tests. This could include environment (for example a database configured with known parameters) or content (such as a dataset).
Fixtures define the steps and data that constitute the arrange phase of a test (see Anatomy of a test). In pytest, they are functions you define that serve this purpose. They can also be used to define a test’s act phase; this is a powerful technique for designing more complex tests.
We can tell pytest that a particular function is a fixture by decorating it with @pytest.fixture.
Fixtures are functions, which will run before each test function to which it is applied. You may have noticed some repetition in the way we initialized the class in each test. This is where pytest fixtures come in. pytest fixtures offer dramatic improvements over the classic xUnit style of setup/teardown functions. Fixture functions are created by marking them with the @pytest.fixture decorator. Test functions that require fixtures should accept them as arguments.
Now let’s modify our Bank Class and add new test file test_fixture.py to test the Bank class.
Notice that now we do not have Bank instance in the test method. If you notice a case where a piece of code is used repeatedly in a number of tests, that might be a good candidate to use as a fixture.
Parametrized Test Functions
Comments
Post a Comment