# Python Testing Design and Clarity Guide This page has been updated with references to Ed Workspaces. For general setup and information on how to use Ed Workspaces, please see our [guide](https://hackmd.io/@cs111/python-guide-ed#Writing-and-running-tests). ## 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` Follow our [guide](https://hackmd.io/@cs111/python-guide-ed#Writing-and-running-tests) ## Example of good testing: **Code file (`rem.py`):** ```python 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) ``` **Test file (`test_rem.py`):** ```python import pytest from rem import * 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 intermediate variables where appropriate. 2. Use helper functions where appropriate. 3. Make sure your helper functions and variables 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 0. Just like for Pyret, include labels for the tasks as comments (e.g. `# Task 1`) 1. Write docstrings for all functions, including helper and nested functions. A good docstring gives a description of the function, including its input(s), output, and any side effects (mutation that affects data used outside of the function). 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 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 variables 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 variables and functions should be lower case and underscore separated. For variables that serve as *configuration constants* (values that won't get modified over the course of a program; for example the height of a character) it is acceptable to use all caps names. 5. Keep lines to a reasonable length. Even though Ed Workspaces doesn't show a vertical ruler when your line exceeds some number of characters, aim for a max line length of 80 characters (text fills up a litle over half of the screen at 100% font size). 7. Indent your code properly. Use 4 spaces to indent your code (*not* the tab character). If you are using Ed Workspaces and kept the default settings, this should be happening automatically. If you need to go to the next lline to define/call a function, keep the inputs 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') ```