Re: [Tutor] Do not understand code snippet from "26.8. test — Regression tests package for Python"

2017-04-18 Thread Martin A. Brown

Greetings,

>Thank you very much Martin; you filled in a lot of details.  I had an
>overall understanding of what unittest does, but you have now enhanced
>that understanding substantially.

Happy to help!  I'll introduce you to my little menagerie below!

>I'm still iffy on how the mixin class gets its test method called 
>when this class does not subclass from unittest.TestCase, but I 
>think I may have an idea now on how it is happening.  Let's get to 
>that part of your response.

Well, the mixin technique is not strictly speaking unittest related, 
but more a matter of understanding multiple inheritance.  Once you 
understand that a bit better, I think you'll understand why this 
technique for using unittest works as it does.

>> I'll make a few observations:
>>
>>   - [on unittest] the unit testing tools use classes because it's a
>> natural way to accommodate the goal of reproducibly setting up
>> arguments and/or an environment for each test (note that each
>> TestCase can have its own setUp() and tearDown() methods; this
>> allows isolation)
>>
>>   - [on unittest] each test collected by the TestLoader can be any
>> Python class (as long as it is also derived from
>> unittest.TestCase)

I'll emphasize this point before going on further.  All 
unittest.TestLoader cares about is that it has found (for example) 
an instance of something that is a unittest.TestCase.  Your class 
can inherit from any number of other classes, but 
unittest.TestLoader will not find it, unless it also derives from 
unittest.TestCase.

Now, on to the MRO bits.

>>   - [on your classes] your classes use a multiple inheritance
>> model, deriving from TestFuncAcceptsSequencesMixin; when
>> instantiated, they'll have all of the expected TestCase methods
>> and the method called 'test_func'
>
>It is here that I am struggling.  If the mixin class does not inherit
>from unittest.TestCase, then how is test_func ever seen?

Your classes (AcceptLists, AcceptTuples, AcceptStrings) specify both 
unittest.TestCase and TestFuncAcceptsSequencesMixin.  This is 
multiple inheritance.  (N.B. I'm not sure where to recommend further 
reading on MRO, but others on the list may know.)

So, how is test_func ever seen?  After your class is defined (and 
instantiated), the instance has access to all of the methods of all 
of the parent classes.

In your case:

  * One of the parent classes of AcceptTuples is 
TestFuncAcceptsSequencesMixin which defines the method 
test_func.

  * The method 'test_func' matches the expectation of unittest when 
it goes looking for any method that matches the name 'test_*'.  

The number of methods on instances of unittest.TestCase class is 
higher (see at bottom of this email), but you will see your 
test_func method exists on each instance of the classes you created.

>This answers one important thing I was wondering about:  How do the
>classes AcceptLists, AcceptStrings, and AcceptTuples get instantiated?
>Apparently the unittest machinery does this for me.

Yes.

>> - for each method name starting with 'test_' (you have only
>>   'test_func') TestRunner will:
>
>And here is my precise sticking point:  How does the TestRunner 
>find test_func?  The classes it has collected and instantiated 
>(AcceptLists, AcceptStrings and AcceptTuples) do not themselves 
>call and make use of the test_func method they inherit from the 
>mixin class.

It looks for methods whose names match a specific pattern.  The name 
should start with 'test_*' (this is configurable if you wanted your 
tests to begin with 'frobnitz_', but I haven't seen anybody do 
this).

>>   - execute the T.setUp() method if it exists
>>
>>   - TestRunner will execute the method 'test_func'
>
>The only thing that makes sense to me is that the TestRunner 
>follows the MRO of the inherited classes and checks for any 
>test_xxx methods that might exist in those superclasses.  Is this 
>correct or do I have a conceptual misunderstanding?

No misunderstanding.  All that's happening here is that unittest is 
defining your class (which imports / inherits everything it needs) 
and then is looking for the 'test_*' methods.

You may (or may not) benefit from studying the MRO any further, but 
here's a function you could call to see multiple inheritance in 
action.  Feed this function an instance of your class:

  def log_class_method_names(*args):
  import inspect
  for o in args:
  logging.info("Class %s found", o.__class__.__name__)
  for methodname, _ in inspect.getmembers(o, inspect.ismethod):
  logging.info("Class %s has method %s", o.__class__.__name__, 
methodname)

If you try that with three instances of your classes, you should see all of the
methods that unittest will see after the class is instantiated (see also at the
foot of this email).

See below my signature if you are a chimera aficianado,

-Martin

sample script to identify chimera features
--

Re: [Tutor] Do not understand code snippet from "26.8. test — Regression tests package for Python"

2017-04-17 Thread boB Stepp
Ah, Peter, if only I could achieve your understanding and mastery!

On Mon, Apr 17, 2017 at 3:37 AM, Peter Otten <__pete...@web.de> wrote:

