Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #89811 > unrolled thread

Is this a good way to implement testing

Started byCecil Westerhof <Cecil@decebal.nl>
First post2015-05-03 00:29 +0200
Last post2015-05-03 11:22 +0200
Articles 13 — 6 participants

Back to article view | Back to comp.lang.python


Contents

  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

#89811 — Is this a good way to implement testing

FromCecil Westerhof <Cecil@decebal.nl>
Date2015-05-03 00:29 +0200
SubjectIs 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]


#89814

FromMark Lawrence <breamoreboy@yahoo.co.uk>
Date2015-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]


#89818

FromTerry Reedy <tjreedy@udel.edu>
Date2015-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]


#89825

FromPaul Rubin <no.email@nospam.invalid>
Date2015-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]


#89828

FromBen Finney <ben+python@benfinney.id.au>
Date2015-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]


#89833

FromCecil Westerhof <Cecil@decebal.nl>
Date2015-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]


#89835

FromMark Lawrence <breamoreboy@yahoo.co.uk>
Date2015-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]


#89837

FromPeter Otten <__peter__@web.de>
Date2015-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]


#89846

FromCecil Westerhof <Cecil@decebal.nl>
Date2015-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]


#89847

FromMark Lawrence <breamoreboy@yahoo.co.uk>
Date2015-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]


#89855

FromCecil Westerhof <Cecil@decebal.nl>
Date2015-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]


#89840

FromBen Finney <ben+python@benfinney.id.au>
Date2015-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]


#89842

FromPeter Otten <__peter__@web.de>
Date2015-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