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
I 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_pythondirectorytree
how to make a directory for the project
I change directory to the
magicproject in thepumping_pythonfoldercd magicthe terminal shows
cd: no such file or directory: magicthere is no folder with the name
magicin this folder, time to makemagicI make the
magicdirectorymkdir magicthis makes a folder (directory) for the project where its files will stay
I use tree again
treethe terminal shows
magic. └── magic
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
I make a child folder in the
magicdirectory for the programmkdir srcthe terminal goes back to the command line
I use tree to see what changed in the
magicdirectorytreethe terminal shows
. └── srcI try to run the
magicprogram againpython3 src/magic.pythe terminal shows the same error from before. I have to make the file
how to make an empty file
I use touch to make an empty file in the
srcfoldertouch src/magic.pythe terminal goes back to the command line
I use tree to see what folders and files I now have
treethe terminal shows
. └── src └── magic.pytouch 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.pymakes a file namedmagic.pyin thesrcfolderI try to run the
magicprogram againpython3 src/magic.pythe terminal goes back to the command line. Success! Even though
magic.pydoes 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
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 unittestpython3is the Python program-mis an option/switch passed when calling Python to run the module, unittest in this casea 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
the terminal shows
---------------------------------------------------------------------- Ran 0 tests in 0.000s NO TESTS RANbecause I do not have any tests yet.
how to make a directory for the 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
treethe terminal shows
. ├── src │ └── magic.py └── tests └── magic.pyI run the test again
python3 -m unittestthe terminal shows
NO TESTS RAN
RED: make it fail
I open
tests/magic.pywith the Integrated Development Environment (IDE) to open it in the editorTip
I can use the terminal to open a file 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 testingclass 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 testing
class TestMagic(unittest.TestCase)definesTestMagicas a “child” of unittest.TestCase which means I can use the methods and attributes of the unittest.TestCase class
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 but it is easier to stick with convention for this conceptself.assertFalse(True)is an assertionassertFalse 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)I try the command again to run the tests in the terminal
python3 -m unittestthe terminal shows
NO TESTS RANI need to tell Python that the
testsfolder is a Python package, so it can find the tests
how to make the tests a Python package
I use touch to add an empty file with the name
__init__.pyin 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
treethe terminal shows
. ├── src │ └── magic.py └── tests ├── __init__.py └── magic.pyI try to run the tests again
python3 -m unittestthe terminal shows
NO TESTS RANI need to tell Python that
magic.pyin thetestsfolder is a test fileI close
magic.pyin the editor of the Integrated Development Environment (IDE)
how to change the name of a file
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 -L 2the terminal shows
. ├── src │ └── magic.py └── tests ├── __init__.py ├── __pycache__ └── test_magic.pyNote
if you do not see
__pycache__in the tree do not worry, the important thing is that you renamedmagic.pytotest_magic.pyfor unittest to find the testthe
-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 foldersI run the tests again
python3 -m unittestthe 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 fileThis is the
REDpart 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 screenFAILED (failures=1)the number of failuresRan 1 test in A.XYZsthe number of tests it ran and how long they tookAssertionError: True is not falsethe 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_failurethe 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, 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
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 unittesta few times to see the test failI ran
python3 -m unittestagain to see the test passI 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, 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-watcherI 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>is an operator that is used to send output from a program to the given filepytest-watcher is a Python program that automatically runs pytest when I change code in the project
pytest is a Python package like unittest, that is used for testing
requirements.txtis the name of a file where I can list Python packages for pip to install. The namerequirements.txtis Python convention, I can use any name I want for the requirements file
I run tree to see what the project looks like now
tree -a -L 2the terminal shows
. ├── requirements.txt ├── src │ └── magic.py └── tests ├── __init__.py ├── __pycache__ └── test_magic.pyrequirements.txtis now in themagicfolder
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 initthe 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 └── testsI remove
main.pysince I already havemagic.pyin thesrcfolder for the main programrm main.pythe terminal goes back to the command line
.../pumping_python/magic (main)Here is what uv added to the project
.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 sharepyproject.tomlis a file that is used to configure Python projects for packaging see pyproject.toml for more.python-versionis a file that has the version of Python I am usingREADME.mdis a file that is used to describe the project
I use cat 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 = "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-versioncat .python-versionthe terminal shows
3.XYI show what is in
README.mdcat README.mdthe 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--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 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.FI run tree to see what changed in the project
tree -a -L 1the terminal shows
. ├── .git ├── .gitignore ├── pyproject.toml ├── .python-version ├── README.md ├── requirements.txt ├── src ├── tests ├── uv.lock └── .venvuv 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 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
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 shows
(magic) .../magic (main)(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 (main)
how to run the tests automatically with uv and pytest-watcher
I try to run the tests again
uv run pytest-watcher . --nowthe terminal shows
command not found: pytest-watchersame error
I use uv to run pytest-watcher
uv run pytest-watcherthe 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: pathoh boy! I just want my tests to run already. I see a
--nowoptionI try to run the tests again with the
--nowoptionuv run pytest-watcher --now--nowis an option that tells pytest-watcher to run the tests now without asking me for inputthe 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: pathI 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 directorythe 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.pyto place the cursor in the editor of the Integrated Development Environment (IDE), then I change False to True on line 77 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: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 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.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 successfully wrote a failing test
I made the failing test pass
I made the tests 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
cd pumping_python mkdir pumping_python cd pumping_python tree cd magic mkdir magic tree cd magic python3 src/magic.py mkdir src tree python3 src/magic.py touch src/magic.py tree python3 src/magic.py python3 -m unittest mkdir tests tree touch tests/magic.py tree python3 -m unittest code tests/magic.py touch tests/__init__.py tree python3 -m unittest mv tests/magic.py tests/test_magic.py tree -L 2 python3 -m unittest pytest-watcher echo "pytest-watcher" echo "pytest-watcher" > requirements.txt tree -a -L 2 cat requirements.txt uv init tree -a -L 1 rm main.py cat .gitignore cat pyproject.toml cat .python-version cat README.md uv add --requirement requirements.txt tree -a -L 1 source .venv/bin/activate deactivate pytest-watcher cat pyproject.toml pytest-watcher uv run pytest-watcher uv run pytest-watcher --now uv run pytest-watcher --now . cd ..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 help make a Python Test Driven Development environment
mkdir NAME_OF_THE_PROJECT cd NAME_OF_THE_PROJECT mkdir src touch src/NAME_OF_THE_PROJECT.py mkdir tests touch tests/__init__.py touch tests/test_NAME_OF_THE_PROJECT.py echo "pytest-watcher" > requirements.txt uv init rm main.py uv add --requirement requirements.txt uv run pytest-watcher --now .where
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, 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