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
I choose
personas the name of this projectI click
Terminalin the menu bar at the top of the Integrated Development Environment (IDE), then clickNew Terminalto open a terminalI change directory to where I will put all the projects from this book. I type cd in the terminal
Note
skip this step if you are already in the
pumping_pythondirectory or made it earliercd pumping_pythonif the terminal shows
cd: no such file or directory: pumping_pythonthe folder (directory) does NOT exist. I need to make it. I use the mkdir program to make the
pumping_pythonfolder (directory)mkdir pumping_pythonthe terminal goes back to the command line.
I try changing directory again
cd pumping_pythonthe terminal shows I am in the
pumping_pythonfolder (directory).../pumping_python
I type tree in the terminal to see what files and folders are in the
pumping_pythondirectorytreeI change directory to the
personproject in thepumping_pythonfoldercd personthe terminal is my friend, and shows
cd: no such file or directory: personthere is no folder with the name
personin this folder, time to makeperson.
how to setup a project with uv
I use the uv Python Package Manager to setup the project
uv init personthe terminal shows
Initialized project `person` at `.../pumping_python/person`uv is a program that makes files and folders needed for a project. It also handles Python and Python Packages.
how to change directory to the project
I try to change directory to the
personfolder againcd personthe terminal shows
.../pumping_python/personI use tree to see what uv added to the folder
tree -a -L 2the terminal shows
. ├── .git │ ├── config │ ├── description │ ├── FETCH_HEAD │ ├── HEAD │ ├── hooks │ ├── info │ ├── objects │ └── refs ├── .gitignore ├── .python-version ├── README.md ├── main.py └── pyproject.tomlit added a few files and folders.
the
-Loption tells tree how deep to go when showing the folders and files, I use2to make it show only the first level of contents of the child foldersHere is what uv made for me
.gitthis 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).gitignoreis 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-versionis a file that has the version of Python I am using, this helps if I do projects with different Python versionsREADME.mdis a file that is used to describe the projectmain.pyis a Python module for me to write the code for the projectpyproject.tomlis 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
.gitignorecat .gitignorethe terminal shows
# Python-generated files __pycache__/ *.py[oc] build/ dist/ wheels/ *.egg-info # Virtual environments .venvI use cat to see what is inside
pyproject.tomlcat pyproject.tomlthe 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-versioncat .python-versionthe terminal is my friend, and shows
3.XYwhere
XYare numbers like13depending on what version of Python you have installedI use cat to show what is inside
README.mdcat README.mdthe terminal goes back to the command line.
.../pumping_python/personthe 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
persondirectory for the program because I want to keep the files separate from the other files in the projectmkdir srcthe terminal goes back to the command line.
I use tree to see what changed in the
persondirectorytree -a -L 1the terminal shows
. ├── .git ├── .gitignore ├── main.py ├── pyproject.toml ├── .python-version ├── README.md └── srcI try to run the
personprogram againpython3 src/person.pythe terminal is my friend, and shows the same error from before because there is no file named
person.pyin thesrcfolder.
how to change the name of a file
I use the mv program to change the name of
main.pytoperson.pyand move it to thesrcfoldermv main.py src/person.pythe terminal goes back to the command line.
I use tree to see what folders and files I now have
tree -a -L 2the terminal shows
. ├── .git │ ├── config │ ├── description │ ├── FETCH_HEAD │ ├── HEAD │ ├── hooks │ ├── info │ ├── objects │ └── refs ├── .gitignore ├── .python-version ├── README.md ├── pyproject.toml └── src └── person.pymain.pyis nowperson.pyin thesrcfolder.I try to run the
personprogram againpython3 src/person.pythe 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.pycat src/person.pythe terminal shows
def main(): print("Hello from person!") if __name__ == "__main__": main()magic!
test_failure
how to manually run tests
I use the unittest module from The Python Standard Library that comes with Python to run tests. I type this in the terminal
python3 -m unittestthe terminal shows
------------------------------------------------------ Ran 0 tests in 0.000s NO TESTS RANbecause I do not have any tests, yet.
python3is the Python program-mis an option/switch passed when calling Python to run the module ( unittest in this case)which leads to the question of what is a module? Any file that ends in
.pyis a Python module.
how to make a directory for the tests
I make a child folder to keep the tests separate from the other files
mkdir teststhe terminal goes back to the command line.
I use tree to see what my project looks like
tree -a -L 1the 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
testsdirectory for the actual testtouch tests/person.pythe terminal goes back to the command line.
I use tree to see what the project looks like now
tree -a -L 2the 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.pyI run the test again
python3 -m unittestthe terminal is my friend, and shows
NO TESTS RANbecause I have not set up the test correctly.
RED: make it fail
I open
person.pyfrom thetestsfolderI add the Python code below in
tests/person.pyNote
the line numbers below are a guide, no need to copy them
1False is TrueI turn on the
Auto Savefeature in the Integrated Development Environment (IDE) to automatically save files when I make a change so that I do not repeat myself. I do not want to use ctrl/command+s (Windows & Linux/MacOS) on the keyboard every time I make a change, I want the computer to do that for meAttention
Turn on the
Auto Savefeature in the Integrated Development Environment (IDE)I try the command again to run the tests in the terminal
python3 -m unittestthe terminal is my friend, and shows
NO TESTS RANbecause the
testsfolder is NOT a Python package and unittest cannot find my test. I need to add a file named__init__.pyto thetestsfolder, to make it a Python package for unittest to find the test.
how to make the tests a Python package
I use touch to add an empty file with the name
__init__.pyto thetestsfolderDanger
use 2 underscores (__) before and after
initfor__init__.pynot_init_.pytouch tests/__init__.pythe terminal goes back to the command line.
I run the tree command to see what changed
tree -a -L 2the 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.pyI try to run the test again
python3 -m unittestthe terminal does not feel like my friend, and shows
NO TESTS RANbecause unittest does not know that
person.pyin thetestsfolder is a test file. I did not start the name withtest_. I have to change the name.I close
person.pyDanger
if you do not close
person.py, there will be 3 files in thetestsfolder after the next step (instead of 2), because theAuto Savefeature (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.pyin thetestsfolder totest_person.pymv tests/person.py tests/test_person.pythe terminal goes back to the command line.
I use tree with the
-Loption to see what I have so fartree -a -L 2the 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.pyif you do not see
__pycache__in the tree do not worry,the important thing is that you renamed
person.pytotest_person.pyfor unittest to find the test.I run the test again
python3 -m unittestthe terminal still shows
NO TESTS RANI add
assertbeforeFalse is Trueintests/test_person.py1# False is True 2assert False is TrueI try to run the test again
python3 -m unittestthe 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 errorsRan 1 test in A.XYZs: the number of tests it ran and how long they tookAssertionError: the Error (Exception) that happened. Since I used an assert statement I get AssertionError because the statement afterassertis False - False is NOT Trueassert False is True: the line of code that caused AssertionErrorthe arrows (
^^^^^^^^^^^^^): point to the part of the line above, that Python thinks caused the errorFile ".../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 fromassert False is TrueFile "/usr/local/lib/python3.XY/unittest/loader.py", line 367, in _get_module_from_nameshows the line, method and file where the error triggered by myassert False is Truehappenedmodule = self._get_module_from_name(name)a failure triggered by the failure triggered by myassert False is TrueFile "/usr/local/lib/python3.XY/unittest/loader.py", line 426, in _find_test_pathshows the line, method and file where the error triggered by the one triggered by myassert False is TruehappenedTraceback (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
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 2in 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.py1# False is True 2# assert False is True 3assert False is FalseI go back to the terminal to run the test
python3 -m unittestthe test passes! The terminal shows
------------------------------------------------------ Ran 0 tests in 0.000s NO TESTS RANthis 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.py1# False is True 2# assert False is True 3assert False is False 4 5 6# Exceptions seen 7# AssertionErrorcomments 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.pyI click in the terminal and change directory to the parent of
personcd ....is for the parent of any directory I am in. the terminal shows.../pumping_pythonI am back in the
pumping_pythonfolder
review
I gave the computer some commands to make a Python Test Driven Development environment
I made some folders
I made some files
I made a failing test
I made the failing test pass
how to view all the commands typed in a terminal
I type history in the terminal to see all the commands I have typed so far
historythe terminal shows
cd person uv init person cd person tree tree -a -L 2 cat .gitignore cat pyproject.toml cat python-version cat .python-version cat README.md mkdir src tree tree -a -L 1 python3 src/person.py mv main.py src/person.py tree -a -L 2 python3 src/person.py cat src/person.py python3 -m unittest mkdir tests tree -a -L 1 touch tests/person.py tree -a -L 2 python3 -m unittest touch tests/__init__.py tree -a -L 2 python3 -m unittest mv tests/person.py tests/test_person.py tree -a -L 2 python3 -m unittest python3 -m unittest git commit --all --message 'setup project' cd ..the history program shows all the commands I typed in the terminal so far.
these are the commands I used to make a Python Test Driven Development environment
uv init NAME_OF_THE_PROJECT cd NAME_OF_THE_PROJECT mkdir src mv main.py src/NAME_OF_THE_PROJECT.py mkdir tests touch tests/__init__.py touch tests/test_NAME_OF_THE_PROJECT.pywhere
NAME_OF_THE_PROJECTis the name I give the projectthese are the steps I took to make a Python Test Driven Development environment
give the project a name
make a Python file to hold the source code in the ‘src’ folder
what is 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.