> Perhaps it becomes clearer if we build our own class discovery / method
> runner system. Given T as the baseclass for classes that provide foo_...()
> methods that we want to run, and M as the mix-in that provides such methods
> but isn't itself a subclass of T...
>

[snip]

> As you can see, to the discovery algorithm it doesn't matter where the
> method is defined, it suffices that it's part of the class and can be found
> by dir() or vars().
>

[snip]

>
> $ cat discovery6.py
> class T:
> pass
>
> class M:
> def foo_one(self):
> print(self.__class__.__name__, "one")
> def foo_two(self):
> print(self.__class__.__name__, "two")
>
> class X(T):
> def foo_x(self):
> print(self.__class__.__name__, "x")
>
> class Y(M, T):
> pass
>
> def safe_issubclass(S, B):
> try:
> return issubclass(S, B)
> except TypeError:
> return False
>
> def discover_Ts():
> for C in globals().values():
> if safe_issubclass(C, T) and C is not T:
> print("found", C, "with foo_... methods")
> for name in dir(C):
> if name.startswith("foo_"):
> yield C, name

So your discover function does not need to instantiate any objects; it
just searches the module's global namespace for class names.  Cool!
And dir(C) gives all attributes of C including _inherited_ attributes!
 This clarifies so much.  Many thanks, Peter!

> def run_method(cls, methodname):
> inst = cls()
> method = getattr(inst, methodname)
> method()
>
> def main():
> for cls, methodname in discover_Ts():
> run_method(cls, methodname)
>
> if __name__ == "__main__":
> main()
> $ python3 discovery6.py
> found  with foo_... methods
> Y one
> Y two
> found  with foo_... methods
> X x
>
> That was easy. We have replicated something similar to the unit test
> framework with very little code.
>
> Now you can go and find the equivalent parts in the unittest source code :)

But not tonight ~(:>))


-- 
boB
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] Do not understand code snippet from "26.8. test — Regression tests package for Python"

2017-04-17 Thread boB Stepp
On Sun, Apr 16, 2017 at 11:50 PM, Mats Wichmann  wrote:

>
> You got me thinking as well, as I don't much care for unittest, at least
> partly because it forces you to use classes even when it doesn't feel
> all that natural.

I have looked into pytest multiple times, but have decided to stick
with unittest until I feel I have mastered its use.  While it forces
the use of classes, this is an advantage for me as I am still in the
beginning stages of learning OOP.  But if I ever make it through these
learning journeys, I will probably switch to using pytest.  Everything
I have read on it to this point has favorably impressed me.

[snip]

> === reverser.py ==
> def slicerev(collection):
> return collection[::-1]
>
> if __name__ == "__main__":
> print slicerev([1,2,3,4])
> print slicerev((1,2,3,4))
> print slicerev('abcd')
> ===

[snip]

> The actual test function should be pretty straightforward.
>
> === test_slicerev.py ===
> import pytest
>
> from reverser import slicerev
>
> @pytest.fixture(params=[
> ([1,2,3,4], [4,3,2,1]),
> ((1,2,3,4), (4,3,2,1)),
> ('abcd','edcba')
> ])
> def slicedata(request):
> return request.param
>
> def test_slicerev(slicedata):
> input, expected = slicedata
> output = slicerev(input)
> assert output == expected
> ===

It's funny you picked this type of example.  Last year I was
struggling with getting unittest to feed in data to my test of a
function (or was it a method?), and almost took the plunge and went
all in on pytest because of the apparent ease of handling these types
of situations while respecting DRY.  I did find a way to do something
similar in unittest, so put off pytest for another day.  I cannot
remember now what I did.  I need to go back and find that code (If I
still have it.) and compare it with this Mixin approach that I started
this whole thread with.  Nonetheless pytest is definitely on my radar
and I will get to it at some point.

Thanks!


-- 
boB
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] Do not understand code snippet from "26.8. test — Regression tests package for Python"

2017-04-17 Thread Peter Otten
Peter Otten wrote:

> class M:
> def foo_one(self):
> print(self.__class__.__name__, "one")
> def foo_two(self):
> print(self.__class__.__name__, "one")
 
Oops, foo_two() should of course print "two", not "one".

___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] Do not understand code snippet from "26.8. test — Regression tests package for Python"

2017-04-17 Thread Peter Otten
boB Stepp wrote:

> It is here that I am struggling.  If the mixin class does not inherit
> from unittest.TestCase, then how is test_func ever seen?

Perhaps it becomes clearer if we build our own class discovery / method 
runner system. Given T as the baseclass for classes that provide foo_...() 
methods that we want to run, and M as the mix-in that provides such methods 
but isn't itself a subclass of T...

class T:
pass

class M:
def foo_one(self):
print(self.__class__.__name__, "one")
def foo_two(self):
print(self.__class__.__name__, "one")

class X(T):
def foo_x(self):
print(self.__class__.__name__, "x")

class Y(M, T):
pass

we want our discovery function to find the classes X and Y.
First attempt:

