-
Notifications
You must be signed in to change notification settings - Fork 0
/
arglinker.py
104 lines (77 loc) · 2.9 KB
/
arglinker.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# coding: utf-8
'''
Enable a py.test like automatic fixture injection with
# unittest
TestCase = arglinker.add_test_linker(unittest.TestCase)
and using the returned TestCase for base class for tests.
Fixtures will be automatically passed as the appropriate parameters of
the test methods of the linked class.
Fixture `a` is defined as the return value of non-test method `a()`
of the same TestCase derived class.
Author: Krisztián Fekete
'''
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from __future__ import print_function
import functools
import inspect
__all__ = ('add_test_linker',)
def call_with_fixtures(obj, function, fixtures):
args = inspect.getargspec(function).args[1:]
for arg in args:
add_fixture(obj, arg, fixtures)
# python2: `self` must be positional parameter, not keyword parameter
return function(obj, **dict((arg, fixtures[arg]) for arg in args))
def add_fixture(obj, arg_name, fixtures):
if arg_name in fixtures:
return
create_fixture = getattr(obj.__class__, arg_name)
fixture = call_with_fixtures(obj, create_fixture, fixtures)
fixtures[arg_name] = fixture
def func_with_fixture_resolver(f):
argspec = inspect.getargspec(f)
does_not_need_transform = (
argspec.args == ['self'] or argspec.varargs or argspec.keywords
)
if does_not_need_transform:
return f
# strong python convention: subject of method is named self
# assumption: developers follow convention
assert argspec.args[0] == 'self'
@functools.wraps(f)
def f_with_fixtures(self):
return call_with_fixtures(self, f, fixtures={})
return f_with_fixtures
class ArgLinkerMeta(type):
'''
Metaclass linking fixtures to parameter names.
Replaces test methods with closure methods that create/resolve fixtures
from parameter names and call the original test method with the fixtures.
'''
def __new__(cls, name, parents, dct):
new_dct = {}
for obj_name, obj in dct.items():
is_test_method = (
obj_name.startswith('test') and inspect.isfunction(obj))
if is_test_method:
new_dct[obj_name] = func_with_fixture_resolver(obj)
else:
new_dct[obj_name] = obj
return (
super(ArgLinkerMeta, cls).__new__(cls, name, parents, new_dct))
def add_test_linker(test_case_class):
'''
Return a new, enhanced test case class.
The returned class' test methods resolve fixtures from argument names.
The fixtures are simply return values of methods having the same name
as the parameter.
'''
# create class by instantiating the metaclass (python 2 & 3 compatible)
return ArgLinkerMeta(
# class name
test_case_class.__name__,
# base classes
(test_case_class,),
# newly defined stuff
{})