Skip to main content

Program execution plans for unit testing and inject custom data

Project description

Unit Test Advanced

The Python Unit Test Toolkit

Unit Test Advanced is the Python testing toolkit for programmable execution plans. It makes it easy to write, test and scale complex applications and libraries.

This package gives you the flexibility to use the production code to run the tests, assert results at any time within the code, and inject custom data. Lightweight with no dependencies, it is compatible with any framework such as Django or Flask.

Features

  • Program multiple execution plans
  • Pass values from one step to another
  • No code duplication; use production code to run tests
  • Inject custom data
  • Auto-create execution plans from the relationship between all tests

Advantages

  • Highly flexible
  • Lightweight and independent
  • Open-source
  • Real use cases
  • Support & documentation

Authors

  • Rudy Fernandez

Install

The easiest way to install the Unit Test Advanced library is to use a package manager: pip install unit-test-advanced

Prepare the tests

Examples can be found here

Create a test class

from unit_test_advanced.UnitTestAction import UnitTestAction
import os, sys

BASE_DIR = os.path.dirname(os.path.abspath(__file__))

sys.path.append(BASE_DIR)
from step1 import run as step1_run
		
class step1_checkFileExist_success(UnitTestAction):
	
    # Function to execute to run the action to test. 
	trigger = step1_run # NOT step1_run()
	
	def final_check(self):
		if os.path.exists(f'{BASE_DIR}/{FILE_NAME}') == False:
			raise BaseException(f'The file {FILE_NAME} is missing')

Main attributes of a test class

Attribute Type Required Info
init_params dict no Kwargs to pass to the test-class when instantiated
trigger_params dict no Kwargs to pass to trigger when executed
trigger function no Action to execute to run the action to test.
dependencies list no Previous test-classes required to run the current test (Ns-1)
children list no Next test-classes to run after the current test (Ns+1)
memory dict no Passing values from one test action to the next ones within the same execution plan
finalCheck method no Final method called when the action is complete

Make sure not to execute function while passing it to "trigger": trigger = step1_run NOT trigger = step1_run(*args, **kwargs)

Create relationships

class relationExample_lvl1_1:
	children = ['scenarios.relationExample_lvl2_1', 'scenarios.relationExample_lvl2_2']

class relationExample_lvl1_2:
	children           = ['scenarios.relationExample_lvl1_2']
	
	
	
class relationExample_lvl2_1:
	dependencies  = [relationExample_lvl1_1, relationExample_lvl1_2]
	children           = ['scenarios.relationExample_lvl3_1']

class relationExample_lvl2_2:
	dependencies = [relationExample_lvl1_1]
	
	
	
# Extending the class "UnitTestAction" gives more features such as accessing the memory attribute
class relationExample_lvl3_1(UnitTestAction):
	dependencies = [relationExample_lvl2_1]
	
		def __init__(self, **kwargs):
			super().__init__(**kwargs)

Value assertation

Extend your unit test class with the package of your choice

from django.test import TestCase
from unit_test_advanced.UnitTestAction import UnitTestAction
import os, sys

BASE_DIR = os.path.dirname(os.path.abspath(__file__))

sys.path.append(BASE_DIR)
from step1 import run as step1_run
		
class step1_checkFileExist_success(UnitTestAction, TestCase):
	
	trigger = step1_run
	
	def final_check(self):
		self.assertEqual(os.path.exists(f'{BASE_DIR}/{FILE_NAME}'), True)

In case you use the __init__ method, do not forget to initialize the parents

def __init__(self, *args, **kwargs):
		super().__init__(*args, **kwargs)
		# your code here

Execute scenarios

from unit_test_advanced.UnitTest import UnitTest

UT = UnitTest(
    is_enabled              = True,  # Default is False. MUST be set to true here when we run the unit tests
    parent_execution_plan   = 'all', # Default is 'all'. Accepted values are 'all', 'main', or 'random'
    children_execution_plan = 'all', # Default is 'all'. Accepted values are 'all', 'main', or 'random'
    count_limit_identify_infinite_loop = 2, # Default is 2
    verbose                 = True, # Default is False
)

UT.execute([
    # When a single test-class is passed, the relationship tree will get build based on the settings "parent_execution_plan" and "children_execution_plan"
    relationExample_lvl1_1,
    # A list of unit test classes
    [
        relationExample_lvl1_1,
        relationExample_lvl1_2,
    ],
])

UnitTest methods

Attribute Description
__init__ Init the settings
updateSettings Update the settings
override Inject data in the function triggered by the test-class
returnValue Used when overriding a single value
preparePlans Preparing all scenarios according to the settings and list passed
getExecutionPlans Get all the execution plans prepared
resetExecutionPlans Reset all the prepared execution plans
execute Execute all the unit tests from the list

__init__ and updateSettings

Argument Type Default Description
is_enabled bool False Set to "True" only when used to run the unit tests
parent_execution_plan str all Algorithm to create the branches from on the "dependencies" side. Accepted values are "all", "main", and "random"
children_execution_plan str all Algorithm to create the branches from on the "children" side. Accepted values are "all", "main", and "random"
count_limit_identify_infinite_loop int 2 Max amount of a test-class execution within an execution plan
verbose bool False Display information while running

Execution plan algorithms:

  • all: All possible execution plans are prepared
  • main: If multiple dependencies or children, the algorithm select the first one
  • random : If multiple dependencies or children, the algorithm randomly select a path

override

Argument Type Default Description
id bool Method to execute within the test-class to override the original function
function str Function to execute if the "id" method is not found within the test-class
*args Arguments passed to the function or the "id" method
**kwargs Arguments passed to the function or the "id" method

returnValue

