Python Testing

Python : Choosing a Test Runner

There are many test runners available for Python. The three most popular test runners are:

  1. unittest
  2. nose or nose2
  3. pytest
PyUnit/ unittest

The one built into the Python standard library is called unittest. In this tutorial, you will be using unittest test cases and the unittest test runner. The principles of unittest are easily portable to other frameworks.

unit-test requires that:
  • You put your tests into classes as methods
  • You use a series of special assertion methods in the unittest.TestCase class instead of the built-in assert statement

To convert the test to a unittest test case, you would have to:

  1. Import unittest from the standard library
  2. Create a class called TestSum that inherits from the TestCase class
  3. Convert the test functions into methods by adding self as the first argument
  4. Change the assertions to use the self.assertEqual() method on the TestCase class
  5. Change the command-line entry point to call unittest.main()

Note: Be careful if you’re writing test cases that need to execute in both Python 2 and 3. In Python 2.7 and below, unittest is called unittest2. If you simply import from unittest, you will get different versions with different features between Python 2 and 3.

Resource: https://docs.python.org/2/library/unittest.html

The Python unit testing framework, sometimes referred to as “PyUnit,” is a Python language version of Junit

unittest module supports test automation, sharing of setup and shutdown code for tests, aggregation of tests into collections, and independence of the tests from the reporting framework.

To achieve this, unittest supports some important concepts:

test fixture

A test fixture represents the preparation needed to perform one or more tests, and any associate cleanup actions.

test case

A test case checks for a specific response to a particular set of inputs. unittest provides a base class, TestCase, which may be used to create new test cases.

test suite

A test suite is a collection of test cases, test suites, or both. It is used to aggregate tests that should be executed together.

test runner

A test runner is a component which orchestrates the execution of tests and provides the outcome to the user. The runner may use a graphical interface, a textual interface, or return a special value to indicate the results of executing the tests.

A test runner is an object that provides a single method, run(), which accepts a TestCase or TestSuite object as a parameter, and returns a result object. The class TestResult is provided for use as the result object. unittest provides the TextTestRunner as an example test runner which reports test results on the standard error stream by default. Alternate runners can be implemented for other environments (such as graphical environments) without any need to derive from a specific class.

Maintainable code using library

Check your code style

PEP 8 is the Python code style guide, and it sets out rules for things like line length, indentation, multi-line expressions, and naming conventions

1. Pylint

Pylint is a library that checks for PEP 8 style violations and common errors.

can also be run from the command line.

To install, run pip install pylint.

To use Pylint from the command line, run pylint

path/to/dir or pylint [options] path/to/module.py. Pylint will output warnings about style violations and other errors to the console.

You can customize what errors Pylint checks for with a configuration file called pylintrc.

Outsource your code style

Remembering to run linters manually from the command line for each file you change is a pain,

A great solution is to use a library that automatically reformats your code into something that passes PEP 8 for you.

1.Autopep8

Autopep8 automatically formats the code in the module you specify. It will re-indent lines, fix indentation, remove extraneous whitespace, and refactor common comparison mistakes (like with booleans and None).

To install, run pip install --upgrade autopep8.

To reformat code in place, run autopep8 --in-place --aggressive --aggressive <filename>. The aggressive flags (and the number of them) indicate how much control you want to give autopep8 over your code style

 

Check your test coverage

You’re writing tests, right? Then you will want to make sure new code committed to your codebase is tested and doesn’t drop your overall amount of test coverage.

For measuring test coverage, we have one recommendation: Coverage.

Coverage has several options for the way it reports your test coverage to you, including outputting results to the console or to an HTML page and indicating which line numbers are missing test coverage

To install, run pip install coverage. To run a program and see its output,

 run coverage run [path/to/module.py] [args], and you will see your program’s output.

To see a report of which lines of code are missing coverage, run coverage report -m.

Core Python

Following topics are covered

  1. Introduction
  2. Logging
  3. Exception Handling
  4. Searching & Regex
  5. Packaging
  6. File handling & problem solution

Introduction


Logging:

Basics of using the logging module to record the events in a file are very simple.
For that, simply import the module from the library.

  1. Create and configure the logger. It can have several parameters. But importantly, pass the name of the file in which you want to record the events.
  2. Here the format of the logger can also be set. By default, the file works in append mode but we can change that to write mode if required.
  3. Also, the level of the logger can be set which acts as the threshold for tracking based on the numeric values assigned to each level.
    There are several attributes which can be passed as parameters.
  4. The list of all those parameters is given in Python Library. The user can choose the required attribute according to the requirement.

After that, create an object and use the various methods as shown in the example.