def discover_Ts():
for C in globals().values():
if issubclass(C, T):
print("found", C)


discover_Ts()

globals().values() gives all toplevel objects in the script and issubclass 
checks if we got a subclass of T. Let's try:

$ python3 discovery.py 
Traceback (most recent call last):
  File "discovery.py", line 23, in 
discover_Ts()
  File "discovery.py", line 19, in discover_Ts
if issubclass(C, T):
TypeError: issubclass() arg 1 must be a class

It turns out that issubclass() raises a TypeError if the object we want to 
check is not a class:

>>> issubclass(int, float)
False
>>> issubclass(42, float)
Traceback (most recent call last):
  File "", line 1, in 
TypeError: issubclass() arg 1 must be a class

For our purposes False is the better answer, so let's write our own 
issubclass:

def safe_issubclass(S, B):
try:
return issubclass(S, B)
except TypeError:
return False

def discover_Ts():
for C in globals().values():
if safe_issubclass(C, T):
print("found", C)

$ python3 discovery2.py 
found 
found 
found 

That's close, we only need to reject T:

def discover_Ts():
for C in globals().values():
if safe_issubclass(C, T) and C is not T:
print("found", C)

Easy. Now we have the classes we can look for the methods:

def discover_Ts():
for C in globals().values():
if safe_issubclass(C, T) and C is not T:
print("found", C, "with foo_... methods")
for name in dir(C):
if name.startswith("foo_"):
print("   ", name)

$ python3 discovery4.py 
found  with foo_... methods
foo_x
found  with foo_... methods
foo_one
foo_two

As you can see, to the discovery algorithm it doesn't matter where the 
method is defined, it suffices that it's part of the class and can be found 
by dir() or vars().

As a bonus, now that we have the class and the method let's invoke them:

def discover_Ts():
for C in globals().values():
if safe_issubclass(C, T) and C is not T:
print("found", C, "with foo_... methods")
for name in dir(C):
if name.startswith("foo_"):
yield C, name

def run_method(cls, methodname):
inst = cls()
method = getattr(inst, methodname)
method()

for cls, methodname in discover_Ts():
run_method(cls, methodname)

While you could invoke run_method() inside discover_Ts() I turned 
discover_Ts() into a generator that produces class/methodname pairs which 
looks a bit more pythonic to me. Let's run it:

$ python3 discovery5.py 
found  with foo_... methods
X x
Traceback (most recent call last):
  File "discovery5.py", line 36, in 
for cls, methodname in discover_Ts():
  File "discovery5.py", line 24, in discover_Ts
for C in globals().values():
RuntimeError: dictionary changed size during iteration

Oops, as the global names cls, and methodname spring into existence they 
torpedize our test discovery. We could (and should when we need a moderate 
amount of robustness) take a snapshot of the global variables with 
list(globals().values()), but for demonstration purposes we'll just move the 
toplevel loop into a function:

$ cat discovery6.py 
class T:
pass

class M:
def foo_one(self):
print(self.__class__.__name__, "one")
def foo_two(self):
print(self.__class__.__name__, "two")

class X(T):
def foo_x(self):
print(self.__class__.__name__, "x")

class Y(M, T):
pass

def safe_issubclass(S, B):
try:
return issubclass(S, B)
except TypeError:
return False

def discover_Ts():
for C in globals().values():
if safe_issubclass(C, T) and C is not T:
print("found", C, "with foo_... methods")
for name in dir(C):
if name.startswith("foo_"):
yield C, name

def run_method(cls, methodname):
inst = cls()
method = getattr(inst, methodname)
method()

def main():
for cls, methodname in discover_Ts():
run_method(cls, methodname)

if __name__ == "__main__":
main()
$ python3 discovery6.py 
found  with foo_... methods
Y one
Y two
found  with foo_.

Re: [Tutor] Do not understand code snippet from "26.8. test — Regression tests package for Python"

2017-04-16 Thread Mats Wichmann
On 04/16/2017 10:01 PM, boB Stepp wrote:
> OK, between Alan and Martin I think that I see how to make the code
> snippet actually test a *function* as the snippet seems to suggest.
> Recollect that my original question(s) started:


You got me thinking as well, as I don't much care for unittest, at least
partly because it forces you to use classes even when it doesn't feel
all that natural.

So here's a vaguely practical example of applying the same pattern using
pytest - that is, if you're going to test a function several different
ways, can you use just one test function instead of writing multiples.

First let's write the function to test: it tries to reverse its
argument, which should be something that can be iterated over, using
fancy list slicing.  To show it's working there is also code to try it
out if it is called as a program (as opposed to as a module).

=== reverser.py ==
def slicerev(collection):
return collection[::-1]

if __name__ == "__main__":
print slicerev([1,2,3,4])
print slicerev((1,2,3,4))
print slicerev('abcd')
===

Now write a test for this function, naming it, by convention,
test_{funcname}.py. (if it's named this way pytest can find it
automatically but it's not mandatory, you can give the name of the test
file as an argument).

Import pytest because we need the definition of the fixture decorator;
and import the function we're going to be testing, since it is, after
all, in a different file.

Since what we're factoring here is supplying different sets of data,
decorate a function "slicedata" which will return the data, turning it
into a pytest fixture (there's plenty of information on pytest fixtures
so won't repeat here); supply pairs of values where one value is the
data to call the function with and the other is the expected result of
calling the function under test.

The actual test function should be pretty straightforward.

=== test_slicerev.py ===
import pytest

from reverser import slicerev

@pytest.fixture(params=[
([1,2,3,4], [4,3,2,1]),
((1,2,3,4), (4,3,2,1)),
('abcd','edcba')
])
def slicedata(request):
return request.param

def test_slicerev(slicedata):
input, expected = slicedata
output = slicerev(input)
assert output == expected
===

Run the tests with "py.test test_slicerev.py"

Note the "expected" data for the string type is intentionally incorrect
so you should see an error with some explanatory output.

(you'll probably have to install pytest, since it's not in the standard
library; pytest can run all the unittest style tests too though).
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] Do not understand code snippet from "26.8. test — Regression tests package for Python"

2017-04-16 Thread Alan Gauld via Tutor
On 17/04/17 05:01, boB Stepp wrote:

> Am I missing anything?  If not, then why did the code snippet use the
> (I believe to be misleading.) class variable approach with "func =
> mySuperWhammyFunction" and "self.func(self.arg)"?

I suspect it was a slightly broken attempt at reuse in that
you can assign other functions to the class variable func.
In your code the function is hard coded into the test_func()
method. The original code (apart from using self.func) allowed
the mixin func attribute to be reset to different functions.

But I'm guessing at the authors intent, it may just have
been over-engineering...

-- 
Alan G
Author of the Learn to Program web site
http://www.alan-g.me.uk/
http://www.amazon.com/author/alan_gauld
Follow my photo-blog on Flickr at:
http://www.flickr.com/photos/alangauldphotos


___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] Do not understand code snippet from "26.8. test — Regression tests package for Python"

2017-04-16 Thread boB Stepp
OK, between Alan and Martin I think that I see how to make the code
snippet actually test a *function* as the snippet seems to suggest.
Recollect that my original question(s) started:

On Sat, Apr 15, 2017 at 6:17 PM, boB Stepp  wrote:
> In the section
>
> https://docs.python.org/3/library/test.html#writing-unit-tests-for-the-test-package
>
> I have been trying to make sense of the given pointer and code snippet:
>
> 
> Try to maximize code reuse. On occasion, tests will vary by something
> as small as what type of input is used. Minimize code duplication by
> subclassing a basic test class with a class that specifies the input:
>
> class TestFuncAcceptsSequencesMixin:
>
> func = mySuperWhammyFunction
>
> def test_func(self):
> self.func(self.arg)
>
> class AcceptLists(TestFuncAcceptsSequencesMixin, unittest.TestCase):
> arg = [1, 2, 3]
>
> class AcceptStrings(TestFuncAcceptsSequencesMixin, unittest.TestCase):
> arg = 'abc'
>
> class AcceptTuples(TestFuncAcceptsSequencesMixin, unittest.TestCase):
> arg = (1, 2, 3)
>
> When using this pattern, remember that all classes that inherit from
> unittest.TestCase are run as tests. The Mixin class in the example
> above does not have any data and so can’t be run by itself, thus it
> does not inherit from unittest.TestCase.
> 

The snippet as supplied will not run.  "mySuperWhammyFunction" is not
defined anywhere.  Additionally, the code to start the unittest
machinery going was not included (But to be fair, it was just
discussed above this snippet.).  In my original effort I tried to stay
as true as possible to the code snippet in the docs and only added the
"missing" elements I just mentioned.  However, I think the docs are
misleading with this line:

func = mySuperWhammyFunciton

and this line:

self.func(self.arg)

I asked myself, how am I now testing functions with unittest?  I've
been doing it for a few months now.  What I would do in the context of
this Mixin approach would be:

def mySuperWhammyFunction(any_input):
return any_input

import unittest

class TestFuncAcceptsSequencesMixin:

def test_func(self):
f = mySuperWhammyFunction(self.arg)# What need is there
for the class variable func?
self.assertEqual(f, self.arg)  # Just call and assign
the function being tested directly!
print(f)

class AcceptLists(TestFuncAcceptsSequencesMixin, unittest.TestCase):
arg = [1, 2, 3]

class AcceptStrings(TestFuncAcceptsSequencesMixin, unittest.TestCase):
arg = 'abc'

class AcceptTuples(TestFuncAcceptsSequencesMixin, unittest.TestCase):
arg = (1, 2, 3)

if __name__ == '__main__':
unittest.main()

This works fine and produces this output:

