[Tutor] Questions as to how to run the same unit test multiple times on varying input data.

Walter Prins wprins at gmail.com
Sat Sep 24 09:34:12 EDT 2016


Hi,

On 24 September 2016 at 06:55, boB Stepp <robertvstepp at gmail.com> wrote:
>
>     def test_returned_len_is_70(self):
>         '''Check that the string returned by "right_justify(a_string)" is the
>         length of the entire line, i.e., 70 columns.'''
>
>         for test_string in self.test_strings:
>             with self.subTest(test_string = test_string):
>                 length_of_returned_string = (
>                     len(right_justify.right_justify(test_string)))
>                 print('The current test string is: ', test_string)
>                 self.assertEqual(length_of_returned_string, 70)
>

To add to what Benn Finney's said: The key thing to understand is that
to the test framework, this is *one* test due to there being one def
test_....() method.  The first iteration of the loop (which is a
"pass" in your mind that should be displayed) is just a part of this
one test that hasn't caused a failure.

To accomplish what you want in a generalized way you have to therefore
specify and make the framework aware of  a) the existence of a
generalized test method that takes some kind of test data to run, and
b) what the test data is.  The framework should then repeatedly run
your test, varying the input as specified and showing the failure
cases as independently run tests.

And the way to do this when using the unittest framework is as Benn's described.

I'd however suggest at this stage to not get stuck in the minutiae of
how to write fully generalized tests, and to simply write separate
test cases (seperate def's) with different data, to keep your focus on
learning TDD rather than learning the ins and outs of unittest, if you
find yourself falling down some kind of test framework learning rabbit
hole.

That said, I would suggest perhaps consider using Py.Test instead of
unittest, which is arguably much "lower friction" with less
overhead/boilerplate/ceremony required.  With Pytest, tests can be
defined via simple functions if you wish, and checks may be performed
with simple asserts if you like.  PyTest will also collect all test
functions automatically wherever they may exist, based on its
conventions.

It is therefore possible to start really simply with any code you
write, and have test functions defined alongside the code you're
writing without any explicit reference/dependency on the test
framework, which allows a very easy way of getting started.  Obviously
you can keep the tests in a separate module if you prefer, and/or make
use of the infrastructure provided by the test framework (base
classes, decorators etc) if you like or need them, but this is not
obligatory and hence makes a very direct style of getting started
possible (hence "low friction")

The kind of parameterization you want can be more compactly and simply
achieved with PyTest attributes, as follows below.  I've rewritten
your test module slightly and include a non-implemented
right_justify() method, and thus you have 3 failing tests if you run
the below module:


----------------------------begin code-----------------------------
#!/usr/bin/env python3

'''This module defines the function  right_justify(a_string) and defines
its unit tests.  When the module is run by itself it will self-test.  The
module should otherwise be imported as normal to use.'''

def right_justify(s):
    #To be completed
    pass

import pytest

# Here we use an attribute to define the various inputs and outputs with which
# to run our generalized test function test_returned_len_is_correct().
# As you can see, you define the paremeter names, followed by a list of
# tuples defining the input and expected output.
@pytest.mark.parametrize("test_string, expected_len", [
    ("Monty Python", 12),
    ("She's a witch!", 14),
    ("Beware of the rabbit!!!  She is a vicious beast who will rip"\
         " your throat out!", 77),
]
                         )
# Here's the generalized test function, that takes an input and expected length.
def test_returned_len_is_correct(test_string, expected_len):
    '''Check that the string returned by "right_justify(a_string)" is the
    length of the input'''
    actual_len = len(right_justify(test_string))
    assert actual_len == expected_len

if __name__ == '__main__':
    # This is how to invoke pytest directly from this module as the
main function.
    # Note this is an uncommon pytest usage pattern.  More common is to run it
    # from command line in your source folder, and have pytest collect all the
    # tests from all modules automatically. See
http://doc.pytest.org/en/latest/usage.html
    pytest.main([__file__])

----------------------------end code-----------------------------

Hope that gives you some ideas.

An aside, you might also consider some of the coding challenge sites
which are also a fun way to practice your TDD skills.  For example:
https://www.hackerrank.com/domains/python/py-introduction
https://www.codeeval.com
https://www.codewars.com

Best wishes,

Walter


More information about the Tutor mailing list