how to make a Python test driven development environment manually


requirements


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

cd
tree
mkdir
touch
echo
cat
mv
python3 -m unittest
history

questions about making a Python Test Driven Development Environment

Questions to think about as I go through the chapter


how to setup the project


how to setup a project with uv



how to change directory to the project


  • I try to change directory to the person folder again

    cd person
    

    the terminal shows

    .../pumping_python/person
    

    uv made the directory for me.

  • I use tree to see what uv added to the folder

    tree -a -L 2
    

    the terminal shows

    .
    ├── .git
       ├── config
       ├── description
       ├── FETCH_HEAD
       ├── HEAD
       ├── hooks
       ├── info
       ├── objects
       └── refs
    ├── .gitignore
    ├── .python-version
    ├── README.md
    ├── main.py
    └── pyproject.toml
    

    it added a few files and folders.

    • 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

    • Here is what uv made for me

      • .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 (as long as it is has access to the repository)

      • .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

      • .python-version is a file that has the version of Python I am using, this helps if I do projects with different Python versions

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

      • main.py is a Python module for me to write the code for the project

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


how to see what is inside a file


  • I can use the cat program 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 = "person"
    version = "0.1.0"
    description = "Add your description here"
    readme = "README.md"
    requires-python = ">=3.XY"
    dependencies = []
    
  • I use cat to look at what is in .python-version

    cat .python-version
    

    the terminal is my friend, and shows

    3.XY
    

    where XY are numbers like 13 depending on what version of Python you have installed

  • I use cat to show what is inside README.md

    cat README.md
    

    the terminal goes back to the command line.

    .../pumping_python/person
    

    the file is empty


how to run a Python program


I use Python to run the person program

python3 src/person.py

the terminal is my friend, and shows

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

Python cannot find the file because it does not exist, and there is no folder named src, yet.


how to make a directory for the source code


  • I make a child folder in the person directory for the program because I want to keep the files separate from the other files in the project

    mkdir src
    

    the terminal goes back to the command line.

  • I use tree to see what changed in the person directory

    tree -a -L 1
    

    the terminal shows

    .
    ├── .git
    ├── .gitignore
    ├── main.py
    ├── pyproject.toml
    ├── .python-version
    ├── README.md
    └── src
    
  • I try to run the person program again

    python3 src/person.py
    

    the terminal is my friend, and shows the same error from before because there is no file named person.py in the src folder.


how to change the name of a file


  • I use the mv program to change the name of main.py to person.py and move it to the src folder

    mv main.py src/person.py
    

    the terminal goes back to the command line.

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

    tree -a -L 2
    

    the terminal shows

    .
    ├── .git
       ├── config
       ├── description
       ├── FETCH_HEAD
       ├── HEAD
       ├── hooks
       ├── info
       ├── objects
       └── refs
    ├── .gitignore
    ├── .python-version
    ├── README.md
    ├── pyproject.toml
    └── src
        └── person.py
    

    main.py is now person.py in the src folder.

  • I try to run the person program again

    python3 src/person.py
    

    the terminal shows

    Hello from person!
    

    Success! The uv Python package manager made the file with some Python code in it, I can successfully run it because the file is now in the folder.

  • I use cat to see what is in src/person.py

    cat src/person.py
    

    the terminal shows

    def main():
        print("Hello from person!")
    
    
    if __name__ == "__main__":
        main()
    

    magic!


test_failure


how to manually run tests



how to make a directory for the tests


  • I make a child folder to keep the tests separate from the other files

    mkdir tests
    

    the terminal goes back to the command line.

  • I use tree to see what my project looks like

    tree -a -L 1
    

    the terminal is my friend, and shows

    .
    ├── .git
    ├── .gitignore
    ├── pyproject.toml
    ├── .python-version
    ├── README.md
    ├── src
    └── 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/person.py
    

    the terminal goes back to the command line.

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

    tree -a -L 2
    

    the terminal is my friend, and shows

    .
    ├── .git
       ├── config
       ├── description
       ├── FETCH_HEAD
       ├── HEAD
       ├── hooks
       ├── info
       ├── objects
       └── refs
    ├── .gitignore
    ├── pyproject.toml
    ├── .python-version
    ├── README.md
    ├── src
       └── person.py
    └── tests
        └── person.py
    
  • I run the test again

    python3 -m unittest
    

    the terminal is my friend, and shows

    NO TESTS RAN
    

    because I have not set up the test correctly.


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 to 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 -a -L 2
    

    the terminal is my friend, and shows

    .
    ├── .git
       ├── config
       ├── description
       ├── FETCH_HEAD
       ├── HEAD
       ├── hooks
       ├── info
       ├── objects
       └── refs
    ├── .gitignore
    ├── pyproject.toml
    ├── .python-version
    ├── README.md
    ├── src
       └── person.py
    └── tests
        ├── __init__.py
        └── person.py
    
  • I try to run the test again

    python3 -m unittest
    

    the terminal does not feel like my friend, and shows

    NO TESTS RAN
    

    because unittest does not know that person.py in the tests folder is a test file. I did not start the name with test_. I have to change the name.

  • I close person.py

    Danger

    if you do not close person.py, 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 after you change its name.

  • I use the mv program to change the name of person.py in the tests folder to test_person.py

    mv tests/person.py tests/test_person.py
    

    the terminal goes back to the command line.

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

    tree -a -L 2
    

    the terminal is my friend, and shows

    .
    ├── .git
       ├── config
       ├── description
       ├── FETCH_HEAD
       ├── HEAD
       ├── hooks
       ├── info
       ├── objects
       └── refs
    ├── .gitignore
    ├── pyproject.toml
    ├── .python-version
    ├── README.md
    ├── src
       └── person.py
    └── tests
        ├── __init__.py
        ├── __pycache__
        └── test_person.py
    

    if you do not see __pycache__ in the tree do not worry,

    the important thing is that you renamed person.py to test_person.py for unittest to find the test.

  • I run the test again

    python3 -m unittest
    

    the terminal still shows NO TESTS RAN

  • I add assert before False is True in tests/test_person.py

    1# False is True
    2assert False is True
    
  • I try to run the test again

    python3 -m unittest
    

    the terminal is my friend, and shows AssertionError

    E
    ======================================================
    ERROR: tests.test_person (unittest.loader._FailedTest.tests.test_person)
    ------------------------------------------------------
    ImportError: Failed to import test module: tests.test_person
    Traceback (most recent call last):
      File "/usr/local/lib/python3.XY/unittest/loader.py",
        line 426, in _find_test_path
        module = self._get_module_from_name(name)
      File "/usr/local/lib/python3.XY/unittest/loader.py",
        line 367, in _get_module_from_name
        __import__(name)
        ~~~~~~~~~~^^^^^^
      File ".../pumping_python/person/tests/test_person.py",
        line 2, in <module>
        assert False is True
               ^^^^^^^^^^^^^
    AssertionError
    
    
    ------------------------------------------------------
    Ran 1 test in 0.001s
    
    FAILED (errors=1)
    

    Success! I have my first failure. I can use any name for the test file. 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 (errors=1): the number of failures or errors

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

    • AssertionError: the Error (Exception) that happened. Since I used an assert statement I get AssertionError because the statement after assert is False - False is NOT True

    • assert False is True: the line of code that caused AssertionError

    • the arrows (^^^^^^^^^^^^^): point to the part of the line above, that Python thinks caused the error

    • File ".../pumping_python/person/tests/test_person.py", line 2, in <module>: the line number of the code that caused the error and the location of the file where the error happened and again the question - what is a module?

    • __import__(name) shows another error that is triggered by the one from assert False is True

    • File "/usr/local/lib/python3.XY/unittest/loader.py", line 367, in _get_module_from_name shows the line, method and file where the error triggered by my assert False is True happened

    • module = self._get_module_from_name(name) a failure triggered by the failure triggered by my assert False is True

    • File "/usr/local/lib/python3.XY/unittest/loader.py", line 426, in _find_test_path shows the line, method and file where the error triggered by the one triggered by my assert False is True happened

    • 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. The last line is usually the most important one that points to what caused the failure, this is why I like to read it from the bottom up. In this case it is the only one I care about because it is the one I added to cause the failure.

    • ERROR: tests.test_person (unittest.loader._FailedTest.tests.test_person) is a header with information in dot notation about the failing test

      • I think of ``tests.test_person `` as an address

        • tests is the tests folder

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


GREEN: make it pass

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

  • I change True to False in test_person.py

    1# False is True
    2# assert False is True
    3assert False is False
    
  • I go back to the terminal to run the test

    python3 -m unittest
    

    the test passes! The terminal shows

    ------------------------------------------------------
    Ran 0 tests in 0.000s
    
    NO TESTS RAN
    

    this is confusing, since the only way I know the test passed, is because I saw it fail. Which is why the RED part of the cycle is important, it shows that the test works. There has to be a better way. For now 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 help me know them better, familiarity. I add AssertionError to test_person.py

    1# False is True
    2# assert False is True
    3assert False is False
    4
    5
    6# Exceptions seen
    7# AssertionError
    

    comments in Python are written with # at the beginning, they do not do anything, they are notes for me. Time to get some practice with Python modules.

  • I add the new files and folders to git for tracking

    git add .
    

    the terminal goes back to the command line.

  • I add a git commit message

    git commit --all --message \
    'setup project'
    

    the terminal shows a summary of the changes then goes back to the command line.


close the project

  • I close test_person.py

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

    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



what is next?

Would you like to find out what a module is?


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.