Created
October 18, 2011 19:05
-
-
Save jeffh/1296365 to your computer and use it in GitHub Desktop.
Python Micro-testing Framework. Inspired from py.test
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# paste code here at bottom of any test file. Then run that python file. | |
# Provides primitive mocking and assertion behavior with some decent | |
# debugging output. | |
# | |
# Designed for use in homework allow sharing test cases with others | |
# (any not require them to install pip and py.test). | |
# | |
# simply prefix test functions with test and use the assert keyword | |
# for all test assertions. | |
# | |
# api inspiration from py.test and python-mock. | |
# | |
# tested only on python3k. | |
# see example: https://gist.github.com/1296369 | |
from io import StringIO | |
import sys | |
import re | |
import traceback | |
from itertools import zip_longest | |
from pprint import pformat | |
class Mockify(object): | |
NONE = object() | |
def __init__(self, module, name, return_value=NONE, side_effect=NONE): | |
self._mod, self._name = module, name | |
self._return_value = None | |
self._return_value_was_set = False | |
self._side_effect = None | |
self._side_effect_was_set = False | |
if return_value != self.NONE: | |
self.return_value = return_value | |
if side_effect != self.NONE: | |
self.side_effect = side_effect | |
@property | |
def return_value(self): | |
return self._return_value | |
@return_value.setter | |
def return_value(self, value): | |
self._return_value = value | |
self._return_value_was_set = True | |
@property | |
def side_effect(self): | |
return self._side_effect | |
@side_effect.setter | |
def side_effect(self, value): | |
self._side_effect = value | |
self._side_effect_was_set = True | |
def __call__(self, *args, **kwargs): | |
if self._side_effect_was_set: | |
return self._side_effect(*args, **kwargs) | |
if self._return_value_was_set: | |
return self.return_value | |
raise TypeError("Mockify requires side_effect or return_value to be set") | |
def __enter__(self): | |
self._old = getattr(self._mod, self._name) | |
setattr(self._mod, self._name, self) | |
return self | |
def __exit__(self, type, value, traceback): | |
setattr(self._mod, self._name, self._old) | |
def most_recent_tb(tb): | |
prev_tb = tb | |
while prev_tb.tb_next: | |
prev_tb = prev_tb.tb_next | |
return prev_tb | |
PARTS = re.compile(r'\W([!=]=|not\W+in|in|[><]=?|or|and)\W') | |
def extract_parts(assert_line): | |
code = assert_line[len('assert'):].strip() | |
parts = PARTS.split(code) | |
if len(parts) > 1: | |
return [p for i, p in enumerate(parts) if i % 2 == 0], [p for i, p in enumerate(parts) if i % 2 == 1] | |
return [code], [] | |
def re_eval(frame): | |
code = traceback.extract_stack(frame)[-1][-1] | |
parts, ops = extract_parts(code) | |
evaled = [] | |
all_globals = {} | |
all_globals.update(frame.f_builtins) | |
all_globals.update(frame.f_globals) | |
for p in parts: | |
evaled.append(eval(p, all_globals, frame.f_locals)) | |
return parts, evaled, ops | |
def format_testname(func): | |
return func.__doc__ or func.__name__[len('test_'):].replace('_', ' ') | |
def is_test(name, value): | |
return name.lower().startswith('test') | |
def tests_from_dict(vars): | |
"Returns all test from a given dictionary." | |
tests = [] | |
for name, value in tuple(vars.items()): | |
if name.startswith('test_'): | |
tests.append(value) | |
return tests | |
def run_tests(tests, fail_fast=False): | |
print("Running {0} Tests:".format(len(tests))) | |
errors = {} | |
true_stdout = sys.stdout | |
for test in tests: | |
stdout = StringIO() | |
sys.stdout = stdout | |
try: | |
test() | |
true_stdout.write('.') | |
true_stdout.flush() | |
except Exception as e: | |
tb = most_recent_tb(sys.exc_info()[2]) | |
snippets, values, ops = re_eval(tb.tb_frame) | |
true_stdout.write('F') | |
true_stdout.flush() | |
errors[test] = (traceback.format_exc(), stdout.getvalue(), zip_longest(ops, values, fillvalue='')) | |
if fail_fast: | |
break | |
sys.stdout = true_stdout | |
if not errors: | |
print("\n\nNo Errors ^_^") | |
sys.exit(0) | |
print("\n") | |
for test_name, (exc, stdout, tree) in errors.items(): | |
print("----- {0} - FAILED -----\n\n{1}\n{2}\n".format( | |
format_testname(test_name), | |
exc, | |
'\n'.join(['%s\n%s' % (pformat(val, indent=4), op.strip()) for op, val in tree]), | |
)) | |
if stdout: | |
print(":::STDOUT:::\n{0}".format(stdout)) | |
print("==== End Errors ====") | |
sys.exit(1) | |
if __name__ == '__main__': | |
run_tests(tests_from_dict(globals()), fail_fast=('-f' in sys.argv or '--failfast' in sys.argv)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment