how to make a python test driven development environment


preview

This is one way to make a Python Test Driven Development project.

I walk through making the folders (directories) and files for the environment, including setting up the first test

By the end of the chapter you will know these commands better

mkdir
cd
touch
echo
cat
mv
rm
uv run pytest-watcher
source .venv/bin/activate
deactivate
history

questions about making a Python Test Driven Development Environment

Here are questions you can answer after going through this chapter


requirements


how to make a Python Test Driven Development environment manually

I choose magic as the name of this project


how to make a directory for the project



how to change directory to the project


I try to go to magic again

cd magic

the terminal shows I am in the magic folder I just made in the pumping_python folder

.../pumping_python/magic

how to run a Python program


I use Python to run the magic program

python3 src/magic.py

the terminal shows

python3: can't open file '.../pumping_python/magic/src/magic.py': [Errno 2] No such file or directory

the computer cannot find the program because it there is no file with that name in the src folder, yet.


how to make a directory for the source code



how to make an empty file


  • I use touch to make an empty file in the src folder

    touch src/magic.py
    

    the terminal goes back to the command line

  • I use tree to see what folders and files I now have

    tree
    

    the terminal shows

    .
    └── src
        └── magic.py
    

    touch is a program that makes an empty file with the name it is given. I can give it the directory I want to put the file in as part of the name, in this case touch src/magic.py makes a file named magic.py in the src folder

  • I try to run the magic program again

    python3 src/magic.py
    

    the terminal goes back to the command line. Success! Even though magic.py does not do anything because there is no code in it, I can successfully run it because the file is now in the folder.


test_failure


how to manually run tests



how to make a directory for the tests


  • I make a child folder to keep the tests in a different place than the actual program

    mkdir tests
    

    the terminal goes back to the command line

  • I use tree to see what my project looks like

    tree
    

    the terminal shows

    .
    ├── src
       └── magic.py
    └── tests
    

how to make a Python file for the tests in the ‘tests’ directory


  • I use touch to add an empty file to the tests directory for the actual test

    touch tests/magic.py
    

    the terminal goes back to the command line

  • I use tree to see what the project looks like now

    tree
    

    the terminal shows

    .
    ├── src
       └── magic.py
    └── tests
        └── magic.py
    
  • I run the test again

    python3 -m unittest
    

    the terminal shows

    NO TESTS RAN
    

RED: make it fail


how to make the tests a Python package


  • I use touch to add an empty file with the name __init__.py in the tests folder

    Danger

    use 2 underscores (__) before and after init for __init__.py not _init_.py

    touch tests/__init__.py
    

    the terminal goes back to the command line

  • I run the tree command to see what changed

    tree
    

    the terminal shows

    .
    ├── src
       └── magic.py
    └── tests
        ├── __init__.py
        └── magic.py
    
  • I try to run the tests again

    python3 -m unittest
    

    the terminal shows

    NO TESTS RAN
    

    I need to tell Python that magic.py in the tests folder is a test file

  • I close magic.py in the editor of the Integrated Development Environment (IDE)

    Caution

    if you do not close magic.py in the editor, there will be 3 files in the tests folder after the next step (instead of 2), because the Auto Save feature (enabled earlier) will save the original file if it is still open in the editor after you change its name


how to change the name of a file


  • I use the mv program to change the name of magic.py in the tests folder to test_magic.py

    mv tests/magic.py tests/test_magic.py
    

    the terminal goes back to the command line

  • I use tree with the -L option to see what I have so far

    tree -L 2
    

    the terminal shows

    .
    ├── src
       └── magic.py
    └── tests
        ├── __init__.py
        ├── __pycache__
        └── test_magic.py
    

    Note

    if you do not see __pycache__ in the tree do not worry, the important thing is that you renamed magic.py to test_magic.py for unittest to find the test

    the -L option tells tree how deep to go when showing the folders and files, I use 2 to make it show only the first level of contents of the child folders

  • I run the tests again

    python3 -m unittest
    

    the terminal shows AssertionError

    F
    =============================================================
    FAIL: test_failure (tests.test_magic.TestMagic.test_failure)
    -------------------------------------------------------------
    Traceback (most recent call last):
      File "...pumping_python/magic/tests/test_magic.py", line 7, in test_failure
        self.assertFalse(True)
        ~~~~~~~~~~~~~~~~^^^^^^
    AssertionError: True is not false
    
    -------------------------------------------------------------
    Ran 1 test in A.XYZs
    
    FAILED (failures=1)
    

    Tip

    I can use any name for the test file but it must start with test_ or unittest will NOT run the tests in the file

    This is the RED part of the Test Driven Development cycle. The message in the terminal is about the failure, I like to read these from the bottom up, here is an explanation of each line, starting from the last line on the screen

    • FAILED (failures=1) the number of failures

    • Ran 1 test in A.XYZs the number of tests it ran and how long they took

    • AssertionError: True is not false the Error (Exception) that happened and its message, in this case AssertionError because True is not False

    • self.assertFalse(True) the line of code that caused AssertionError

    • ~~~~~~~~~~~~~~~~^^^^^^ points to the part of the line above, that Python thinks caused the error

    • File ".../magic/tests/test_magic.py", line 7, in test_failure the line number of the code that caused the error and the location of the file where it is

    • Traceback (most recent call last): all the information shown after this line that is indented to the right shows the calls that led to the failure, this is why I like to read it from the bottom up

    • FAIL: test_failure (tests.test_magic.TestMagic.test_failure) is a header with information in dot notation about the failing test method

      • tests.test_magic.TestMagic.test_failure is the location of the failing test

        • tests is the tests folder

        • test_magic is the test_magic.py file in the tests folder

        • TestMagic is the class defined on line 4 in test_magic.py in the tests folder

        • test_failure is the method (function) defined in the TestMagic class, on line 6 in test_magic.py in the tests folder

    • F shows a failure

  • I hold ctrl (Windows/Linux) or option/command (MacOS) on the keyboard and use the mouse to click on File ".../pumping_python/magic/tests/test_magic.py", line 7 in the terminal, and the Integrated Development Environment (IDE) opens the file in the editor with the cursor at the line where the failure happened


GREEN: make it pass

I change True to False on line 7 of test_magic.py in the editor

7        self.assertFalse(False)

I run the test again in the terminal

python3 -m unittest

the test passes! The terminal shows

.
------------------------------------------------------
Ran 1 test in A.XYZs

OK

cue CELEBRATION MUSIC AND DANCE! I am GREEN!!


REFACTOR: make it better

I keep a list of Errors/Exceptions that show up in the terminal as I go through this book to know them better, it helps when I run into them later. I add AssertionError in test_magic.py in the editor

 1import unittest
 2
 3
 4class TestMagic(unittest.TestCase):
 5
 6    def test_failure(self):
 7        self.assertFalse(True)
 8
 9
10# Exceptions seen
11# AssertionError
  • I ran python3 -m unittest a few times to see the test fail

  • I ran python3 -m unittest again to see the test pass

  • I will run python3 -m unittest again when I add any code, to make sure tests that passed before do not fail and that the new code I add does what I want.

This means I have to run python3 -m unittest for each part of the Test Driven Development cycle or any time there is a code change.

I do not want to type python3 -m unittest again, I want the computer to do it for me.


how to run the tests automatically


I can use pytest-watcher to run tests automatically. It is a Python program that automatically runs pytest any time a Python file changes in the folder it is looking at, this means it will run the tests for me every time I make a change.

pytest is a Python package like unittest, it is not part of the Python standard library

I type pytest-watcher in the terminal

pytest-watcher

the terminal shows

command not found: pytest-watcher

I need to install pytest-watcher for the computer to use it. Next, I use the uv Python package manager to install it


how to write text to a file


I want to make a file where I can list all the Python packages that my project needs as a way to document it and have uv install the programs listed in the file

  • I can write text to a file with the echo program, it shows whatever it is given as an argument, on the screen (standard output (stdout)) for example

    echo "pytest-watcher"
    

    the terminal shows

    pytest-watcher
    
  • I can also use echo to add text to a file, I use it to make the requirements file with pytest-watcher as what is inside it

    echo "pytest-watcher" > requirements.txt
    
  • I run tree to see what the project looks like now

    tree -a -L 2
    

    the terminal shows

    .
    ├── requirements.txt
    ├── src
       └── magic.py
    └── tests
        ├── __init__.py
        ├── __pycache__
        └── test_magic.py
    

    requirements.txt is now in the magic folder


