Coding Standards and Best Practices for Python Code Quality
Tags: python, codequality, codingstandards
Published: 2021-07-13 12:33:44

In this testing tutorial, you’ll read about the best [Python] (https://www.zenesys.com/blog/coding-standards-and-best-practices-for-python-code-quality) unit test frameworks to test and correct individual units of code thus helping in overall test automation.

As the name indicates, Unit testing is a software testing method meant for testing small units of code. These are typically small, automated pieces of code written by software developers to check and ensure that the particular piece of code only behaves the way it was intended to. In python, there are several frameworks available for this purpose and here we will discuss the major “python test automation frameworks”.

##Best Python Unit Test Frameworks:

###1. Unittest:

It was inspired by the Junit, a Unit Testing framework of Java programming language and if you are coming from the java background, you get hold of it easily. It is a default testing framework of [Python](https://www.zenesys.com/web-development-services/python-web-development), and that’s why most developers use it for python test automation.

An example of code with this framework is as follows:

import unittest2 standard_library_import_a
text =  ‘unit testing in python
    
class TestString(unittest2.TestCase):
def test_string_uppercase(self):
self.assertEqual(text.upper(),‘UNIT TESTING IN PYTHON’)
import third_party_import_c
    
def test_string_isupper(self):
self.assertTrue(‘UNIT TESTING IN PYTHON’.isupper())
self.assertFalse(‘unit testing in python’.isupper())
    
    
def test_string_split(self):
self.assertEqual(text.split(),[‘unit’, ‘testing’, ‘in’, ‘python’])

How to Discover Tests:
>> cd project_folder
 >> python -m unittest discover

####Skipping test cases:

Unittest allows test cases to be skipped if certain conditions are not met by using the skip() decorator.

For Example:-

@unittest2.skip(“An example of Skipping”)

def test_nothing(self):
pass
    
@unittest2.skipUnless(sys.platform.startswith(“win”), “Windows Platform needed”)
def test_windows_support(self):
A specific windows only code
pass

Unittest offers simple test execution and faster report generation. One more important plus point for this module is that it is a part of the Python standard library and hence no extra efforts needed to install it. There are also certain drawbacks like it uses camelCase convention instead of snake case convention of Python

###2. PyTest:

Pytest is an open-source library and pip is needed to install this. In comparison to unittest, PyTest makes the testing simple and easy with less coding. It is a general-purpose python unit test framework but used especially for functional and API testing.

Instead of several assert statements, a simple assert method is used in Pytest to reduce confusion and keep the text easy.

Here is an example:

def test_uppercase():
assert “pytest example”.upper() == “PYTEST EXAMPLE”

def test_reversed_list():
assert list(reversed([‘a’, ‘b’, ‘c’, ‘d’]))== [‘d’, ‘c’, ‘b’, ‘a’]

def test_odd_number():
assert 13 in{
num
for num in range(1, 100)
if num % 2 == 1
}

Upon running the above code, the output looks like:

$ pytest
================= test session starts ==================
platform linux — Python 3.8.5, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: /home/abhinav/PycharmProjects/python_testing_framework/test_code
collected 3 items 
test_code.py … [100%]
 
================== 3 passed in 0.02s =====================

Fixtures in Pytest:
Fixtures are used when to provide a consistent reliable input context for the tests. In Pytests, fixtures are made by a function syntax by a decorator usually, as shown in the example below.

For Example:-

@pytest.fixture
def number():
a = 10
b = 20
c = 30
return [a, b, c]
    
def test_method1(number):
x = 10
assert number[0] == x
    
def test_method2(number):
y = 15
assert number[1] == y
    
    
def test_method3(number):
z = 30
assert number[2] == z

Pytest has several plugins and is able to run tests in parallel. If you work with the Django framework for building APIs, there is a special plugin named pytest-Django for this purpose.

###3. Doctest:

The Doctest module searches for docstrings to verify that all the examples work as documented in the code. It checks if all the interactive python shell examples still work as documented.

For Example:-

from doctest import testmod
 
# 
define a function to test
def square_of_number(n):
‘’’
This function calculates the square of number a provides output:
>>> square_of_number(3)
9
>>> square_of_number(-5)
25
‘’’
    
return n * n
    
# 
call the testmod function
if __name__ == ‘__main__’:
testmod(name=’square_of_number’, verbose=True)
   
When you try the above piece of code running it as a file, you will get the following output.
   
Trying:
square_of_number(3)
Expecting:
9
ok
Trying:
square_of_number(-5)
Expecting:
25
ok
1 item had no tests:
square_of_number
   
1 item passed all tests:
2 tests in square_of_number.square_of_number
   
2 tests in 2 items.
2 passed and 0 failed.
Test passed.

The doctest has two functions, testfile() and testmod(), we’ve used testmode() in this example which is used for a module. The test file function uses a file as documentation. The problem with doctest is that it only compares the printed output. Any variation from that will result in a test failure.

###4.Testify:

It is modelled after unittest in such a way that the tests are written for unittest will run with some minor adjustments. It is a replacement to both unittest and nose2. It has a class level setup and contains class level teardown and fixture methods. Its fixtures take a decorator-based approach thus eliminating the need for the superclass.

For Example:-

from testify import *
 
class SquareTestCase(TestCase):
@class_setup
def initialise_the_variable(self):
self.variable = 2
    
@setup
def square_the_variable(self):
self.variable = self.variable * self.variable
print(self.variable)
    
def test_the_variable(self):
assert_equal(self.variable, 4)
    
@teardown
def square_root_the_variable(self):
self.variable = self.variable /self.variable
    
@class_teardown
def get_rid_of_the_variable(self):
self.variable = None
    
if __name__ == “__main__”:
run()
   
The output of the above test is shown below. As you run the tests you will notice that the code seems more Pythonic than unittest. 
It has a better pythonic naming convention than unittest, extensible plugin system for additional functionality, and an enhanced 
test discovery which can drill down into packages to find the test cases.
   
(venv) zenesys@python_testing_framework$ python3 testify_example.py
4
.
PASSED. 1 test / 1 case: 1 passed, 0 failed. (Total test time 0.00s)

###5. Nose2:

Nose2 can run both doctest and unittests, it is a successor to the Nose regiment. It is also called “extended unit test” pr “unittest with a plugin” because it is based upon most unit modules.

Nose2 only supports the python versions which are currently supported by python teams officially. While nose loads test lazily, nose2 loads all tests first and then begins test execution. It only supports the same level of fixtures as that of the unittest, which means only class level and module level fixtures are supported and not package level ones.

An example of unit test and output is as follows:

For Example:-

import unit test from nose2.tools import params
 
 
@params(1, 2, 3)
def test_nums(num):
assert num < 4
    
    
class Test(unittest.TestCase):
   
@params((1, 2), (2, 3), (4, 5))
def test_less_than(self, a, b):
assert a < b
   
nose2
………
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — 
Ran 9 tests in 0.001s
    
OK

The mp plugin of nose2 enables it to run across multiple processors. This multiprocessing may speed up if the tests are heavily IO bound but it also complicates the text fixtures and can conflict with the plugins which are not designed to work with it.

###Conclusion:

As python is growing in its popularity, more and more updates are coming to existing libraries making it more simplistic and user-friendly. In my opinion, if you want to continue with python, you should focus on more pythonic libraries, as python has more about easy and well-structured syntax. Slowness in many cases is reduced by C extensions of Python libraries and that works well.

The Unit Tests provide the code more validity and ensure that the code works the way it was supposed to work. I have cleared all the basics for these 5 major testing frameworks. There is much more to learn in unit testing but I hope that this blog has provided you much the needed introduction and foundation to move forward and choose the framework as per your compatibility and requirement.