#importing module
import logging
#Create and configure logger
FORMAT = '%(asctime)-15s %(clientip)s %(user)-8s %(levelname)s %(message)s'
logging.basicConfig(filename="newfile.log",format= FORMAT,filemode='w')
#Creating an object
logger=logging.getLogger()
#Setting the threshold of logger to DEBUG
logger.setLevel(logging.DEBUG)
#Test messages
logger.debug("Harmless debug Message")
logger.info("Just an information")
logger.warning("Its a Warning")
logger.error("Did you try to divide by zero")
logger.critical("Internet is down")



The above code will generate a file with the provided name

Configuration:

Standard attributes names:

Attribute nameFormatDescription
argsYou shouldn’t need to format this yourself.The tuple of arguments merged into msg to produce message, or a dict whose values are used for the merge (when there is only one argument, and it is a dictionary).
asctime%(asctime)sHuman-readable time when the LogRecord was created. By default this is of the form ‘2003-07-08 16:49:45,896’ (the numbers after the comma are millisecond portion of the time).
created%(created)fTime when the LogRecord was created (as returned by time.time()).
exc_infoYou shouldn’t need to format this yourself.Exception tuple (à la sys.exc_info) or, if no exception has occurred, None.
filename%(filename)sFilename portion of pathname.
funcName%(funcName)sName of function containing the logging call.
levelname%(levelname)sText logging level for the message (‘DEBUG’, ‘INFO’, ‘WARNING’, ‘ERROR’, ‘CRITICAL’).
levelno%(levelno)sNumeric logging level for the message (DEBUG, INFO, WARNING, ERROR, CRITICAL).
lineno%(lineno)dSource line number where the logging call was issued (if available).
message%(message)sThe logged message, computed as msg % args. This is set when Formatter.format() is invoked.
module%(module)sModule (name portion of filename).
msecs%(msecs)dMillisecond portion of the time when the LogRecord was created.
msgYou shouldn’t need to format this yourself.The format string passed in the original logging call. Merged with args to produce message, or an arbitrary object (see Using arbitrary objects as messages).
name%(name)sName of the logger used to log the call.
pathname%(pathname)sFull pathname of the source file where the logging call was issued (if available).
process%(process)dProcess ID (if available).
processName%(processName)sProcess name (if available).
relativeCreated%(relativeCreated)dTime in milliseconds when the LogRecord was created, relative to the time the logging module was loaded.
stack_infoYou shouldn’t need to format this yourself.Stack frame information (where available) from the bottom of the stack in the current thread, up to and including the stack frame of the logging call which resulted in the creation of this record.
thread%(thread)dThread ID (if available).
threadName%(threadName)sThread name (if available).

Logging Variable Data

1. this can actually be done directly by using a format string for the message and appending the variable data as arguments

import logging

name = ‘John’

logging.error(‘%s raised an error’, name)

Lazy Logging:

Issue:

log.debug(“I found {} and {}”, getone(), gettwo());

Seems, pretty good. A debug log with two parameters into the String.

Resources:

  1. https://docs.python.org/3/howto/logging-cookbook.html
  2. https://realpython.com/python-loggin/

Exception Handling:

syntax error-

missing : after ‘if’ or ‘for’

Exception :

Errors detected during execution are called exceptions

Handling Exceptions:

The try statement works as follows.

  • First, the try clause (the statement(s) between the try and except keywords) is executed.
  • If no exception occurs, the except clause is skipped and execution of the try statement is finished.
  • If an exception occurs during execution of the try clause, the rest of the clause is skipped. Then if its type matches the exception named after the except keyword, the except clause is executed, and then execution continues after the try statement.
  • If an exception occurs which does not match the exception named in the except clause, it is passed on to outer try statements; if no handler is found, it is an unhandled exception and execution stops with a message as shown above.