how to see what is inside a file


I can use the cat program to see what is inside a file. I use it to make sure my requirements.txt has pytest-watcher inside it

cat requirements.txt

the terminal shows

pytest-watcher

life is good!


how to setup a project with uv


  • I use uv to setup the project

    uv init
    

    the terminal shows

    Initialized project `magic`
    

    and the command line has (main) on the right side of the project name

    .../pumping_python/magic (main)
    
  • I run tree to see what uv did to the folder

    tree -a -L 1
    
    .
    ├── .git
    ├── .gitignore
    ├── main.py
    ├── pyproject.toml
    ├── .python-version
    ├── README.md
    ├── requirements.txt
    ├── src
    └── tests
    

    it added a few files and folders

  • I remove main.py since I already have magic.py in the src folder for the main program

    rm main.py
    

    the terminal goes back to the command line

    .../pumping_python/magic (main)
    
  • Here is what uv added to the project

    • .git this folder makes the project a git repository, it makes it easy to keep track of changes I make, and if I publish the repository I can work on the project from any computer anywhere

    • .gitignore is a file that tells git what files in the project to not keep track of, this is useful for things that I do not want or need to share

    • pyproject.toml is a file that is used to configure Python projects for packaging see pyproject.toml for more

    • .python-version is a file that has the version of Python I am using

    • README.md is a file that is used to describe the project

  • I use cat to look at what is inside .gitignore

    cat .gitignore
    

    the terminal shows

    # Python-generated files
    __pycache__/
    *.py[oc]
    build/
    dist/
    wheels/
    *.egg-info
    
    # Virtual environments
    .venv
    
  • I use cat to see what is inside pyproject.toml

    cat pyproject.toml
    

    the terminal shows

    [project]
    name = "magic"
    version = "0.1.0"
    description = "Add your description here"
    readme = "README.md"
    requires-python = ">=3.XY"
    dependencies = []
    
  • I look at what is in .python-version

    cat .python-version
    

    the terminal shows

    3.XY
    
  • I show what is in README.md

    cat README.md
    

    the terminal goes back to the command line

    .../pumping_python/magic (main)
    

    the file is empty


how to install Python packages with uv


  • I use uv to install pytest-watcher from the requirements file

    uv add --requirement requirements.txt
    
    • --requirement is an option that can be given to the add argument for Python packages in a given file

    • requirements.txt is the name of the given file. It helps to manage Python programs that are needed by the project. In this case I only have one program. A project can have a big number of programs it needs and using one file with one command is easier than one command for each program

    the terminal shows setup and installation

    Using CPython 3.XY.Z interpreter at: /usr/local/bin/python3.XY
    Creating virtual environment at: .venv
    Resolved 3 packages in GHIms
    ░░░░░░░░░░░░░░░░░░░░ [0/2] Installing wheels...
    ...
    Installed 2 packages in JKLms
     + pytest-watcher==A.B.C
     + watchdog==D.E.F
    
  • I run tree to see what changed in the project

    tree -a -L 1
    

    the terminal shows

    .
    ├── .git
    ├── .gitignore
    ├── pyproject.toml
    ├── .python-version
    ├── README.md
    ├── requirements.txt
    ├── src
    ├── tests
    ├── uv.lock
    └── .venv
    

    uv added 2 things

  • I use cat to show what is now in pyproject.toml

    cat pyproject.toml
    

    the terminal shows

    [project]
    name = "magic"
    version = "0.1.0"
    description = "Add your description here"
    readme = "README.md"
    requires-python = ">=3.XY"
    dependencies = [
        "pytest-watcher>=A.B.C",
    ]
    

    it added pytest-watcher to the dependencies of the project


what is a virtual environment?


A virtual environment is a separate folder where I can install Python packages that my project needs. This helps me keep things that belong to the project in one place, separate from other things on the computer.

It means I can have a separate virtual environment for every project with only the programs that the project needs. I do not have to keep every program I have ever used for projects that do not need them.


how to activate a virtual environment



how to deactivate a virtual environment


  • I leave the virtual environment by typing deactivate in the terminal

    deactivate
    

    the terminal goes back to the command line, (magic) is no longer on the left side

    .../pumping_python/magic (main)
    

how to run the tests automatically with uv and pytest-watcher


  • I try to run the tests again

    uv run pytest-watcher . --now
    

    the terminal shows

    command not found: pytest-watcher
    

    same error

  • I use uv to run pytest-watcher

    uv run pytest-watcher
    

    the terminal shows

    usage: pytest_watcher [-h] [--now] [--clear] [--notify-on-failure] [--delay DELAY]
                          [--runner RUNNER] [--patterns PATTERNS]
                          [--ignore-patterns IGNORE_PATTERNS] [--version]
                          path
    pytest_watcher: error: the following arguments are required: path
    

    oh boy! I just want my tests to run already. I see a --now option

  • I try to run the tests again with the --now option

    uv run pytest-watcher --now
    

    --now is an option that tells pytest-watcher to run the tests now without asking me for input

    the terminal shows

    usage: pytest_watcher [-h] [--now] [--clear] [--notify-on-failure] [--delay DELAY]
                          [--runner RUNNER] [--patterns PATTERNS]
                          [--ignore-patterns IGNORE_PATTERNS] [--version]
                          path
    pytest_watcher: error: the following arguments are required: path
    

    I have to tell pytest-watcher what folder has the tests I want it to run

  • I try to run the tests again

    uv run pytest-watcher --now  .
    

    . is the current working directory

    the terminal shows results without going back to the command line

    pytest-watcher version X.Y.Z
    Runner command: pytest
    Waiting for file changes in .../pumping_python/magic
    ======================= test session starts ========================
    platform linux -- Python 3.A.B, pytest-C.D.E, pluggy-F.G.H
    rootdir: .../pumping_python/magic
    configfile: pyproject.toml
    collected 1 item
    
    tests/test_magic.py .                                         [100%]
    
    ======================== 1 passed in X.YZs =========================
    [pytest-watcher]
    Current runner args: []
    Press w to show menu
    

how to open the test file in the editor from the terminal


  • I hold ctrl (Windows/Linux) or option/command (MacOS) on the keyboard and click on tests/test_magic.py to place the cursor in the editor of the Integrated Development Environment (IDE), then I change False to True on line 7

    7      self.assertFalse(True)
    

    the terminal shows AssertionError

    ============================ FAILURES ==============================
    _____________________ TestMagic.test_failure _______________________
    
    self = <tests.test_magic.TestMagic testMethod=test_failure>
    
        def test_failure(self):
    >       self.assertFalse(True)
    E       AssertionError: True is not false
    
    tests/test_magic.py:7: AssertionError
    =================== short test summary info ========================
    FAILED tests/test_magic.py::TestMagic::test_failure - AssertionError: True is not false
    ======================= 1 failed in X.YZs ==========================
    
  • I hold ctrl (Windows/Linux) or option/command (MacOS) on the keyboard and click on tests/test_magic.py:7 to place the cursor in the editor of the Integrated Development Environment (IDE), then I change True back to False in test_magic.py

    7      self.assertFalse(False)
    

    the test passes. I can write the rest of the code for the project and get results back quickly since the tests run when I change the code


how to stop the automated tests


I go to the the terminal and use q on the keyboard to stop the tests, the terminal goes back to the command line

.../pumping_python/magic (main)

close the project

  • I close test_magic.py in the editor of the Integrated Development Environment (IDE)

  • I click in the terminal and change directory to the parent of magic

    cd ..
    

    .. is for the parent of any directory I am in. The terminal shows

    .../pumping_python
    

    I am back in the pumping_python folder


review


how to view all the commands typed in a terminal



How many questions can you answer after going through this chapter?


what is next?

You have seen me make a Test Driven Development environment for a project called magic on any Linux, Windows with Windows Subsystem for Linux or MacOS computers. Would you like to test AssertionError next?


rate pumping python

If this has been a 7 star experience for you, please CLICK HERE to leave a 5 star review of pumping python. It helps other people get into the book too