Argument Type Default Description
value Return the value passed

preparePlans

Argument Type Default Description
list_unit_tests list [] Prepare all the execution plans from the list

getExecutionPlans

Does not have any attribute

resetExecutionPlans

Does not have any attribute

execute

Argument Type Default Description
list_unit_tests list None Execute the prepared unit tests. The method "preparePlans" is not required if the list of plans is passed here.

Prepare actions

With decorators

from unit_test_advanced.functools import initUT

@initUT
def run():
	pass

if __name__ == '__main__':
	run()

If you need to inject custom data within the function, it needs to accept the parameter UT

from unit_test_advanced.functools import initUT

@initUT
def run(UT):
	pass

if __name__ == '__main__':
	run()

Without decorators

from unit_test_advanced.UnitTest import UnitTest

# For each entry point, the function must accept the parameter UT
def run(UT = None):
	UT = UT if type(UT) == type(UnitTest) else UnitTest()

if __name__ == '__main__':
	run()

With another parameter name

from unit_test_advanced.functools import initUT
from unit_test_advanced.UnitTest import UnitTest

# Default value is 'UT'
@initUT
def test1(UT, a, b):
	print('test1: ', type(UT), a, b)

# Looking for the parameter with the type UnitTest
@initUT
def test2(UT_other_name: UnitTest, a, b):
	print('test2: ', type(UT_other_name), a, b)
	
# Explicitly state the name
@initUT('UT_other_name')
def test3(UT_other_name, a, b):
	print('test3: ', type(UT_other_name), UT_other_name._parent_execution_plan, a, b)

if __name__ == '__main__':
    test1(1, 2)
    test2(1, 2)
    test3(1, 2)
    test3( UnitTest(parent_execution_plan= 'child'), 1, 2)
    test3(UT_other_name= 'value is passed', a= 1, b= 2)

My first unit test

In production, you have two actions triggered serially: Step1 and Step2. For this example, we will keep things simple:

Action Result Verification
python step1.py File "myfile.txt" created Does the file "myfile.txt" exist?
python step2.py Add response of a fake API call in the file Does the file contains the string "result"?

When executing the file step2.py, the script prints out the content of the file at the end: File content after executing step2.py: "{"result": "success"}"

Unit test for Step1

import os
from step1 import run as step1_run

class step1_checkFileExist_success:
	
	# Function to execute to run the action to test
	trigger = step1_run

	def finalCheck(self):
	
		BASE_DIR = os.path.dirname(os.path.abspath(__file__))
		FILE_NAME = 'myfile.txt'
		
		if os.path.exists(f'{BASE_DIR}/{FILE_NAME}') == False:
			raise BaseException(f'The file {FILE_NAME} is missing')

Unit test for Step2

import os
from step2 import run as step2_run

class step2_NoOverride:
	
	trigger = step2_run
	
	def finalCheck(self):
		
		BASE_DIR = os.path.dirname(os.path.abspath(__file__))
		FILE_NAME = 'myfile.txt'
		
		f = open(f'{BASE_DIR}/{FILE_NAME}', 'r')
		if 'result' not in f.read():
			raise BaseException(f'Content invalid')

Execution plans file

The actual example file can be found here


Scenario 1: Test passed
from unit_test_advanced.UnitTest import UnitTest
import os, sys
BASE_DIR = os.path.dirname(os.path.abspath(__file__))

sys.path.append(BASE_DIR)

from scenarios import step1_checkFileExist_success, step2_NoOverride


def run():
	UT = UnitTest(
		is_enabled              = True,  # Default is False. MUST be set to true here when we run the unit tests
	)
	
	UT.execute([
		# A list of unit test classes
		[
			step1_checkFileExist_success,
			step2_NoOverride,
		],
	])
	

if __name__ == '__main__':
	run()

python unit_tests_example0_1.py

File content after executing step1.py: "" File content after executing step2.py: "{"result": "success"}"


Scenario 2: Override data & test passed
from unit_test_advanced.UnitTest import UnitTest
import os, sys
BASE_DIR = os.path.dirname(os.path.abspath(__file__))

sys.path.append(BASE_DIR)

from scenarios import step1_checkFileExist_success, step2_overrideDataExample


def run():
	UT = UnitTest(
		is_enabled              = True,  # Default is False. MUST be set to true here when we run the unit tests
	)
	
	UT.execute([
		# A list of unit test classes
		[
			step1_checkFileExist_success,
			step2_overrideDataExample,
		],
	])
	

if __name__ == '__main__':
	run()

python unit_tests_example0_2.py

File content after executing step1.py: "" File content after executing step2.py: "{"result": "ok "}"


Scenario 3: Override data & test failed

from unit_test_advanced.UnitTest import UnitTest
import os, sys
BASE_DIR = os.path.dirname(os.path.abspath(__file__))

sys.path.append(BASE_DIR)

from scenarios import step1_checkFileExist_success, step2_Override_fail


def run():
	UT = UnitTest(
		is_enabled              = True,  # Default is False. MUST be set to true here when we run the unit tests
	)
	
	UT.execute([
		# A list of unit test classes
		[
			step1_checkFileExist_success,
			step2_Override_fail,
		],
	])
	

if __name__ == '__main__':
	run()

python unit_tests_example0_3.py

File content after executing step1.py: "" File content after executing step2.py: "{"code": 200}" BaseException: Invalid API response

More scenarios

Scenario 4: override a value

Scenario 5: multiple execution plans

Scenario 6: create execution plans automatically based on dependencies

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

unit_test_advanced-0.1.5.tar.gz (15.2 kB view hashes)

Uploaded Source

Built Distribution

unit_test_advanced-0.1.5-py3-none-any.whl (18.8 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page