Syntax-
try:
x = int(input("Please enter a number: "))
break
except ValueError:
print("Oops!  That was no valid number.  Try again..

Note :Above program expects an integer ,If provided any other datatype it complains/Throws Error.

A try statement may have more than one except clause, to specify handlers for different exceptions. At most one handler will be executed. Handlers only handle exceptions that occur in the corresponding try clause, not in other handlers of the same try statement. An except clause may name multiple exceptions as a parenthesized tuple, for example

 except (RuntimeError, TypeError, NameError):
Order or evoking exceptions:

A class in an except clause is compatible with an exception if it is the same class or a base class thereof (but not the other way around — an except clause listing a derived class is not compatible with a base class). For example, the following code will print B, C, D in that order:

class B(Exception):
 pass
class C(B):
 pass
class D(C):
 pass
for cls in [B, C, D]:
 try:
   raise cls()
 except D:
  print("D")
 except C:
     print("C")
 except B:
 print("B")

Note that if the except clauses were reversed (with exceptB first), it would have printed B, B, B — the first matching except clause is triggered.

Summary: an except clause can capture only those exception class which are Base(or smaller footprint than that of raise exception class)

Supreme Exception Handler:

The last except clause may omit the exception name(s), to serve as a wildcard. Use this with extreme caution, since it is easy to mask a real programming error in this way! It can also be used to print an error message and then re-raise the exception (allowing a caller to handle the exception as well):

import sys
try:
   f = open('myfile.txt')
   s = f.readline()
   i = int(s.strip())
except OSError as err:
   print("OS error: {0}".format(err))
except ValueError:
 print("Could not convert data to an integer.")
except:
 print("Unexpected error:", sys.exc_info()[0])
 raise

The tryexcept statement has an optional else clause, which, when present, must follow all except clauses. It is useful for code that must be executed if the try clause does not raise an exception. For example:

for arg in sys.argv[1:]:
 try:
 f = open(arg, 'r')
 except OSError:
   print('cannot open', arg)
else:
      print(arg, 'has', len(f.readlines()), 'lines')
 f.close()

The use of the else clause is better than adding additional code to the try clause because it avoids accidentally catching an exception that wasn’t raised by the code being protected by the try … except statement.



Regular Expression module:

re — Regular expression operations

Both patterns and strings to be searched can be Unicode strings as well as 8-bit strings. However, Unicode strings and 8-bit strings cannot be mixed to match a literal backslash, one might have to write ‘\\\\’ as the pattern string, because the regular expression must be \\, and each backslash must be expressed as \\ inside a regular Python string literal.

The solution is to use Python’s raw string notation for regular expression patterns; backslashes are not handled in any special way in a string literal prefixed with ‘r’. So r”\n” is a two-character string containing ‘\’ and ‘n’, while “\n” is a one-character string containing a newline

search() vs. match()

Python offers two different primitive operations based on regular expressions: re.match() checks for a match only at the beginning of the string, while re.search() checks for a match anywhere in the string.

Example:

>>> re.match("c", "abcdef")  # No match
>>> re.search("c", "abcdef") # Match

however that in MULTILINE mode match() only matches at the beginning of the string, whereas using search() with a regular expression beginning with ‘^’ will match at the beginning of each line.

>>> re.match('X', 'A\nB\nX', re.MULTILINE)  # No match
>>> re.search('^X', 'A\nB\nX', re.MULTILINE)  # Match
FINDING all Adverbs

For example, if one was a writer and wanted to find all of the adverbs in some text, he or she might use findall() in the following manner:

>>> text = "He was carefully disguised but captured quickly by police."
>>> re.findall(r"\w+ly", text)
['carefully', 'quickly']

Use case: parsing log file

import sys
import re
logFileName = open(sys.argv[1], "r")
putputFileName = "parsedLogOutput.txt"
outputfile = open(putputFileName, "w")
lines = logFileName.readlines()
regex = "2019-08-12 03:00:*"
compiledReg = re.compile(regex)
for line in lines:
   if re.match(compiledReg, line, flags=0):
        outputfile.write(line + "\n")
  print(line)

Package and making a module deliverable

Each package in Python is a directory which MUST contain a special file called __init__.py. This file can be empty, and it indicates that the directory it contains is a Python package, so it can be imported the same way a module can be imported.

Example:

we create a directory called foo, which marks the package name, we can then create a module inside that package called bar. We also must not forget to add the __init__.py file inside the foo directory.

To use the module bar, we can import it in two ways:
import foo.bar

Tip:Listing functions of module:

# re is a module and following is script to list all functions:

Import re
find_members = []
for member in dir(re):
    if "find" in member:
        find_members.append(member)
print(sorted(find_members))

Problem Specific code snippets:

1.Run a linux command

 make a command
makecmd = "ls -lrt"
# Execute the command
cwd = subprocess.Popen(makecmd, stdout=subprocess.PIPE, shell=True)
(output, err) = cwd.communicate()
# print the output
print("output: " + str(output))
  1. File Handling
    1. Text File
    2. Excel File

There are different modules for handling excel file like :

XlsxWriter(only for writing),openpyxl module(rw all variation of excels), xlwt module, panda module

Working with xlwt- in pyhton

Step 1-Install module:

pip install xlwt

Step 2-code snippet to write to an xml and apply style:

# importing xlwt module
import xlwt
workbook = xlwt.Workbook() 
sheet = workbook.add_sheet("Sheet Name")
# Applying multiple styles
style = xlwt.easyxf('font: bold 1, color red;')
# Writing on specified sheet
sheet.write(0, 0, 'SAMPLE', style)
workbook.save("sample.xls")
Working with panda for excel handling-

Python Pandas is a data analysis library. It can read, filter and re-arrange small and large datasets and output them in a range of formats including Excel.

Note: If only purpose is writing to excel use native module instead of pandas which internally uses it as it will be over-burden.

Pandas writes Excel files using the XlsxWriter modules

So first needs to install xlsxWriter:

Pip install xlsxWriter