Chapter 3: Using PyTest, MyPy and flake8 for testing
Tags: python, testing, pytest, flake8
Published: 2022-10-21 12:06:29

- This chapter is part of the series: {% embed https://dev.to/zim95/understanding-the-python-tox-ecosystem-3noi %}
- Please consider reading the previous chapter (Chapter 2) before moving forward. This chapter builds upon the project structure discussed in the previous chapter (Chapter 2).
{% embed https://dev.to/zim95/chapter-2-the-need-for-a-project-structure-82h %}
- From the previous chapter (Chapter 2), we have the following project structure:
    ```markdown
        - src/
        - something/
            - __init__.py
        	- app.py
        - tests/
        	- __init__.py
        	- test_app.py
        - .gitignore
        - LICENSE
        - pyproject.toml
        - README.md
        - requirements.txt
        - setup.cfg
        - setup.py
    ```
- Here we are going to setup `pytest`, `mypy` and `flake8`.
- ### TABLE OF CONTENTS
    - [3.1 - Adding new dependencies to requirements](#part-3_1)
    - [3.2 - Adding to our metadata in setup.cfg](#part-3_2)
    - [3.3 - Adding python configurations to pyproject.toml](#part-3_3)

### 3.1 Adding new dependencies to requirements <a name="part-3_1"></a>
- Now we need to install more libraries. Namely `pytest`, `mypy` and `flake8`.
- So our `requirements.txt` file looks something like this. We have added dependencies for `pytest`:
    ```
    flake==3.9.4
    tox==3.24.3
    pytest==6.2.5
    pytest-cov==2.12.1
    mypy==0.910
    ```
- Instead of directly overwriting the `requirements.txt` file, we create a new file called `requirements_dev.txt`. Since testing is required in `dev` environment.
- So we have two requirement files:
    - `requirements.txt` which we use while running our code:
        ```
        requests==2.26.0
        ```
    - `requirements_dev.txt` which holds dev dependencies like our test, lint and type check libraries.
        ```
        flake==3.9.4
        tox==3.24.3
        pytest==6.2.5
        pytest-cov==2.12.1
        mypy==0.910
        ```
- While running tests we will use `requirements_dev.txt` file to install dependencies.

### 3.2 Adding to our metadata in `setup.cfg` <a name="part-3_2"></a>
- Our `setup.cfg` file looks something like this:
        
    ```
        [metadata]
        name = something
        description = just some dummy codebase
        author = Coding with Zim
        license = MIT
        license_file = LICENSE
        platforms = unix, linux, osx, cygwin, win32
        classifiers = 
        	Programming Language :: Python :: 3
        	Programming Language :: Python :: 3 :: Only
        	Programming Language :: Python :: 3.6
        	Programming Language :: Python :: 3.7
        	Programming Language :: Python :: 3.8
        	Programming Language :: Python :: 3.9
        
        [options]
        packages =
        	something
        install_requires =
        	requests>=2
        python_requires = >=3.6
        package_dir =
        	=src
        zip_safe = no
        
        [options.extras_require]
        testing=
        	pytest>=6.0
        	pytest-cov>=2.0
        	mypy>=0.910
        	flake8>=3.9
        	tox>=3.24
        
        [options.package_data]
        something = py.typed
        
        [flake8]
        max-line-length = 160
    ```
- `options.extras_require` are optional dependencies. We can read more about it here: [https://setuptools.pypa.io/en/latest/userguide/dependency_management.html](https://setuptools.pypa.io/en/latest/userguide/dependency_management.html)
- `options.package_data` is a type of `Data File Support` , we can read more about it here: [https://setuptools.pypa.io/en/latest/userguide/datafiles.html](https://setuptools.pypa.io/en/latest/userguide/datafiles.html). We are basically using it to denote that our code is `type-hinted`, which means we are using types in the code.
- For this to work we need to create a `py.typed` blank file in our something directory alongside `__init__.py`.
- `flake8` is the configuration for the `flake8` linter that we will be using. Here we are only saying that the maximum line length is 160.
- So our `flake8` configuration is stored in the `cfg` file and our `python` configuration is stored in our `pyproject.toml` file.

### 3.3 Adding python configurations to `pyproject.toml` <a name="part-3_3"></a>
- Our `pyproject.toml` file looks something like this:
        
    ```toml
    [build-system]
    requires = ["setuptools>=42.0", "wheel"]
    build-backend = "setuptools.build_meta"
    
    [tool.pytest.ini_options]
    addopts = "--cov=something"
    testpaths = [
        "tests",
    ]
    
    [tool.mypy]
    mypy_path = "src"
    check_untyped_defs = true
    disallow_any_generics = true
    ignore_missing_imports = true
    no_implicit_optional = true
    show_error_codes = true
    strict_equality = true
    warn_redundant_casts = true
    warn_return_any = true
    warn_unreachable = true
    warn_unused_configs = true
    no_implicit_reexport = true
    ```
- We already discussed about the `build-system`.
- `tool.pytest.ini_options` are `pytest` tool options during the execution of tests. In our case we have added the `--cov=something` option in `addopts` so that we see the test coverage. `testpaths` basically denotes all the directories containing tests.
- `tool.mypy` are `mypy` tool options. To learn about `mypy`: [https://realpython.com/lessons/type-checking-mypy/#:~:text=“Mypy is an optional static,background on the Mypy project](https://realpython.com/lessons/type-checking-mypy/#:~:text=%E2%80%9CMypy%20is%20an%20optional%20static,background%20on%20the%20Mypy%20project).
- So, now we install the `dev` dependencies with `pip`:
    
    ```bash
    (env)yourproject$ pip install -r requirements_dev.txt
    ```
    
- Finally we should be able to run our test with `pytest`, check linting with `flake8` and type check with `mypy`.
    
    ```bash
    $ pytest # this command will test our code with default test discovery mechanism.
    $ mypy # this command will run type checks on our code with mypy.
    $ flake8 # this command will check code linting.
    ```