> python -m unittest -v test_super.py
test_func (test_super.AcceptLists) ... [1, 2, 3]
ok
test_func (test_super.AcceptStrings) ... abc
ok
test_func (test_super.AcceptTuples) ... (1, 2, 3)
ok

--
Ran 3 tests in 0.002s

OK

Am I missing anything?  If not, then why did the code snippet use the
(I believe to be misleading.) class variable approach with "func =
mySuperWhammyFunction" and "self.func(self.arg)"?

boB
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] Do not understand code snippet from "26.8. test — Regression tests package for Python"

2017-04-16 Thread boB Stepp
Thank you very much Martin; you filled in a lot of details.  I had an
overall understanding of what unittest does, but you have now enhanced
that understanding substantially.  I'm still iffy on how the mixin
class gets its test method called when this class does not subclass
from unittest.TestCase, but I think I may have an idea now on how it
is happening.  Let's get to that part of your response.

On Sun, Apr 16, 2017 at 3:59 PM, Martin A. Brown  wrote:

[snip]

> Your next question is why do the mixins work?  And how do they work?
>
> I'll make a few observations:
>
>   - [on unittest] the unit testing tools use classes because it's a
> natural way to accommodate the goal of reproducibly setting up
> arguments and/or an environment for each test (note that each
> TestCase can have its own setUp() and tearDown() methods; this
> allows isolation)
>
>   - [on unittest] each test collected by the TestLoader can be any
> Python class (as long as it is also derived from
> unittest.TestCase)
>
>   - [on your classes] your classes use a multiple inheritance
> model, deriving from TestFuncAcceptsSequencesMixin; when
> instantiated, they'll have all of the expected TestCase methods
> and the method called 'test_func'

It is here that I am struggling.  If the mixin class does not inherit
from unittest.TestCase, then how is test_func ever seen?

> In more detail, you have created three different classes, each of
> which is derived from unittest.TestCase (I'm showing just the
> signatures):
>
>   class AcceptLists(TestFuncAcceptsSequencesMixin, unittest.TestCase):
>   class AcceptStrings(TestFuncAcceptsSequencesMixin, unittest.TestCase):
>   class AcceptTuples(TestFuncAcceptsSequencesMixin, unittest.TestCase):
>
> Here's what's happening:
>
>   - TestLoader finds the files that contains the above classes (probably
> named 'test_something.py')
>
>   - Testloader imports the file 'test_something.py'; this defines your
> classes: AcceptLists, AcceptStrings and AcceptTuples (or will
> produce a traceback if the code does not import; try breaking
> your code and you should see that the import of your test code
> fails during the TestLoader phase)
>
>   - TestLoader appends the now-defined classes: AcceptLists,
> AcceptStrings and AcceptTuples to the list of tests
>
>   - control passes back to main and then to TestRunner
>
>   - for each unittest.TestCase in the list of tests, TestRunner will:
>
> - create an instance T from the defined class

This answers one important thing I was wondering about:  How do the
classes AcceptLists, AcceptStrings, and AcceptTuples get instantiated?
 Apparently the unittest machinery does this for me.

> - for each method name starting with 'test_' (you have only
>   'test_func') TestRunner will:

And here is my precise sticking point:  How does the TestRunner find
test_func?  The classes it has collected and instantiated
(AcceptLists, AcceptStrings and AcceptTuples) do not themselves call
and make use of the test_func method they inherit from the mixin
class.

>   - execute the T.setUp() method if it exists
>
>   - TestRunner will execute the method 'test_func'

The only thing that makes sense to me is that the TestRunner follows
the MRO of the inherited classes and checks for any test_xxx methods
that might exist in those superclasses.  Is this correct or do I have
a conceptual misunderstanding?

>   - collect the success / failure and any outputs
>
>   - report on the success / failure
>
>   - produce some final summary output and set the exit code
> accordingly (os.EX_OK means success, anything else is failure)
>

[snip]

> I hope my long-winded explanation amkes that a bit clearer.

More clarity has been achieved, but I am not fully there yet!

Thanks!

-- 
boB
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] Do not understand code snippet from "26.8. test — Regression tests package for Python"

2017-04-16 Thread Martin A. Brown

Greetings boB,

>2)  The big question:  What is the program flow for this program?  I
>am not seeing the order of execution here.  How is the unittest module
>handling the execution of this? 

This is a very good question and one that was (at one time) 
inobvious to me, as well.

When you write a program, you usually have a clear notion of where 
the program will begin and then can follow its execution path (from 
"if __name__ == '__main__'") through your functions, class 
instantiations and method calls.  This is the execution structure of 
your program.  (I'd imagine you have used print and/or logging to 
debug program flow, as well)

Testing flow is different.

The sequence of test execution has nothing to do with your program 
structure.  This is utterly intentional.

