Testing corner cases by iteration

Corner cases will appear as we continue to develop our code. By capturing corner cases in an iterable list, there is less code to write and capture another test scenario. This can increase our efficiency at testing new scenarios.

How to do it...

  1. Create a new file called recipe23.py and use it to store all our code for this recipe.
  2. Create a function that converts base 10 to any other base.
    def convert_to_basen(value, base):
        import math
    
        def _convert(remaining_value, base, exp):
            def stringify(value):
                if value > 9:
                    return chr(value + ord('a')-10)
                else:
                    return str(value)
    
            if remaining_value >= 0 and exp >= 0:
                factor = int(math.pow(base, exp))
                if factor <= remaining_value:
                    multiple = remaining_value / factor
                    return stringify(multiple) + 
                      _convert(remaining_value-multiple*factor, 
                                    base, exp-1)
                else:
                    return "0" + 
                           _convert(remaining_value, base, exp-1)
            else:
                return ""
    
        return "%s/%s" % (_convert(value, base, 
                             int(math.log(value, base))), base)
  3. Add some doc tests that include an array of input values to generate an array of expected outputs. Include one failure.
    def convert_to_basen(value, base):
        """Convert a base10 number to basen.
    
        Base 2
        >>> inputs = [(1,2,'1/2'), (2,2,'11/2')]
        >>> for value,base,expected in inputs:
        ...     actual = convert_to_basen(value,base)
        ...     assert actual == expected, 'expected: %s actual: %s' % (expected, actual)
    
        >>> convert_to_basen(0, 2)
        Traceback (most recent call last):
           ...
        ValueError: math domain error
    
        Base 36.
        >>> inputs = [(1,36,'1/36'), (35,36,'z/36'), (36,36,'10/36')]
        >>> for value,base,expected in inputs:
        ...     actual = convert_to_basen(value,base)
        ...     assert actual == expected, 'expected: %s actual: %s' % (expected, value)
    
        >>> convert_to_basen(0, 36)
        Traceback (most recent call last):
           ...
        ValueError: math domain error
        """
        import math
  4. Add a test runner.
    if __name__ == "__main__":
        import doctest
        doctest.testmod()
  5. Run the recipe.
    How to do it...

    Tip

    In the previous screenshot, the key information is on this line: AssertionError: expected: 11/2 actual: 10/2. Is this test failure a bit contrived? Sure it is. But seeing a test case shows useful output is not. It's important to verify that our tests give us enough information to fix either the tests or the code.

How it works...

We created an array with each entry containing both the input data as well as the expected output. This provides us with an easy way to glance at a set of test cases.

Then, we iterate over each test case, calculate the actual value, and run it through a Python assert. An important part that is needed is the custom message 'expected: %s actual: %s'. Without it, we would never get the information to tell us which test case failed.

Tip

What if one test case fails?

If one of the tests in the array fails, then that code block exits and skips over the rest of the tests. This is the trade off for having a more succinct set of tests.

Does this type of test fit better into doctest or unittest?

Here are some criteria that are worth considering when deciding whether to put these tests in doctest:

  • Is the code easy to comprehend at a glance?
  • Is this clear, succinct, useful information when users view the docstrings?

If there is little value in having this in the documentation, and if it clutters the code, then that is a strong hint that this test block belongs to a separate test module.

See also

Testing corner cases by iteration section of Chapter 1

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.254.61