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 │ ├── HEAD │ ├── branches │ ├── config │ ├── description │ ├── 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.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.XYI 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 │ ├── HEAD │ ├── branches │ ├── config │ ├── description │ ├── hooks │ ├── info │ ├── objects │ └── refs ├── .gitignore ├── .python-version ├── README.md ├── main.py ├── pyproject.toml └── 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 │ ├── HEAD │ ├── branches │ ├── config │ ├── description │ ├── hooks │ ├── info │ ├── objects │ └── refs ├── .gitignore ├── .python-version ├── README.md ├── pyproject.toml └── src └── magic.pymain.pyis nowmagic.pyin thesrcfolderI 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)a Python module is any file that ends with
.py, this means somewhere on the computer there is a file namedunittest.py, click here to see the source code for unittest
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 2the terminal is my friend, and shows
. ├── .git │ ├── HEAD │ ├── branches │ ├── config │ ├── description │ ├── hooks │ ├── info │ ├── objects │ └── refs ├── .gitignore ├── .python-version ├── README.md ├── pyproject.toml ├── 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
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 │ ├── HEAD │ ├── branches │ ├── config │ ├── description │ ├── hooks │ ├── info │ ├── objects │ └── refs ├── .gitignore ├── .python-version ├── README.md ├── pyproject.toml ├── 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, yet
RED: make it fail
I use the Integrated Development Environment (IDE) to open
tests/magic.pyin the editorTip
I can open a file from the terminal in the Integrated Development Environment (IDE) by typing the name of the program and the name of the file, for example with Visual Studio Code when I type this in the terminal
code tests/magic.pymagic.pyfrom thetestsfolder opens in the editor.I can also open a file from the terminal with ctrl (Windows/Linux) or command (MacOS) on the keyboard and a click with the mouse on the name of the file
I add the Python code below in
tests/magic.pyin the editorNote
the line numbers below are a guide, no need to copy them
1import unittest 2 3 4class TestMagic(unittest.TestCase): 5 6 def test_failure(self): 7 self.assertFalse(True)Here is an explanation of the code I typed in the file
import unittestimports the unittest module from The Python Standard Library, this is what I am using for testing. It is like a toolbox that contains the tools I will use to test the codeclass TestMagicclassis the Python keyword for making classes - a group of attributes (values) and methods (functions) that belong together, see the classes chapter for moreTestMagicis the name I gave this class and will hold the testTip
I can use any name for the test class, it MUST start with
Testor unittest will NOT run the tests in itunittest.TestCase is a class from the unittest module that has methods for , this is the tool that contains the methods I will use to test code in this book
class TestMagic(unittest.TestCase)definesTestMagicas a “child” of unittest.TestCase which means I can use the methods and attributes of the unittest.TestCase class, I do not have to make them because someone else already did
def test_failuredef is the Python keyword for making methods (functions), see functions for more
test_failureis the name of the method I used for this first testTip
I can use any name for the test method, it MUST start with
test_or unittest will NOT run the tests in itself.lets me use attributes and methods of theTestMagicclass which is a “child” of the unittest.TestCase class, instead of usingTestMagic().orunittest.TestCase().when I want to use something from the unittest.TestCase classTip
the name
selfis Python convention. I can use any name though it is easier to stick with convention for this oneself.assertFalse(True)is an assertion (a way to make sure something is True or False)assertFalse is a method in the unittest.TestCase class that checks if its input is False
True is given as the input
I expect this line to fail because True is NOT False. If it does not fail, then Python and I have a problem
I 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)In Visual Studio Code it is under
Filein the menuI 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 │ ├── HEAD │ ├── branches │ ├── config │ ├── description │ ├── hooks │ ├── info │ ├── objects │ └── refs ├── .gitignore ├── .python-version ├── README.md ├── pyproject.toml ├── src │ └── magic.py └── tests ├── __init__.py └── magic.pyI try to run the tests 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 nameI close
magic.pyin the editor of the Integrated Development Environment (IDE)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 │ ├── HEAD │ ├── branches │ ├── config │ ├── description │ ├── hooks │ ├── info │ ├── objects │ └── refs ├── .gitignore ├── .python-version ├── README.md ├── pyproject.toml ├── 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 testI run the tests again
python3 -m unittestthe terminal is my friend, and 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. It must start with
test_or unittest will NOT run the tests in the fileThis 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 failuresRan 1 test in A.XYZs: the number of tests it ran and how long they tookAssertionError: True is not false: the Error (Exception) that happened and its message, in this case AssertionError because True is NOT Falseself.assertFalse(True): the line of code that caused AssertionError~~~~~~~~~~~~~~~~^^^^^^: points to the part of the line above, that Python thinks caused the errorFile ".../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 isTraceback (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 upFAIL: test_failure (tests.test_magic.TestMagic.test_failure)is a header with information in dot notation about the failing test methodtests.test_magic.TestMagic.test_failureis the location of the failing test
Fshows 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 7in 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
1import unittest
2
3
4class TestMagic(unittest.TestCase):
5
6 def test_failure(self):
7 self.assertFalse(False)
I run the test again in the terminal
python3 -m unittest
the test passes! the terminal is my friend, and 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 help me know them better, it helps when I run into them later, because I will be more familiar with them. I add AssertionError to 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
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 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 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 the tests automatically with uv and pytest-watcher
I try to run the tests 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 in the editor 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 in the editor 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.pyin the editor of the Integrated Development Environment (IDE)I 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
the 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?
You have seen me make a Test Driven Development environment for a project called magic on any Linux, MacOS and Windows_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