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
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
Questions to think about as I go through the chapter
how to setup the project
I choose
magicas 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
magicproject in thepumping_pythonfoldercd magicthe terminal is my friend, and shows
cd: no such file or directory: magicthere is no folder with the name
magicin this folder, time to makemagic.
how to setup a project with uv
I use the uv Python Package Manager to setup the project
uv init magicthe terminal shows
Initialized project `magic` at `.../pumping_python/magic`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
magicfolder againcd magicthe terminal shows
.../pumping_python/magicI 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 and shows
[project] name = "magic" 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/magicthe file is empty
how to run a Python program
I use Python to run the magic program
python3 src/magic.py
the terminal is my friend, and shows
python3: can't open file
'.../pumping_python/magic/src/magic.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
magicdirectory 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
magicdirectorytree -a -L 1the terminal shows
. ├── .git ├── .gitignore ├── main.py ├── pyproject.toml ├── .python-version ├── README.md └── srcI try to run the
magicprogram againpython3 src/magic.pythe terminal is my friend, and shows the same error from before because there is no file named
magic.pyin thesrcfolder.
how to change the name of a file
I use the mv program to change the name of
main.pytomagic.pyand move it to thesrcfoldermv main.py src/magic.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 └── magic.pymain.pyis nowmagic.pyin thesrcfolder.I try to run the
magicprogram againpython3 src/magic.pythe terminal shows
Hello from magic!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/magic.pycat src/magic.pythe terminal shows
def main(): print("Hello from magic!") 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 in a different place from the actual program
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/magic.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 │ └── magic.py └── tests └── magic.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
magic.pyfrom thetestsfolderI add the Python code below in
tests/magic.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+s ((Windows/Linux) or command+s (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 │ └── magic.py └── tests ├── __init__.py └── magic.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
magic.pyin thetestsfolder is a test file. I did not start the name withtest_. I have to change the name.I close
magic.pyDanger
if you do not close
magic.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
magic.pyin thetestsfolder totest_magic.pymv tests/magic.py tests/test_magic.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 │ └── magic.py └── tests ├── __init__.py ├── __pycache__ └── test_magic.pyif you do not see
__pycache__in the tree do not worry,the important thing is that you renamed
magic.pytotest_magic.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_magic.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_magic (unittest.loader._FailedTest.tests.test_magic) ------------------------------------------------------ ImportError: Failed to import test module: tests.test_magic 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/magic/tests/test_magic.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/magic/tests/test_magic.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.14/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.14/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_magic (unittest.loader._FailedTest.tests.test_magic)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/magic/tests/test_magic.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_magic.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_magic.py
1# False is True
2# assert False is True
3assert False is False
4
5
6# Exceptions seen
7# AssertionError
Note
comments in Python are written with # at the beginning, they do not do anything, they are notes for me
I ran
python3 -m unittestto see the test failI ran
python3 -m unittestevery time I made a change until the test passedI will run
python3 -m unittestagain 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 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 that is used for testing. It is not part of The Python Standard Library
I use uv to run pytest-watcher in the terminal
uv run pytest-watcher
the terminal is my friend, and shows
Using CPython 3.13.13
Creating virtual environment at: .venv
error: Failed to spawn: `pytest-watcher`
Caused by: No such file or directory (os error 2)
because pytest-watcher is not installed on the computer. I can install it with the uv Python Package Manager.
how to write text to a file
I want to make a file where I 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, if I type this in the terminal
echo "pytest"it shows
pytestI can also use echo to add text to a file. I use it to make the requirements file with pytest as what is inside it
echo "pytest" > requirements.txt>is an operator that is used to send output from a program to the given filepytest is a Python package like unittest, that is used for testing
requirements.txtis the name of the file where I am adding Python packages for the uv Python Package Manager to install. The namerequirements.txtis Python convention, I can use any name I want for the requirements file
I add pytest-watcher to the requirements file as well
echo "pytest-watcher" >> requirements.txt>>is an operator that is used to send output from a program to the given file, it adds to what is in the file without writing over itpytest-watcher is a Python program that automatically runs pytest when I change code in the project
I use tree to see what the project looks like now
tree -a -L 2the terminal shows
. ├── .git │ ├── HEAD │ ├── branches │ ├── config │ ├── description │ ├── hooks │ ├── info │ ├── objects │ └── refs ├── .gitignore ├── .python-version ├── README.md ├── pyproject.toml ├── requirements.txt ├── src │ └── magic.py └── tests ├── __init__.py ├── __pycache__ └── test_magic.pyrequirements.txtis now in themagicfolderI use cat to make sure
requirements.txthaspytestandpytest-watcherinside itcat requirements.txtthe terminal shows
pytest pytest-watcherlife is good!
how to install Python packages with uv
I use uv to install pytest-watcher from the requirements file
uv add --requirement requirements.txt--requirementis an option that can be given to theaddargument for Python packages in a given filerequirements.txtis the name of the given file. It helps to manage Python programs that are needed by the project. In this case I only have two programs. 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 is my friend, and shows setup and installation
Resolved 9 packages in GHIms ░░░░░░░░░░░░░░░░░░░░ [0/7] Installing wheels... ... Installed 7 packages in JKLms + iniconfig==A.B.C + packaging==DE.F + pluggy==G.H.I + pygments==J.K.L + pytest==M.N.O + pytest-watcher==P.Q.R + watchdog==S.T.UI run tree to see what changed in the project
tree -a -L 1the terminal shows
. ├── .git ├── .gitignore ├── .python-version ├── .venv ├── README.md ├── pyproject.toml ├── requirements.txt ├── src ├── tests └── uv.lockuv added 2 things
.venva folder for a Virtual Environmentuv.locka file that has the exact versions of the Python programs that were installed
I use cat to show what is now in
pyproject.tomlcat pyproject.tomlthe terminal is my friend, and shows
[project] name = "magic" version = "0.1.0" description = "Add your description here" readme = "README.md" requires-python = ">=3.XY" dependencies = [ "pytest>=M.N.O", "pytest-watcher>=P.Q.R", ]it added pytest and 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
When I want to work in a virtual environment, I make sure I am in the parent directory of it, for example,
magicin this case. I activate the virtual environment in the terminal to use itsource .venv/bin/activatethe terminal is my friend, and shows
(magic) .../magic(magic)on the far left of the command line in the terminal shows that I am in the virtual environment
how to deactivate a virtual environment
I leave the virtual environment by typing
deactivatein the terminaldeactivatethe terminal goes back to the command line,
(magic)is no longer on the left side.../pumping_python/magic
how to run tests automatically with uv and pytest-watcher
I try to run the test again, this time with uv
uv run pytest-watcher . --nowthe terminal is my friend, and 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 ____ -- 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, then click on
tests/test_magic.pyto place the cursor of the Integrated Development Environment (IDE), then I change False to True on line 71import unittest 2 3 4class TestMagic(unittest.TestCase): 5 6 def test_failure(self): 7 self.assertFalse(True)the terminal is my friend, and 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, then click on
tests/test_magic.py:7to place the cursor of the Integrated Development Environment (IDE), then I change True back to False intest_magic.py7 self.assertFalse(False)the test passes. I can write the rest of the code for the project and get results back quickly because the tests run when I change the code
how to stop the automated tests
I go to the terminal and use q on the keyboard to stop the tests, the terminal goes back to the command line.
.../pumping_python/magic
close the project
I close
test_magic.pyI click in the terminal and change directory to the parent of
magiccd ....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
I made the test run automatically with pytest-watcher
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
uv init magic cd magic 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 2 python3 src/magic.py mv main.py src/magic.py tree -a -L 2 python3 src/magic.py cat src/magic.py python3 -m unittest mkdir tests tree -a -L 2 touch tests/magic.py tree python3 -m unittest touch tests/__init__.py tree -a -L 2 python3 -m unittest mv tests/magic.py tests/test_magic.py tree -a -L 2 python3 -m unittest python3 -m unittest uv run pytest-watcher echo "pytest" echo "pytest" > requirements.txt echo "pytest-watcher" >> requirements.txt tree -a -L 2 cat requirements.txt uv add --requirement requirements.txt tree -a -L 1 cat pyproject.toml source .venv/bin/activate deactivate uv run pytest-watcher . --now cd .. historythe history program shows all the commands I typed in the terminal so far, and I use them to write the program that will automatically make a Python Test Driven Development environment for me
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.py echo "pytest" > requirements.txt echo "pytest-watcher" >> requirements.txt uv add --requirement requirements.txt uv run pytest-watcher . --nowwhere
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
make the test pass
How many questions can you answer after going through this chapter?
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.