Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.python > #52170 > unrolled thread
| Started by | adam.preble@gmail.com |
|---|---|
| First post | 2013-08-07 23:32 -0700 |
| Last post | 2013-08-11 21:25 -0700 |
| Articles | 11 — 4 participants |
Back to article view | Back to comp.lang.python
Is it possible to make a unittest decorator to rename a method from "x" to "testx?" adam.preble@gmail.com - 2013-08-07 23:32 -0700
Re: Is it possible to make a unittest decorator to rename a method from "x" to "testx?" Terry Reedy <tjreedy@udel.edu> - 2013-08-08 04:04 -0400
Re: Is it possible to make a unittest decorator to rename a method from "x" to "testx?" adam.preble@gmail.com - 2013-08-08 09:20 -0700
Re: Is it possible to make a unittest decorator to rename a method from "x" to "testx?" Terry Reedy <tjreedy@udel.edu> - 2013-08-08 15:28 -0400
Re: Is it possible to make a unittest decorator to rename a method from "x" to "testx?" Peter Otten <__peter__@web.de> - 2013-08-08 10:32 +0200
Re: Is it possible to make a unittest decorator to rename a method from "x" to "testx?" Peter Otten <__peter__@web.de> - 2013-08-08 10:50 +0200
Re: Is it possible to make a unittest decorator to rename a method from "x" to "testx?" adam.preble@gmail.com - 2013-08-08 09:17 -0700
Re: Is it possible to make a unittest decorator to rename a method from "x" to "testx?" Ned Batchelder <ned@nedbatchelder.com> - 2013-08-08 13:14 -0400
Re: Is it possible to make a unittest decorator to rename a method from "x" to "testx?" adam.preble@gmail.com - 2013-08-08 22:07 -0700
Re: Is it possible to make a unittest decorator to rename a method from "x" to "testx?" Peter Otten <__peter__@web.de> - 2013-08-09 08:31 +0200
Re: Is it possible to make a unittest decorator to rename a method from "x" to "testx?" adam.preble@gmail.com - 2013-08-11 21:25 -0700
| From | adam.preble@gmail.com |
|---|---|
| Date | 2013-08-07 23:32 -0700 |
| Subject | Is it possible to make a unittest decorator to rename a method from "x" to "testx?" |
| Message-ID | <215331fa-379f-4251-b722-44555349fbb5@googlegroups.com> |
We were coming into Python's unittest module from backgrounds in nunit, where they use a decorate to identify tests. So I was hoping to avoid the convention of prepending "test" to the TestClass methods that are to be actually run. I'm sure this comes up all the time, but I mean not to have to do:
class Test(unittest.TestCase):
def testBlablabla(self):
self.assertEqual(True, True)
But instead:
class Test(unittest.TestCase):
@test
def Blablabla(self):
self.assertEqual(True, True)
This is admittedly a petty thing. I have just about given up trying to actually deploy a decorator, but I haven't necessarily given up on trying to do it for the sake of knowing if it's possible.
Superficially, you'd think changing a function's __name__ should do the trick, but it looks like test discovery happens without looking at the transformed function. I tried a decorator like this:
def prepend_test(func):
print "running prepend_test"
func.__name__ = "test" + func.__name__
def decorator(*args, **kwargs):
return func(args, kwargs)
return decorator
When running unit tests, I'll see "running prepend_test" show up, but a dir on the class being tested doesn't show a renamed function. I assume it only works with instances. Are there any other tricks I could consider?
[toc] | [next] | [standalone]
| From | Terry Reedy <tjreedy@udel.edu> |
|---|---|
| Date | 2013-08-08 04:04 -0400 |
| Message-ID | <mailman.341.1375949083.1251.python-list@python.org> |
| In reply to | #52170 |
On 8/8/2013 2:32 AM, adam.preble@gmail.com wrote:
> We were coming into Python's unittest module from backgrounds in nunit, where they use a decorate to identify tests. So I was hoping to avoid the convention of prepending "test" to the TestClass methods that are to be actually run. I'm sure this comes up all the time, but I mean not to have to do:
>
> class Test(unittest.TestCase):
> def testBlablabla(self):
> self.assertEqual(True, True)
>
> But instead:
> class Test(unittest.TestCase):
> @test
> def Blablabla(self):
> self.assertEqual(True, True)
I cannot help but note that this is *more* typing. But anyhow, something
like this might work.
def test(f):
f.__class__.__dict__['test_'+f.__name__]
might work. Or maybe for the body just
setattr(f.__class__, 'test_'+f.__name__)
> Superficially, you'd think changing a function's __name__ should do the trick, but it looks like test discovery happens without looking at the transformed function.
I am guessing that unittest discovery for each class is something like
if isinstance (cls, unittest.TestCase):
for name, f in cls.__dict__.items():
if name.startswith('test'):
yield f
You were thinking it would be
...
for f in cls.__dict__.values():
if f.__name__.startwith('test'):
yield f
Not ridiculous, but you seem to have disproven it. I believe you can
take 'name' in the docs to be bound or namespace name rather than
definition or attribute name.
--
Terry Jan Reedy
[toc] | [prev] | [next] | [standalone]
| From | adam.preble@gmail.com |
|---|---|
| Date | 2013-08-08 09:20 -0700 |
| Message-ID | <292722f2-56a7-4a9d-a3ad-e65b44cac834@googlegroups.com> |
| In reply to | #52175 |
On Thursday, August 8, 2013 3:04:30 AM UTC-5, Terry Reedy wrote: > I cannot help but note that this is *more* typing. But anyhow, something It wasn't so much about the typing so much as having "test" in front of everything. It's a problem particular to me since I'm writing code that, well, runs experiments. So the word "test" is already all over the place. I would even prefer if I could do away with assuming everything starting with "test" is a unittest, but I didn't think I could; it looks like Peter Otten got me in the right direction. > like this might work. > > def test(f): > > f.__class__.__dict__['test_'+f.__name__] > > > > might work. Or maybe for the body just > > setattr(f.__class__, 'test_'+f.__name__) > Just for giggles I can mess around with those exact lines, but I did get spanked trying to do something similar. I couldn't reference __class__ for some reason (Python 2.7 problem?).
[toc] | [prev] | [next] | [standalone]
| From | Terry Reedy <tjreedy@udel.edu> |
|---|---|
| Date | 2013-08-08 15:28 -0400 |
| Message-ID | <mailman.367.1375990134.1251.python-list@python.org> |
| In reply to | #52209 |
On 8/8/2013 12:20 PM, adam.preble@gmail.com wrote: > On Thursday, August 8, 2013 3:04:30 AM UTC-5, Terry Reedy wrote: >> def test(f): >> >> f.__class__.__dict__['test_'+f.__name__] Sorry, f.__class__ is 'function', not the enclosing class. A decorator for a method could not get the enclosing class name until 3.3, when it would be part of f.__qualname__. Use one of the other suggestions. > Just for giggles I can mess around with those exact lines, but I did get spanked trying to do something similar. I couldn't reference __class__ for some reason (Python 2.7 problem?). In 2.x, old-style classes and instances thereof do not have .__class__. All other objects do, as far as I know. -- Terry Jan Reedy
[toc] | [prev] | [next] | [standalone]
| From | Peter Otten <__peter__@web.de> |
|---|---|
| Date | 2013-08-08 10:32 +0200 |
| Message-ID | <mailman.342.1375950785.1251.python-list@python.org> |
| In reply to | #52170 |
adam.preble@gmail.com wrote:
> We were coming into Python's unittest module from backgrounds in nunit,
> where they use a decorate to identify tests. So I was hoping to avoid the
> convention of prepending "test" to the TestClass methods that are to be
> actually run. I'm sure this comes up all the time, but I mean not to have
> to do:
>
> class Test(unittest.TestCase):
> def testBlablabla(self):
> self.assertEqual(True, True)
>
> But instead:
> class Test(unittest.TestCase):
> @test
> def Blablabla(self):
> self.assertEqual(True, True)
>
> This is admittedly a petty thing. I have just about given up trying to
> actually deploy a decorator, but I haven't necessarily given up on trying
> to do it for the sake of knowing if it's possible.
>
> Superficially, you'd think changing a function's __name__ should do the
> trick, but it looks like test discovery happens without looking at the
> transformed function. I tried a decorator like this:
>
> def prepend_test(func):
> print "running prepend_test"
> func.__name__ = "test" + func.__name__
>
> def decorator(*args, **kwargs):
> return func(args, kwargs)
>
> return decorator
>
> When running unit tests, I'll see "running prepend_test" show up, but a
> dir on the class being tested doesn't show a renamed function. I assume
> it only works with instances. Are there any other tricks I could
> consider?
I think you are misunderstanding what a decorator does. You can think of
def f(...): ...
as syntactic sugar for an assignment
f = make_function(...)
A decorator intercepts that
f = decorator(make_function(...))
and therefore can modify or replace the function object, but has no
influence on the name binding.
For unittest to allow methods bound to a name not starting with "test" you
have to write a custom test loader.
import functools
import unittest.loader
import unittest
def test(method):
method.unittest_method = True
return method
class MyLoader(unittest.TestLoader):
def getTestCaseNames(self, testCaseClass):
def isTestMethod(attrname, testCaseClass=testCaseClass,
prefix=self.testMethodPrefix):
attr = getattr(testCaseClass, attrname)
if getattr(attr, "unittest_method", False):
return True
return attrname.startswith(prefix) and callable(attr)
testFnNames = list(filter(isTestMethod, dir(testCaseClass)))
if self.sortTestMethodsUsing:
testFnNames.sort(key=functools.cmp_to_key(self.sortTestMethodsUsing))
return testFnNames
class A(unittest.TestCase):
def test_one(self):
pass
@test
def two(self):
pass
if __name__ == "__main__":
unittest.main(testLoader=MyLoader())
Alternatively you can write a metaclass that *can* intercept the name
binding process:
$ cat mytestcase.py
import unittest
__UNITTEST = True
PREFIX = "test_"
class Type(type):
def __new__(class_, name, bases, classdict):
newclassdict = {}
for name, attr in classdict.items():
if getattr(attr, "test", False):
assert not name.startswith(PREFIX)
name = PREFIX + name
assert name not in newclassdict
newclassdict[name] = attr
return type.__new__(class_, name, bases, newclassdict)
class MyTestCase(unittest.TestCase, metaclass=Type):
pass
def test(method):
method.test = True
return method
$ cat mytestcase_demo.py
import unittest
from mytestcase import MyTestCase, test
class T(MyTestCase):
def test_one(self):
pass
@test
def two(self):
pass
if __name__ == "__main__":
unittest.main()
$ python3 mytestcase_demo.py -v
test_one (__main__.test_two) ... ok
test_two (__main__.test_two) ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
[toc] | [prev] | [next] | [standalone]
| From | Peter Otten <__peter__@web.de> |
|---|---|
| Date | 2013-08-08 10:50 +0200 |
| Message-ID | <mailman.343.1375951856.1251.python-list@python.org> |
| In reply to | #52170 |
Peter Otten wrote: > $ python3 mytestcase_demo.py -v > test_one (__main__.test_two) ... ok > test_two (__main__.test_two) ... ok > > ---------------------------------------------------------------------- > Ran 2 tests in 0.000s Oops, that's an odd class name. Fixing the name clash in Types.__new__() is left as an exercise...
[toc] | [prev] | [next] | [standalone]
| From | adam.preble@gmail.com |
|---|---|
| Date | 2013-08-08 09:17 -0700 |
| Message-ID | <c85e5960-f1ed-4e93-9031-60d25519ad14@googlegroups.com> |
| In reply to | #52178 |
On Thursday, August 8, 2013 3:50:47 AM UTC-5, Peter Otten wrote: > Peter Otten wrote: > Oops, that's an odd class name. Fixing the name clash in Types.__new__() is > > left as an exercise... I will do some experiments with a custom test loader since I wasn't aware of that as a viable alternative. I am grateful for the responses.
[toc] | [prev] | [next] | [standalone]
| From | Ned Batchelder <ned@nedbatchelder.com> |
|---|---|
| Date | 2013-08-08 13:14 -0400 |
| Message-ID | <mailman.363.1375982886.1251.python-list@python.org> |
| In reply to | #52208 |
On 8/8/13 12:17 PM, adam.preble@gmail.com wrote: > On Thursday, August 8, 2013 3:50:47 AM UTC-5, Peter Otten wrote: >> Peter Otten wrote: >> Oops, that's an odd class name. Fixing the name clash in Types.__new__() is >> >> left as an exercise... > I will do some experiments with a custom test loader since I wasn't aware of that as a viable alternative. I am grateful for the responses. If you can use another test runner, they often have more flexible and powerful ways to do everything. nosetests will let you use a __test__ attribute, for example, to mark tests. Your decorator could simply assign that attribute on the test methods. You'd still write your tests using the unittest base classes, but run them with nose. --Ned.
[toc] | [prev] | [next] | [standalone]
| From | adam.preble@gmail.com |
|---|---|
| Date | 2013-08-08 22:07 -0700 |
| Message-ID | <3b1f6c61-da0b-42bd-acf5-95962f6d33d9@googlegroups.com> |
| In reply to | #52178 |
On Thursday, August 8, 2013 3:50:47 AM UTC-5, Peter Otten wrote: > Peter Otten wrote: > Oops, that's an odd class name. Fixing the name clash in Types.__new__() is > > left as an exercise... Interesting, I got __main__.T, even though I pretty much just tried your code wholesale. For what it's worth, I'm using Python 2.7. I'm glad to see that code since I learned a lot of tricks from it.
[toc] | [prev] | [next] | [standalone]
| From | Peter Otten <__peter__@web.de> |
|---|---|
| Date | 2013-08-09 08:31 +0200 |
| Message-ID | <mailman.387.1376029907.1251.python-list@python.org> |
| In reply to | #52245 |
adam.preble@gmail.com wrote:
> On Thursday, August 8, 2013 3:50:47 AM UTC-5, Peter Otten wrote:
>> Peter Otten wrote:
>> Oops, that's an odd class name. Fixing the name clash in Types.__new__()
>> is
>>
>> left as an exercise...
>
> Interesting, I got __main__.T, even though I pretty much just tried your
> code wholesale.
I see I have to fix it myself then...
> For what it's worth, I'm using Python 2.7. I'm glad to
> see that code since I learned a lot of tricks from it.
[My buggy code]
> class Type(type):
> def __new__(class_, name, bases, classdict):
Here 'name' is the class name
> newclassdict = {}
> for name, attr in classdict.items():
> if getattr(attr, "test", False):
> assert not name.startswith(PREFIX)
> name = PREFIX + name
> assert name not in newclassdict
> newclassdict[name] = attr
Here 'name' is the the last key of classdict which is passed to type.__new__
instead of the actual class name.
> return type.__new__(class_, name, bases, newclassdict)
[Fixed version]
class Type(type):
def __new__(class_, classname, bases, classdict):
newclassdict = {}
for name, attr in classdict.items():
if getattr(attr, "test", False):
assert not name.startswith(PREFIX)
name = PREFIX + name
assert name not in newclassdict
newclassdict[name] = attr
return type.__new__(class_, classname, bases, newclassdict)
[toc] | [prev] | [next] | [standalone]
| From | adam.preble@gmail.com |
|---|---|
| Date | 2013-08-11 21:25 -0700 |
| Message-ID | <77d3df08-a287-4cb8-8230-ec59ea6219b2@googlegroups.com> |
| In reply to | #52248 |
On Friday, August 9, 2013 1:31:43 AM UTC-5, Peter Otten wrote: > I see I have to fix it myself then... Sorry man, I think in my excitement of seeing the first of your examples to work, that I missed the second example, only seeing your comments about it at the end of the post. I didn't expect such a good response.
[toc] | [prev] | [standalone]
Back to top | Article view | comp.lang.python
csiph-web