[ background / digression ] By breaking down a complex program into 
smaller testable pieces, you can have more assurance that your 
program is doing exactly what you intend.  Since you are breaking 
the program into smaller pieces, those pieces can (and should) be 
runnable and tested without requiring any of the other pieces.

Usually (almost always) tests are run in isolation.  This allows you 
to control exactly the arguments, environment and conditions of each 
test.

You may know most of the above already, but I repeat it because 
these facts help explain why testing tools work as they do...
[ end digression ]

Now, to the unittest module.

(Please note, I'm not an expert on unittest internals, so I may get 
a detail or two wrong.  Nonetheless, I hope that my answer will help 
you orient yourself around what's happening.)

When you run a tool to collect your tests and execute them, the path 
through your pieces of code under test has no connection whatsoever 
to process flow through your program.

The basic flow looks like this:

  * find the test cases / test suites you have written
  * run each of the tests independently, i.e. isolated conditions
  * report on the success/failure of each test case, test suite and 
the whole batch

See below for more detail of the mechanics inside the unittest 
module.  What happens when you execute your testing suite?  Let's 
say you run:

  $ python -m unittest

(Unless there is customization of TestLoader TestSuite and/or 
TestRunner) the following sequence occurs:

  1. the Python interpreter starts up

  2. Python loads the unittest module and passes control to unittest

  3. unittest.main creates an instance of unittest.TestLoader [0]

  4. unittest.TestLoader scans the filesystem, collecting a list of 
 tests to run from:

   - any test suites subclassed from unittest.TestSuite [1]
   - any test cases subclassed unittest.TestCase [2]

  5. unittest.TestLoader imports anything it found and returns the 
 list of tests to the main testing program loop

  6. the main loop passes the tests to the unittest.TextTestRunner [3],
 which executes each test and (probably) produces some output 
 telling you either that your hard work has paid off or that 
 something is still wrong

Your next question is why do the mixins work?  And how do they work?

I'll make a few observations:

  - [on unittest] the unit testing tools use classes because it's a 
natural way to accommodate the goal of reproducibly setting up 
arguments and/or an environment for each test (note that each 
TestCase can have its own setUp() and tearDown() methods; this 
allows isolation)

  - [on unittest] each test collected by the TestLoader can be any 
Python class (as long as it is also derived from 
unittest.TestCase)

  - [on your classes] your classes use a multiple inheritance 
model, deriving from TestFuncAcceptsSequencesMixin; when 
instantiated, they'll have all of the expected TestCase methods
and the method called 'test_func'

In more detail, you have created three different classes, each of 
which is derived from unittest.TestCase (I'm showing just the 
signatures):

  class AcceptLists(TestFuncAcceptsSequencesMixin, unittest.TestCase):
  class AcceptStrings(TestFuncAcceptsSequencesMixin, unittest.TestCase): 
  class AcceptTuples(TestFuncAcceptsSequencesMixin, unittest.TestCase):

Here's what's happening:

  - TestLoader finds the files that contains the above classes (probably
named 'test_something.py')

  - Testloader imports the file 'test_something.py'; this defines your
classes: AcceptLists, AcceptStrings and AcceptTuples (or will 
produce a traceback if the code does not import; try breaking 
your code and you should see that the import of your test code 
fails during the TestLoader phase)

  - TestLoader appends the now-defined classes: AcceptLists,
AcceptStrings and AcceptTuples to the list of tests

  - control passes back to main and then to TestRunner

  - for each unittest.TestCase in the list of tests, TestRunner will:

- create an instance T from the defined class

- for each method name starting with 'test_

Re: [Tutor] Do not understand code snippet from "26.8. test — Regression tests package for Python"

2017-04-16 Thread Alan Gauld via Tutor
On 16/04/17 16:21, boB Stepp wrote:

> I did this and it indeed works.  But how do I use this technique to
> unittest the given function?  I am just not seeing how to do it with
> this with this mixin approach, and I have yet to study mixins, though
> apparently I have just now started!

I'm not familiar with the mixin approach so don;t know what
they intend but...

> --
> class SuperWhammy:
> def mySuperWhammyFunction(self, any_input):
> return any_input
> 
> import unittest
> 
> class TestFuncAcceptsSequencesMixin:
> 
> obj = SuperWhammy
> func = obj.mySuperWhammyFunction

Note this is a class attribute not an instance one
so you could attach a "normal" function and call it
via the class.

> def test_func(self):
> f = self.func(self.arg)

f = TestFuncAcceptsSequencesMixin.func(self.args)

> self.assertEqual(f, self.arg)
> print(f)

But I've no idea if that's what the author intended...

I tried it on your original code and it seemed to work OK.

> 1)  I did not notice it until this AM, but I used (as above) "obj =
> SuperWhammy".  Normally I would write this as "obj = SuperWhammy()"
> with parentheses.  But I see that both work.  Are the parentheses
> unneeded when creating an object instance if there are no
> initialization arguments needed?

No, you are not creating an instance but a reference to the class.
So when you assigned the function you were in effect doing

