--- title: Python Testing Design and Clarity Guide tags: Documentation, 2018, Python --- # Python Testing Design and Clarity Guide ## Testing 1. All functions require examples/test cases, including helper functions and testing functions 2. Examples/test cases should reflect various input scenarios so that you exercise the possible main behaviors of your function. You do not need to test every input or every input combination, but rather enough to be confident that your function works with various inputs. 3. Test the "edge cases" of functions. For example, functions that take in numeric inputs and work differently depending on the range that number is in should test the boundaries of those ranges. 4. Even if your function does not work as expected, you can still write examples/test cases for it and receive full testing credit. 6. If a function has a side effect (like modifying a list), **test that side effect**. 7. If a function both has a side effect *and* returns information, test **both the main return and the side effect**. 8. If a function *fails* in certain cases, test that failure (see below example on how to test errors). 9. If a function modifies (or mutates) data, set up testing data rather than working on a global piece of data/dataset. ## Setting up `pytest` * To use pytest, you need to install the package first: * Open a terminal in VS Code (`Terminal > New Terminal` menu) * Install the pytest package in your terminal by running `pip3 install pytest` :::warning **Note:** If you get an error, you might have to run `pip3 install --user pytest` :::info ![](https://i.imgur.com/U7cCnJu.png) ::: * Then, put `import pytest` at the top of your Python test file. * All of your tests must be contained within functions that begin with `test_` * **Example of good testing:** ```python import pytest def rem_if_gt_3(lst : list, numb : int): """ given a list and a number, remove that number from the list if it is greater than 3 do nothing if the numb is <= 3. raise an error if numb is not in the list """ if numb not in lst: raise ValueError('numb must be in list') if numb > 3: lst.remove(numb) def test_rem(): """ tests the rem_if_gt_3 function """ test_list = [0, 4, 5, 3] rem_if_gt_3(test_list, 4) assert test_list == [0, 5, 3] assert rem_if_gt_3(test_list, 0) == None # testing errors: with pytest.raises(ValueError): rem_if_gt_3(test_list, 6) with pytest.raises(ValueError): rem_if_gt_3(test_list, 2) ``` ## Design 1. Use constants where appropriate. 2. Use helper functions where appropriate. 3. Make sure your helper functions and constants are not redundant. There is no point in making this function: ```python def string_to_lower(s : str) -> str: return s.lower() ``` since you could always use `s.lower()` instead. ## Clarity 1. Write docstrings for all functions, including helper and nested functions. A good docstring gives a description of the function, including its input(s) and output. Ideally, by looking at the docstring you know what the function does and how to use it without looking at the function body itself. You can include docstrings as a comment with `"""` just below your function signatures. E.g. ``` def sum_of_list(l : list) -> int: """Computes the sum of integers in a list""" ``` 2. Give constants and helper functions useful names. 3. All functions require type annotations on inputs and output. You can omit output annotation for functions that have no return (for example functions that only need to `print`). 5. Names of constants and functions should be lower case and underscore separated. For configuration constants (for example the height of a character) it is acceptable to use all caps names. 5. Keep lines under 80 characters. 7. Indent your code properly. If you are using Pycharm, this should be happening automatically. If you are not using Pycharm, use 4 spaces to indent your code (*not* the tab character) and keep arguments of functions lined up: ```python def func_name(long_parameter_1 : int, long_parameter_2 : str long_parameter_3 : list, long_parameter_4 : list) -> int: ... func_name(long_argument_1, long_argument_2, long_argument_3, long_argument_4) ``` 8. `return` statements should be not be written like this: ```python return(value) ``` but rather like this: ```python return value ``` 9. `if` statements should be written with newlines: ```python if condition1: print('condition1') elif condition2: print('condition2') else: print('condition3') ```