Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.python > #89811 > unrolled thread
| Started by | Cecil Westerhof <Cecil@decebal.nl> |
|---|---|
| First post | 2015-05-03 00:29 +0200 |
| Last post | 2015-05-03 11:22 +0200 |
| Articles | 13 — 6 participants |
Back to article view | Back to comp.lang.python
Is this a good way to implement testing Cecil Westerhof <Cecil@decebal.nl> - 2015-05-03 00:29 +0200
Re: Is this a good way to implement testing Mark Lawrence <breamoreboy@yahoo.co.uk> - 2015-05-03 00:17 +0100
Re: Is this a good way to implement testing Terry Reedy <tjreedy@udel.edu> - 2015-05-02 20:06 -0400
Re: Is this a good way to implement testing Paul Rubin <no.email@nospam.invalid> - 2015-05-02 20:58 -0700
Re: Is this a good way to implement testing Ben Finney <ben+python@benfinney.id.au> - 2015-05-03 14:49 +1000
Re: Is this a good way to implement testing Cecil Westerhof <Cecil@decebal.nl> - 2015-05-03 09:36 +0200
Re: Is this a good way to implement testing Mark Lawrence <breamoreboy@yahoo.co.uk> - 2015-05-03 09:38 +0100
Re: Is this a good way to implement testing Peter Otten <__peter__@web.de> - 2015-05-03 10:45 +0200
Re: Is this a good way to implement testing Cecil Westerhof <Cecil@decebal.nl> - 2015-05-03 11:49 +0200
Re: Is this a good way to implement testing Mark Lawrence <breamoreboy@yahoo.co.uk> - 2015-05-03 11:21 +0100
Re: Is this a good way to implement testing Cecil Westerhof <Cecil@decebal.nl> - 2015-05-03 12:50 +0200
Re: Is this a good way to implement testing Ben Finney <ben+python@benfinney.id.au> - 2015-05-03 18:52 +1000
Re: Is this a good way to implement testing Peter Otten <__peter__@web.de> - 2015-05-03 11:22 +0200
| From | Cecil Westerhof <Cecil@decebal.nl> |
|---|---|
| Date | 2015-05-03 00:29 +0200 |
| Subject | Is this a good way to implement testing |
| Message-ID | <878ud6mx4y.fsf@Equus.decebal.nl> |
Still on my journey to learn Python.
At the moment I define the test functionality in the following way:
if __name__ == '__main__':
keywords = [
'all',
'factorial',
'fibonacci',
'happy',
'lucky',
]
keywords_msg = [
'--all',
'--factorial',
'--fibonacci',
'--happy',
'--lucky',
]
(options,
extraParams) = getopt.getopt(sys.argv[1:], '', keywords)
progname = split(sys.argv[0])[1]
if len(options) > 1 or len(extraParams) != 0:
error = '{0}: Wrong parameters ({1})'. \
format(progname, ' '.join(sys.argv[1:]))
usage = ' {0} {1}'.format(progname, ' | '.join(keywords_msg))
print(error, file = sys.stderr)
print(usage, file = sys.stderr)
sys.exit(1)
do_all = do_factorial = do_fibonacci = do_happy = do_lucky = False
if len(options) == 0:
do_all = True
else:
action = options[0][0]
if action == '--all':
do_all = True
elif action == '--factorial':
do_factorial = True
elif action == '--fibonacci':
do_fibonacci = True
elif action == '--happy':
do_happy = True
elif action == '--lucky':
do_lucky = True
else:
print >> sys.stderr, progname + ': Unhandled parameter ' + action
sys.exit(1)
if do_all or do_factorial:
.
.
.
Is this an acceptable way of working?
--
Cecil Westerhof
Senior Software Engineer
LinkedIn: http://www.linkedin.com/in/cecilwesterhof
[toc] | [next] | [standalone]
| From | Mark Lawrence <breamoreboy@yahoo.co.uk> |
|---|---|
| Date | 2015-05-03 00:17 +0100 |
| Message-ID | <mailman.35.1430608701.12865.python-list@python.org> |
| In reply to | #89811 |
On 02/05/2015 23:29, Cecil Westerhof wrote:
> Still on my journey to learn Python.
>
> At the moment I define the test functionality in the following way:
> if __name__ == '__main__':
> keywords = [
> 'all',
> 'factorial',
> 'fibonacci',
> 'happy',
> 'lucky',
> ]
> keywords_msg = [
> '--all',
> '--factorial',
> '--fibonacci',
> '--happy',
> '--lucky',
> ]
> (options,
> extraParams) = getopt.getopt(sys.argv[1:], '', keywords)
> progname = split(sys.argv[0])[1]
>
> if len(options) > 1 or len(extraParams) != 0:
> error = '{0}: Wrong parameters ({1})'. \
> format(progname, ' '.join(sys.argv[1:]))
> usage = ' {0} {1}'.format(progname, ' | '.join(keywords_msg))
> print(error, file = sys.stderr)
> print(usage, file = sys.stderr)
> sys.exit(1)
>
> do_all = do_factorial = do_fibonacci = do_happy = do_lucky = False
> if len(options) == 0:
> do_all = True
> else:
> action = options[0][0]
> if action == '--all':
> do_all = True
> elif action == '--factorial':
> do_factorial = True
> elif action == '--fibonacci':
> do_fibonacci = True
> elif action == '--happy':
> do_happy = True
> elif action == '--lucky':
> do_lucky = True
> else:
> print >> sys.stderr, progname + ': Unhandled parameter ' + action
> sys.exit(1)
>
> if do_all or do_factorial:
> .
> .
> .
>
> Is this an acceptable way of working?
>
For code like the above I prefer the third party docopt module
https://github.com/docopt/docopt although you could also try
https://docs.python.org/3/library/argparse.html#module-argparse
The standard library unit testing framework is here
https://docs.python.org/3/library/unittest.html#module-unittest but also
see https://wiki.python.org/moin/PythonTestingToolsTaxonomy
--
My fellow Pythonistas, ask not what our language can do for you, ask
what you can do for our language.
Mark Lawrence
[toc] | [prev] | [next] | [standalone]
| From | Terry Reedy <tjreedy@udel.edu> |
|---|---|
| Date | 2015-05-02 20:06 -0400 |
| Message-ID | <mailman.38.1430611594.12865.python-list@python.org> |
| In reply to | #89811 |
On 5/2/2015 6:29 PM, Cecil Westerhof wrote:
> At the moment I define the test functionality in the following way:
Any automated testing is better than none. For idlelib, I use unittest.
For an individual project with specialized needs, I use a custom test
framework tuned to those needs.
> else:
> action = options[0][0]
> if action == '--all':
> do_all = True
> elif action == '--factorial':
> do_factorial = True
> elif action == '--fibonacci':
> do_fibonacci = True
> elif action == '--happy':
> do_happy = True
> elif action == '--lucky':
> do_lucky = True
> else:
> print >> sys.stderr, progname + ': Unhandled parameter ' + action
> sys.exit(1)
There is usually a way to factor out the duplication of repetitive code
like this. Often, a dict is somehow involved. I believe the following
is equivalent to the above.
else:
action = options[0][0]
try:
globals()['do_'+action[2:]] = True
except KeyError:
print >> sys.stderr, progname + ': Unhandled parameter ' + action
sys.exit(1)
--
Terry Jan Reedy
[toc] | [prev] | [next] | [standalone]
| From | Paul Rubin <no.email@nospam.invalid> |
|---|---|
| Date | 2015-05-02 20:58 -0700 |
| Message-ID | <87k2wq2tx8.fsf@jester.gateway.sonic.net> |
| In reply to | #89811 |
Cecil Westerhof <Cecil@decebal.nl> writes: > Still on my journey to learn Python. > > At the moment I define the test functionality in the following way: > action = options[0][0] > if action == '--all': ... Yecch, use an option parsing library for that, whichever one is currently fashionable. I think optparse is deprecated now but I still use it because I'm used to it. > Is this an acceptable way of working? You should also use the currently fashionable unit testing library. I use unittest because yada yada but I think it's now considered old school.
[toc] | [prev] | [next] | [standalone]
| From | Ben Finney <ben+python@benfinney.id.au> |
|---|---|
| Date | 2015-05-03 14:49 +1000 |
| Message-ID | <mailman.45.1430628582.12865.python-list@python.org> |
| In reply to | #89825 |
Paul Rubin <no.email@nospam.invalid> writes: > Cecil Westerhof <Cecil@decebal.nl> writes: > > > action = options[0][0] > > if action == '--all': ... > > Yecch, use an option parsing library for that, whichever one is > currently fashionable. I think optparse is deprecated now but I still > use it because I'm used to it. Yes, ‘optparse’ has been superseded by the more capable ‘argparse’ library <URL:https://docs.python.org/3/library/argparse.html>. Migrating from ‘optparse’ to ‘argparse’ is quite straightforward, since the latter was designed to be very similar. > > Is this an acceptable way of working? > > You should also use the currently fashionable unit testing library. I > use unittest because yada yada but I think it's now considered old > school. I disagree, using ‘unittest’ is still quite normal. It's also in the standard library, unlike proposed replacements. -- \ “Instead of having ‘answers’ on a math test, they should just | `\ call them ‘impressions’, and if you got a different | _o__) ‘impression’, so what, can't we all be brothers?” —Jack Handey | Ben Finney
[toc] | [prev] | [next] | [standalone]
| From | Cecil Westerhof <Cecil@decebal.nl> |
|---|---|
| Date | 2015-05-03 09:36 +0200 |
| Message-ID | <87383em7sn.fsf@Equus.decebal.nl> |
| In reply to | #89811 |
Op Sunday 3 May 2015 00:29 CEST schreef Cecil Westerhof:
> Still on my journey to learn Python.
>
> At the moment I define the test functionality in the following way:
> if __name__ == '__main__':
> keywords = [
> 'all',
> 'factorial',
> 'fibonacci',
> 'happy',
> 'lucky',
> ]
> keywords_msg = [
> '--all',
> '--factorial',
> '--fibonacci',
> '--happy',
> '--lucky',
> ]
> (options,
> extraParams) = getopt.getopt(sys.argv[1:], '', keywords)
> progname = split(sys.argv[0])[1]
>
> if len(options) > 1 or len(extraParams) != 0:
> error = '{0}: Wrong parameters ({1})'. \
> format(progname, ' '.join(sys.argv[1:]))
> usage = ' {0} {1}'.format(progname, ' | '.join(keywords_msg))
> print(error, file = sys.stderr)
> print(usage, file = sys.stderr)
> sys.exit(1)
>
> do_all = do_factorial = do_fibonacci = do_happy = do_lucky = False
> if len(options) == 0:
> do_all = True
> else:
> action = options[0][0]
> if action == '--all':
> do_all = True
> elif action == '--factorial':
> do_factorial = True
> elif action == '--fibonacci':
> do_fibonacci = True
> elif action == '--happy':
> do_happy = True
> elif action == '--lucky':
> do_lucky = True
> else:
> print >> sys.stderr, progname + ': Unhandled parameter ' + action
> sys.exit(1)
>
> if do_all or do_factorial:
> .
> .
> .
>
> Is this an acceptable way of working?
Thanks for the tips. For most I have to read a ‘little’ first, so I
will not implement them immediately.
Another question. Is it acceptable to have it in the module itself, or
should I put it in something like test_<module>.py? The code for
testing is bigger as the code for the implementation, so I am leaning
to putting it in a separate file.
--
Cecil Westerhof
Senior Software Engineer
LinkedIn: http://www.linkedin.com/in/cecilwesterhof
[toc] | [prev] | [next] | [standalone]
| From | Mark Lawrence <breamoreboy@yahoo.co.uk> |
|---|---|
| Date | 2015-05-03 09:38 +0100 |
| Message-ID | <mailman.51.1430642317.12865.python-list@python.org> |
| In reply to | #89833 |
On 03/05/2015 08:36, Cecil Westerhof wrote: > > Thanks for the tips. For most I have to read a ‘little’ first, so I > will not implement them immediately. > Another question. Is it acceptable to have it in the module itself, or > should I put it in something like test_<module>.py? The code for > testing is bigger as the code for the implementation, so I am leaning > to putting it in a separate file. > I'd go for the former if your implementation code base is measured in hundreds of lines of code, anything larger and I'd probably split the test code out. That's just my own rule of thumb, I'm sure others will quote different figures, but what it ultimately gets down to is what are you comfortable with? I ask because you should keep in mind that Python modules containing several classes and running into thousands of lines of code are fairly common. Python is not Java, and Java isn't Python either :) -- My fellow Pythonistas, ask not what our language can do for you, ask what you can do for our language. Mark Lawrence
[toc] | [prev] | [next] | [standalone]
| From | Peter Otten <__peter__@web.de> |
|---|---|
| Date | 2015-05-03 10:45 +0200 |
| Message-ID | <mailman.53.1430642740.12865.python-list@python.org> |
| In reply to | #89833 |
Cecil Westerhof wrote:
> Another question. Is it acceptable to have it in the module itself, or
> should I put it in something like test_<module>.py? The code for
> testing is bigger as the code for the implementation, so I am leaning
> to putting it in a separate file.
Definitely use an established testing framework instead of rolling your own,
and definitely put it into a separate file -- by the time there is good
coverage the test code is usually much bigger than the tested code.
Be aware that there is also doctest which scans docstrings for text
resembling interactive Python sessions. Doctests are both tests and usage
examples, so I think it's good to put a few of these into the module. Here's
how it works:
$ cat factorial.py
def factorial(n):
"""Calculate the factorial 1 * 2 * ... * n.
>>> factorial(0)
1
>>> factorial(1)
1
>>> factorial(10)
3628800
"""
return 1
$ python3 -m doctest factorial.py
**********************************************************************
File "/home/peter/clpy/factorial.py", line 8, in factorial.factorial
Failed example:
factorial(10)
Expected:
3628800
Got:
1
**********************************************************************
1 items had failures:
1 of 3 in factorial.factorial
***Test Failed*** 1 failures.
$
[toc] | [prev] | [next] | [standalone]
| From | Cecil Westerhof <Cecil@decebal.nl> |
|---|---|
| Date | 2015-05-03 11:49 +0200 |
| Message-ID | <87iocakn2f.fsf@Equus.decebal.nl> |
| In reply to | #89837 |
Op Sunday 3 May 2015 10:45 CEST schreef Peter Otten: > Cecil Westerhof wrote: > >> Another question. Is it acceptable to have it in the module itself, >> or should I put it in something like test_<module>.py? The code for >> testing is bigger as the code for the implementation, so I am >> leaning to putting it in a separate file. > > Definitely use an established testing framework instead of rolling > your own, and definitely put it into a separate file -- by the time > there is good coverage the test code is usually much bigger than the > tested code. Yep, the module already has 370 lines of testing code and only 225 of working code. And I just started. > Be aware that there is also doctest which scans docstrings for text > resembling interactive Python sessions. Doctests are both tests and > usage examples, so I think it's good to put a few of these into the > module. Here's how it works: > > $ cat factorial.py > def factorial(n): > """Calculate the factorial 1 * 2 * ... * n. > >>>> factorial(0) > 1 >>>> factorial(1) > 1 >>>> factorial(10) > 3628800 """ return 1 $ python3 -m doctest factorial.py > ********************************************************************** > File "/home/peter/clpy/factorial.py", line 8, in factorial.factorial > Failed example: factorial(10) Expected: 3628800 Got: 1 > ********************************************************************** > 1 items had failures: 1 of 3 in factorial.factorial ***Test > Failed*** 1 failures. $ That looks very promising. But I use the test to verify the correctness and show the performance. Is that also possible? Or should I split those out. -- Cecil Westerhof Senior Software Engineer LinkedIn: http://www.linkedin.com/in/cecilwesterhof
[toc] | [prev] | [next] | [standalone]
| From | Mark Lawrence <breamoreboy@yahoo.co.uk> |
|---|---|
| Date | 2015-05-03 11:21 +0100 |
| Message-ID | <mailman.60.1430648512.12865.python-list@python.org> |
| In reply to | #89846 |
On 03/05/2015 10:49, Cecil Westerhof wrote: > Op Sunday 3 May 2015 10:45 CEST schreef Peter Otten: > >> Cecil Westerhof wrote: >> >>> Another question. Is it acceptable to have it in the module itself, >>> or should I put it in something like test_<module>.py? The code for >>> testing is bigger as the code for the implementation, so I am >>> leaning to putting it in a separate file. >> >> Definitely use an established testing framework instead of rolling >> your own, and definitely put it into a separate file -- by the time >> there is good coverage the test code is usually much bigger than the >> tested code. > > Yep, the module already has 370 lines of testing code and only 225 of > working code. And I just started. > > >> Be aware that there is also doctest which scans docstrings for text >> resembling interactive Python sessions. Doctests are both tests and >> usage examples, so I think it's good to put a few of these into the >> module. Here's how it works: >> >> $ cat factorial.py >> def factorial(n): >> """Calculate the factorial 1 * 2 * ... * n. >> >>>>> factorial(0) >> 1 >>>>> factorial(1) >> 1 >>>>> factorial(10) >> 3628800 """ return 1 $ python3 -m doctest factorial.py >> ********************************************************************** >> File "/home/peter/clpy/factorial.py", line 8, in factorial.factorial >> Failed example: factorial(10) Expected: 3628800 Got: 1 >> ********************************************************************** >> 1 items had failures: 1 of 3 in factorial.factorial ***Test >> Failed*** 1 failures. $ > > That looks very promising. But I use the test to verify the > correctness and show the performance. Is that also possible? Or should > I split those out. > Get it working correctly and if it's fast enough for your needs then job done. If and only if you actually have a performance issue profile your code to find the bottlenecks, as gut instinct about Python performance is wrong 99.99% of the time. -- My fellow Pythonistas, ask not what our language can do for you, ask what you can do for our language. Mark Lawrence
[toc] | [prev] | [next] | [standalone]
| From | Cecil Westerhof <Cecil@decebal.nl> |
|---|---|
| Date | 2015-05-03 12:50 +0200 |
| Message-ID | <87618akk8y.fsf@Equus.decebal.nl> |
| In reply to | #89847 |
Op Sunday 3 May 2015 12:21 CEST schreef Mark Lawrence: >> That looks very promising. But I use the test to verify the >> correctness and show the performance. Is that also possible? Or >> should I split those out. >> > > Get it working correctly and if it's fast enough for your needs then > job done. If and only if you actually have a performance issue > profile your code to find the bottlenecks, as gut instinct about > Python performance is wrong 99.99% of the time. I'll split them out. One is to verify the correctness, the other is to show the difference between implementations. For example: even with memoization the recursive version of fibonacci is much slower as the iterative version. -- Cecil Westerhof Senior Software Engineer LinkedIn: http://www.linkedin.com/in/cecilwesterhof
[toc] | [prev] | [next] | [standalone]
| From | Ben Finney <ben+python@benfinney.id.au> |
|---|---|
| Date | 2015-05-03 18:52 +1000 |
| Message-ID | <mailman.55.1430643305.12865.python-list@python.org> |
| In reply to | #89833 |
Peter Otten <__peter__@web.de> writes: > Be aware that there is also doctest which scans docstrings for text > resembling interactive Python sessions. Doctests are both tests and > usage examples, so I think it's good to put a few of these into the > module. Yes, it's definitely a good idea to put some examples into the docstrings as doctests. Be aware, though, that this is *not* a substitute for unit tests; doctest is for testing your documentation, not testing your code. You should not aim for extensive coverage in doctests. Heavy use of doctests makes for bad tests *and* bad documentation. Instead, write only those examples that the reader will find helpful to understand normal usage; and use the ‘doctest’ module to test that your documentation is still accurate. Put all your other broad-coverage tests elsewhere (unit tests, behaviour tests, etc.) and leave the docstrings readable. -- \ “Generally speaking, the errors in religion are dangerous; | `\ those in philosophy only ridiculous.” —David Hume, _A Treatise | _o__) of Human Nature_, 1739 | Ben Finney
[toc] | [prev] | [next] | [standalone]
| From | Peter Otten <__peter__@web.de> |
|---|---|
| Date | 2015-05-03 11:22 +0200 |
| Message-ID | <mailman.57.1430644949.12865.python-list@python.org> |
| In reply to | #89811 |
Terry Reedy wrote:
> On 5/2/2015 6:29 PM, Cecil Westerhof wrote:
>
>> At the moment I define the test functionality in the following way:
>
> Any automated testing is better than none. For idlelib, I use unittest.
> For an individual project with specialized needs, I use a custom test
> framework tuned to those needs.
>
>> else:
>> action = options[0][0]
>> if action == '--all':
>> do_all = True
>> elif action == '--factorial':
>> do_factorial = True
>> elif action == '--fibonacci':
>> do_fibonacci = True
>> elif action == '--happy':
>> do_happy = True
>> elif action == '--lucky':
>> do_lucky = True
>> else:
>> print >> sys.stderr, progname + ': Unhandled parameter '
>> + action sys.exit(1)
>
> There is usually a way to factor out the duplication of repetitive code
> like this.
> Often, a dict is somehow involved.
That cannot be stressed enough. In Python if you test against multiple
constant values you should always consider a dict-based approach.
Also, you should not shy away from using functions as variables. Instead of
do_lucky = True
...
if do_lucky:
do_lucky_func()
you can often write something more direct
action = do_lucky_func
...
action_func()
> I believe the following
> is equivalent to the above.
>
> else:
> action = options[0][0]
> try:
> globals()['do_'+action[2:]] = True
> except KeyError:
> print >> sys.stderr, progname + ': Unhandled parameter ' + action
> sys.exit(1)
To go a bit farther down that path of using naming conventions, dict instead
of extensive if ... elif ... and functions instead of flags here's a
complete implementation with argparse:
$ python3 choose_action.py -h
usage: choose_action.py [-h] [{all,fibonacci,factorial}]
positional arguments:
{all,fibonacci,factorial}
Known actions: all, fibonacci, factorial
optional arguments:
-h, --help show this help message and exit
$ python3 choose_action.py all
fibonacci
factorial
$ python3 choose_action.py
factorial
fibonacci
$ python3 choose_action.py fibonacci
fibonacci
$ python3 choose_action.py non-existent
usage: choose_action.py [-h] [{all,factorial,fibonacci}]
choose_action.py: error: argument action: invalid choice: 'non-existent'
(choose from 'all', 'factorial', 'fibonacci')
$ cat choose_action.py
import argparse
def do_factorial():
print("factorial")
# ...
def do_fibonacci():
print("fibonacci")
# ...
def do_all():
for name, action in lookup.items():
if name != "all":
action()
lookup = {
name[3:]: func
for name, func in globals().items()
if name.startswith("do_")
}
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"action",
nargs="?",
default="all",
choices=lookup,
help="Known actions: %(choices)s")
args = parser.parse_args()
lookup[args.action]()
$
[toc] | [prev] | [standalone]
Back to top | Article view | comp.lang.python
csiph-web