func = SuperWhammy.mySuperWhammyFunction

Which of course works fine.

> 2)  The big question:  What is the program flow for this program?  I
> am not seeing the order of execution here.  How is the unittest module
> handling the execution of this?  The ending comment in the docs'
> example cited reads:

I'll let a unittest expert comment on that fully.

So far as I understand it, the unittest framework
just calls all the test_xxx methods of all classes
that inherit from TestCase. And because all three
test classes inherit the mixin and its test_func()
method they all execute that method but each providing
their own version of args.

Exactly how that magic is accomplished I leave to
the framework authors! ;-)


> "When using this pattern, remember that all classes that inherit from
> unittest.TestCase are run as tests. The Mixin class in the example
> above does not have any data and so can’t be run by itself, thus it
> does not inherit from unittest.TestCase."
> 
> This suggests to me that unittest "uses" the bottom three classes, but
> even though each of the three inherits from the class
> TestFuncAcceptsSequenceMixin, those classes don't have any methods
> that they call on that class, so how does its code get run?  

They inherit the test_fujnc() method from the mixin.
And the TestCase looks for  methods called test_xxx
and runs them. (It could be as simple as doing a dir(self),
I really don't know.)

> that the mixin's concepts is where I am stumbling.  I have yet to find
> a reference that is making things clear to me, though I will continue
> searching and reading.

Mixins are conceptually very simple, just small classes
expressing a capability that you inherit along with your
other super classes. There is nothing intrinsically special
about them, its more about the concept than the implementation.
(Some languages use mixins a lot and have dedicated support
for them such as not having them inherit from object to avoid
the dreaded MI diamond patterns or other similar tricks.) The
introduction of interfaces into languages like Java and C#
have made mixins less common.

-- 
Alan G
Author of the Learn to Program web site
http://www.alan-g.me.uk/
http://www.amazon.com/author/alan_gauld
Follow my photo-blog on Flickr at:
http://www.flickr.com/photos/alangauldphotos


___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] Do not understand code snippet from "26.8. test — Regression tests package for Python"

2017-04-16 Thread boB Stepp
On Sat, Apr 15, 2017 at 6:31 PM, Alan Gauld via Tutor  wrote:
> On 16/04/17 00:17, boB Stepp wrote:
>
>> --
>> #!/usr/bin/env python3
>>
>> def mySuperWhammyFunction(any_input):
>> return any_input
>
> This is a simple function, its not bound to an object

I did not name this function.  I think that if the docs' example meant
it to be a method, they would have named it, "mySuperWhammyMethod".

>>
>> import unittest
>>
>> class TestFuncAcceptsSequencesMixin:
>>
>> func = mySuperWhammyFunction
>>
>> def test_func(self):
>> self.func(self.arg)
>
> This is calling self.function which implies a method.
>
> Convert your function to a method and it should work.

I did this and it indeed works.  But how do I use this technique to
unittest the given function?  I am just not seeing how to do it with
this with this mixin approach, and I have yet to study mixins, though
apparently I have just now started!

In the modified program (per your suggestion) to test a method (I
still need to know how to make this test the original *function*!), I
now have:

--
class SuperWhammy:
def mySuperWhammyFunction(self, any_input):
return any_input

import unittest

class TestFuncAcceptsSequencesMixin:

obj = SuperWhammy
func = obj.mySuperWhammyFunction

def test_func(self):
f = self.func(self.arg)
self.assertEqual(f, self.arg)
print(f)

class AcceptLists(TestFuncAcceptsSequencesMixin, unittest.TestCase):
arg = [1, 2, 3]

class AcceptStrings(TestFuncAcceptsSequencesMixin, unittest.TestCase):
arg = 'abc'

class AcceptTuples(TestFuncAcceptsSequencesMixin, unittest.TestCase):
arg = (1, 2, 3)

if __name__ == '__main__':
unittest.main()
--

This gives me the results:

> python -m unittest -v test_super.py
test_func (test_super.AcceptLists) ... [1, 2, 3]
ok
test_func (test_super.AcceptStrings) ... abc
ok
test_func (test_super.AcceptTuples) ... (1, 2, 3)
ok

--
Ran 3 tests in 0.000s

Questions:

1)  I did not notice it until this AM, but I used (as above) "obj =
SuperWhammy".  Normally I would write this as "obj = SuperWhammy()"
with parentheses.  But I see that both work.  Are the parentheses
unneeded when creating an object instance if there are no
initialization arguments needed?

2)  The big question:  What is the program flow for this program?  I
am not seeing the order of execution here.  How is the unittest module
handling the execution of this?  The ending comment in the docs'
example cited reads:

"When using this pattern, remember that all classes that inherit from
unittest.TestCase are run as tests. The Mixin class in the example
above does not have any data and so can’t be run by itself, thus it
does not inherit from unittest.TestCase."

