Re: [Tutor] Do not understand code snippet from "26.8. test — Regression tests package for Python"
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"
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"
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"
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"
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"
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"
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"
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"
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"
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"
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"
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"
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"
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