This suggests to me that unittest "uses" the bottom three classes, but
even though each of the three inherits from the class
TestFuncAcceptsSequenceMixin, those classes don't have any methods
that they call on that class, so how does its code get run?  I suspect
that the mixin's concepts is where I am stumbling.  I have yet to find
a reference that is making things clear to me, though I will continue
searching and reading.

-- 
boB
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] Do not understand code snippet from "26.8. test — Regression tests package for Python"

2017-04-15 Thread Alan Gauld via Tutor
On 16/04/17 00:17, boB Stepp wrote:

> --
> #!/usr/bin/env python3
> 
> def mySuperWhammyFunction(any_input):
> return any_input

This is a simple function, its not bound to an object

> 
> import unittest
> 
> class TestFuncAcceptsSequencesMixin:
> 
> func = mySuperWhammyFunction
> 
> def test_func(self):
> self.func(self.arg)

This is calling self.function which implies a method.

Convert your function to a method and it should work.

> ERROR: test_func (test_super.AcceptLists)
> --
> Traceback (most recent call last):
>   File "c:\Projects\test_super.py", line 13, in test_func
> self.func(self.arg)
> TypeError: mySuperWhammyFunction() takes 1 positional argument but 2 were 
> given

The missing self parameter...

> I suspect that both an object instance and self.arg is getting passed

Its self.
When you do

object.method()

object gets passed as the first parameter (traditionally
called self) But because your function is not a method
it does not expect a self to be passed.

HTH
-- 
Alan G
Author of the Learn to Program web site
http://www.alan-g.me.uk/
http://www.amazon.com/author/alan_gauld
Follow my photo-blog on Flickr at:
http://www.flickr.com/photos/alangauldphotos


___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


[Tutor] Do not understand code snippet from "26.8. test — Regression tests package for Python"

2017-04-15 Thread boB Stepp
In the section

https://docs.python.org/3/library/test.html#writing-unit-tests-for-the-test-package

I have been trying to make sense of the given pointer and code snippet:


Try to maximize code reuse. On occasion, tests will vary by something
as small as what type of input is used. Minimize code duplication by
subclassing a basic test class with a class that specifies the input:

class TestFuncAcceptsSequencesMixin:

func = mySuperWhammyFunction

def test_func(self):
self.func(self.arg)

class AcceptLists(TestFuncAcceptsSequencesMixin, unittest.TestCase):
arg = [1, 2, 3]

class AcceptStrings(TestFuncAcceptsSequencesMixin, unittest.TestCase):
arg = 'abc'

class AcceptTuples(TestFuncAcceptsSequencesMixin, unittest.TestCase):
arg = (1, 2, 3)

When using this pattern, remember that all classes that inherit from
unittest.TestCase are run as tests. The Mixin class in the example
above does not have any data and so can’t be run by itself, thus it
does not inherit from unittest.TestCase.


I have tried to implement this in various ways, but cannot overcome a
basic hurdle where I get an argument mismatch.  Following is my
simplest effort to implement the above which does not actually test
anything yet, but demonstrates this mismatch hurdle:

--
#!/usr/bin/env python3

def mySuperWhammyFunction(any_input):
return any_input

import unittest

class TestFuncAcceptsSequencesMixin:

func = mySuperWhammyFunction

def test_func(self):
self.func(self.arg)

class AcceptLists(TestFuncAcceptsSequencesMixin, unittest.TestCase):
arg = [1, 2, 3]

class AcceptStrings(TestFuncAcceptsSequencesMixin, unittest.TestCase):
arg = 'abc'

class AcceptTuples(TestFuncAcceptsSequencesMixin, unittest.TestCase):
arg = (1, 2, 3)

if __name__ == '__main__':
unittest.main()
--

This gives me the following result:

--
> python -m unittest test_super.py
EEE
==
ERROR: test_func (test_super.AcceptLists)
--
Traceback (most recent call last):
  File "c:\Projects\test_super.py", line 13, in test_func
self.func(self.arg)
TypeError: mySuperWhammyFunction() takes 1 positional argument but 2 were given

==
ERROR: test_func (test_super.AcceptStrings)
--
Traceback (most recent call last):
  File "c:\Projects\test_super.py", line 13, in test_func
self.func(self.arg)
TypeError: mySuperWhammyFunction() takes 1 positional argument but 2 were given

==
ERROR: test_func (test_super.AcceptTuples)
--
Traceback (most recent call last):
  File "c:\Projects\test_super.py", line 13, in test_func
self.func(self.arg)
TypeError: mySuperWhammyFunction() takes 1 positional argument but 2 were given

--
Ran 3 tests in 0.000s

FAILED (errors=3)
--

I suspect that both an object instance and self.arg is getting passed
to mySuperWhammyFunction(), but I am not seeing how the object
instance is getting passed -- if my suspicion is indeed correct.

I also am not truly understanding how this code is working within the
unittest framework.

Help!

TIA!

boB
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor