Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Locked learning resources

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Writing Your First Test & The Core TDD Cycle

After setting up the project, you’ll now write your first unit test and learn about the core TDD cycle.

00:00 To start, let’s just go ahead and create a skeleton for our stack data structure. To do that, I’m going to create a class called Stack. And since I don’t have any particular implementation, I’m just going to type in pass.

00:17 So, this will at least be syntactically correct if I try to run it. Next, I would like to import this Stack class into my test file. So, normal import rules apply—I’m going to say from ds.stack import Stack.

00:38 Okay.

00:41 Now, what I can do is run this. Now of course, there are no actual tests, but that won’t prevent us from running it. I’m going to switch over to the terminal and I’m going to run this and just see what happens.

00:54 So, how you can run pytest is you type in python -mwhich means module—pytest -v. And -v means verbose, so, “Show me all the particular tests that are either passing or failing.” And of course we can see here, it says collected 0 items—that means there were no tests—and we can see that there were no tests, and this test ran in 0.01 seconds.

01:17 So this is showing us that pytest is working, it is operational. It’s just—there were no tests that it saw. That’s okay, we can add some.

01:29 Let’s go ahead and add a test for our constructor. So, we might want to go ahead and construct an object from this class, and that’s called a constructor. Let’s create a test around that.

01:39 If I were to create def—which is, “I’m going to create a function”—I’m going to, call it test_constructor(), just like that. And so how pytest works is the name of the function should be prepended with test_ (test underscore). If you were testing a dog, you would say test_dog(). If you were testing a cat—test_cat().

01:59 So, that’s just how it works. Now, because we’re testing a constructor, I’m just going to call this test_constructor(). Now in this method right here, I need to create an object.

02:08 I’m going to call this object s and it’s going to come from this Stack class. Now, once you have this object, you can make what’s called an assertion against it. And what assertions are—it’s a keyword, assert, and what happens after it… Let’s imagine I say 1 == 2. This, what assert is looking for, is a Boolean expression. 1 == 2 will evaluate to either a True or False. If it’s True, the assert passes.

02:41 If it’s False, the assert fails. So here, because 1 clearly does not equal 2, this test actually will fail. So if I come back here and run this test again, we can see down here it says assert 1 == 2that was an error. It happened because 1 does not equal 2, and it says 1 test failed. So, how you can make this test pass, of course, is you could type in 2 == 2 or 1 == 1.

03:08 We can flip back here and run the test again. And it says 1 passed and it’s in green. The test_constructor() actually passed. So, this is how you can actually test out something—whether it works or not—using these assertion statements.

03:24 So instead of using 2 == 2, I can change this to say isinstance(). Is what an instance? Is s—this object that I just created—is that an instance of Stack.

03:36 That’s maybe one assertion that I can make—I can make multiple assertions, but let’s just start off with a very simple one. Okay. I’m going to flip back to the terminal…

03:50 and that passed, and that should make sense, right? You can see right here that s is actually coming from Stack(), so this assertion should pass.

04:00 Now, the question you want to ask yourself is, “Is this a good test?” And I would say “No.” The reason is because when you create a stack, it’s actually a data structure that contains internal data—like a list contains data, or a set contains data, or a dictionary contains data.

04:17 There’s nothing in this Stack over here that actually contains data. And so what we can do is let’s go ahead and override our constructor so it actually will contain some data, and then we can test it.

04:29 Now, this is called test driven development, and it’s called test driven development because you should actually do the tests first. And so what I would like to do is just go ahead and write our tests.

04:38 I’m going to say assert, and the test is going to fail and it should fail—that’s the whole purpose behind it, is a test will fail and then we’ll make it pass. And in the process of making it pass, we are essentially adding functionality to our Stack class.

04:54 Okay. What I’d like to do is I would like to say “Is the length of s,” which happens to be a Stack, “is it 0?” Is the length of this Stack 0?

05:10 So, this is pretty strange. Normally, you take the length of a list or length of a tuple, for example—but how can you take the length of a Stack? That may not seem to make any sense.

05:20 And of course, this test will fail.

05:24 If I come back to my terminal and run the test, we can see that it failed, and the reason it failed—it says the Stack has no function called len().

05:32 So, we’re going to have to do a couple things. One is we’re going to have to create a function called len()so maybe we’ll do that first.

05:42 In order for this len() function to work, inside the Stack we have to create a magic method called called .__len__().

05:51 self is the parameter. And then we can just return—obviously, .__len__() should return some numeric value, like 5 or 7 or whatever. Let’s just say return 3, just so the test can move forward.

06:08 If I save this and I flip back to here and I run my test again, last time it gave me an error and it said len() was undefined. Now it says that 3 doesn’t equal 0, because remember, I’m actually returning 3 from that test.

06:25 And of course, I could actually just return 0 and the test would pass, but that’s not true to what I’m trying to do here. So how can I make this .__len__() return a number 0 and actually do what I want to do, which is contain some internal data? And how I’m going to do this is I’m actually going to create a constructor method.

06:45 And so I’m going to say def __init__(self). So this is my constructor method. And in here, what I want to do is I’m going to say self._storage is equal to an empty list.

07:01 So, this is going to be the internal storage inside of my Stack data structure.

07:08 So if I put in dogs or cats or people or numbers or Booleans or whatever—I need someplace to actually store that, and so that will be stored inside here.

07:17 Now, you may be wondering why am I calling it _storage instead of just storage. Well, sort of by doing an underscore (_), I’m relaying information to potential other programmers—or maybe my future self—that this attribute should be private.

07:35 Okay? I’m signaling that I want this to be private, because there actually isn’t private in Python. You have to signal it with these underscores.

07:45 So, I don’t actually want to expose ._storage to the external world. I want it to be private, I want to be internal, so that’s why I’m, prefixing _storage with the underscore (_). Now—now that I have this—what I can do in my .__len__() function is instead of returning 3, I can just say return the length of self._storage.

08:09 And because ._storage is actually a list, I’m effectively saying, “What is the length of this list?” The list is 0there’s nothing inside the list, because it’s brand new.

08:19 And so this will return a 0, and therefore, when I do len() over here—len(s)len() actually calls this .__len__()—the double underscore .__len__().

08:30 Okay? And so that should return 0. So, hopefully this test will pass. I’m going to switch back to my terminal and let’s see what happens. So here I am in the terminal, I’m going to clear my screen and rerun the test—and it passed.

08:46 And that’s exactly what I wanted to happen. So, in recap, I create an object called s from the Stack class. And what happens is I create some internal storage that’s private, that’s a list. And it’s empty—it’s an empty list.

09:01 And when I happened to call len() on this Stack, it actually calls this .__len__(), and it returns the length of this actual list, which happens to be 0, and therefore this assertion passes, and the assertion on line 6 passes as well too, because s is a Stack.

09:19 So, this is the first step in actually creating our Stack class.

Avatar image for Dan Bader

Dan Bader RP Team on March 13, 2019

@Elie: Thanks for your question. Our video lessons are only available for streaming at the moment.

Avatar image for Elie Kawerk

Elie Kawerk on March 13, 2019

Thanks @Dan, is the material covered in the video available on Github?

BTW, I like the revamped real-python platform a lot!

Avatar image for Dan Bader

Dan Bader RP Team on March 13, 2019

Not right now but that’s a great idea, we can definitely put the sample code up on GitHub and then link it from here :)

Avatar image for Vikram Kalabi

Vikram Kalabi on March 18, 2019

Could you please share information on your Visual Studio theme and setup? Looks wonderful!

Avatar image for Dan Bader

Dan Bader RP Team on March 18, 2019

@Vikram: Here they are—

The theme is Monokai Pro. www.monokai.pro/vscode/

I use either Dank Mono or Operator Mono. dank.sh www.typography.com/fonts/operator/styles/

I copied this from one of Chyld’s comments on another video in this series.

Avatar image for Grant King

Grant King on March 20, 2019

That import line gives me an error with this file structure, but everything works if I move test_stack.py directly under the DS folder. Is there a better way to make it work?

Avatar image for Vanam

Vanam on April 12, 2019

Nice tutorial, terminal that is used is it ZSH?

Avatar image for adoormouse

adoormouse on Sept. 10, 2019

Had issues with running pytest with this setup. Got it working with:

test_stack.py
from ds import Stack
...

(venv)$ python -m pytest tests/

Avatar image for AugustoVal

AugustoVal on Sept. 18, 2019

Quick question - I am getting this message trying to run Pytest

AVal-iMac% python -m pytest -v
/usr/bin/python: No module named pytest
AVal-iMac% python3 -m pytest -v
/usr/local/bin/python3: No module named pytest

I have the package installed according to my list.

AVal-iMac% pip3 list           
Package            Version   
------------------ ----------
pytest             5.1.2     
pytest-cache       1.0       
pytest-pep8        1.0.6 

Any subjection why it is not working for me? I already un-installed and re-installed but is not working.

Thanking you in advances for your help

Avatar image for Dan Bader

Dan Bader RP Team on Sept. 18, 2019

@AugustoVal, try running the pytest command directly like so:

pytest -v
Avatar image for Dri

Dri on Oct. 2, 2019

I’m getting the following error when running pytest:

ImportError while importing test module '/test-driven-dev/tests/test_stack.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
tests/test_stack.py:1: in <module>
    from ds.stack import Stack
E   ModuleNotFoundError: No module named 'ds'

My directory schema:

test-driven-dev ]$ tree
.
├── ds__.py
   └── stack.py
└── tests
    ├── __pycache__
       ├── test-stack.cpython-37-pytest-5.2.0.pyc
       └── test_stack.cpython-37-pytest-5.2.0.pyc
    └── test_stack.py
   ├── __init

Any one gone through this?

Avatar image for przemodev

przemodev on Nov. 20, 2019

I believe that saying that a single underscore makes variable private and protects from accessing the value directly does not make any sense. Would rather say that __var makes the variable somewhat private i.e: not accessible by just a . notation on the object (__var is called _ClassName__var instead and . accessible anyway).

Avatar image for Christopher Lee

Christopher Lee on May 5, 2020

Never in the history of forever has anyone ever picked such a terrible font to due a coding tutorial, it’s like a horrible Walt Disney style of text that I cannot read…

Avatar image for subonlinetmp

subonlinetmp on July 29, 2020

@Dri Yes, me too if I try just pytest -v (Though your problem may be that __init.py__ should be in ds/ not tests/ – as ds is meant to be the package from which one can import the module stack with the Stack class. However, supposedly in Python 3.3+ __init.py__ is no longer required…but, maybe you’re running an earlier version and this causes ds to not show up as a package for you?)

@Dan Bader First off, I don’t understand why we’re not running pytest directly as you suggest (i.e. why he’s using python -m pytest -v to start with, instead of just pytest -v as you suggest)…?

Indirectly, maybe this is why: When I attempt to run pytest directly, I get the same error @Dri reported:

12:08 ~/src/python/examples/tdd} pytest -v
============================= test session starts =============================
platform darwin -- Python 3.8.2, pytest-5.4.3, py-1.8.1, pluggy-0.13.1 -- /Library/Frameworks/Python.framework/Versions/3.8/bin/python
cachedir: .pytest_cache
rootdir: /Users/rich/src/python/examples/tdd
plugins: cov-2.10.0
collected 0 items / 1 error                                                   

=================================== ERRORS ====================================
____________________ ERROR collecting tests/test_stack.py _____________________
ImportError while importing test module '/Users/rich/src/python/examples/tdd/tests/test_stack.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
tests/test_stack.py:1: in <module>
    from ds.stack import Stack
E   ModuleNotFoundError: No module named 'ds'

My import is same as in the course:

from ds.stack import Stack

My file structure is also the same:

12:08 ~/src/python/examples/tdd} tree
.
├── README.txt
├── README.txt~
├── ds
   ├── __init.py__
   ├── __pycache__
      ├── stack.cpython-38.pyc
      └── test_stack.cpython-38-pytest-5.4.3.pyc
   ├── stack.py
   └── stack.py~
└── tests
    ├── __pycache__
       └── test_stack.cpython-38-pytest-5.4.3.pyc
    ├── test_stack.py
    └── test_stack.py~

I’m glad about this error, actually, as it points out something I don’t understand, but feel is important: I’d like to understand how the import is searching for the ds module in general, and why it’s not finding it with “pytest”, but does find it with python -m pytest…?

From web sleuthing, I’ve read that the latter adds CWD to sys.path. But, I still don’t see how it all fits and why one works and the other doesn’t? Why would it need to add CWD to a path when I’m running from that same dir? Is it searching from tests/ since it finds test_stack.py there, but no ds/ subdir in tests/?

To test that, I tried adding CWD to --rootdir, but got the same error:

01:15 ~/src/python/examples/tdd} pytest -v --rootdir=$HOME/src/python/examples/tdd/
============================= test session starts =============================
platform darwin -- Python 3.8.2, pytest-5.4.3, py-1.8.1, pluggy-0.13.1 -- /Library/Frameworks/Python.framework/Versions/3.8/bin/python
cachedir: .pytest_cache
rootdir: /Users/rich/src/python/examples/tdd
...
tests/test_stack.py:2: in <module>
    from ds.stack import Stack
E   ModuleNotFoundError: No module named 'ds'

…so, I added an assert to test_stack.py to check sys.path and found that, sure enough, the difference is that CWD = /Users/rich/src/python/examples/tdd on the sys.path (and that --rootdir=$HOME/src/python/examples/tdd/ does NOT add it to sys.path!!)

via python -m pytest -v:

sys.path = ['/Users/rich/src/python/examples/tdd/tests', '/Users/rich/src/python/examples/tdd', '/Library/Frameworks/Python.framework/Versions/3.8/lib/python38.zip', '/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8', '/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/lib-dynload', '/Users/rich/Library/Python/3.8/lib/python/site-packages', '/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages', '/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/gnureadline-8.0.0-py3.8-macosx-10.9-x86_64.egg']

via pytest -v: and pytest -v --rootdir=$HOME/src/python/examples/tdd/:

sys.path = ['/Users/rich/src/python/examples/tdd/tests', '/Library/Frameworks/Python.framework/Versions/3.8/bin', '/Library/Frameworks/Python.framework/Versions/3.8/lib/python38.zip', '/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8', '/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/lib-dynload', '/Users/rich/Library/Python/3.8/lib/python/site-packages', '/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages', '/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/gnureadline-8.0.0-py3.8-macosx-10.9-x86_64.egg']

So, I’m at a loss to figure out just what’s going on here (unless there’s a bug in pytest --rootdir), nor how to get pytest to work directly as suggested…

[ other than this symlink hack in tests/:
01:29 ~/src/python/examples/tdd/tests} ls -l ds
lrwxr-xr-x  1 rich  staff  5 Jul 29 01:28 ds@ -> ../ds
]

I’d really appreciate some help with this!!

Avatar image for subonlinetmp

subonlinetmp on July 29, 2020

PS. After reading docs.python.org/2/tutorial/modules.html#the-module-search-path, I found that this also allowed pytest -v to work (ie, find the ds.stack module):

01:35 ~/src/python/examples/tdd} export PYTHONPATH="$HOME/src/python/examples/tdd/"

But, that also seems like a hack: In order for this to work automatically, we’d need to be setting PYTHONPATH every time we change directories!

Why doesn’t pytest just include CWD by default? Why doesn’t --rootdir=CWD work either?

(Maybe the author’s already been down this path!? All the sudden python -m pytest isn’t looking so bad! :)

Avatar image for Julie Stenning

Julie Stenning on Sept. 27, 2020

On my Windows system, the import statement in the test_Stack.py file didn’t work because it couldn’t find the module “ds”.

I resolved it by using steps covered at python-packaging.readthedocs.io/en/latest/minimal.html. However, it isn’t a perfect fix because I couldn’t tell it to just import the class Stack. It did allow me to carry on with the tutorial.

Avatar image for Julie Stenning

Julie Stenning on Sept. 27, 2020

I don’t appear to be able to edit my own comments. I was too hasty. Because I can’t refer to the class properly in the test file, I am unable to carry on. I will be able to if I put the module stack.py and the test module in the same folder. So .... ignore my comment above.

Avatar image for Darek

Darek on May 8, 2021

Hi there. I’d like to kindly point out that the __init__ method should not be called “constructor” but “initializer.” In Python, constructors are rarely used in day-to-day programming and when one starts using them, one does something that’s called meta-programming. Writing an object constructor requires writing code for the type/class of the object, not the object itself. Just wanted to clarify.

Avatar image for eulle100

eulle100 on Jan. 31, 2022

Could you please share information on your Terminal theme and setup? Looks wonderful!

Avatar image for Lucas Zago

Lucas Zago on Nov. 3, 2022

I believe some people have the same issue when running pytest:

from ds.stack import Stack
E   ModuleNotFoundError: No module named 'ds'

How to solve it?

Avatar image for Bartosz Zaczyński

Bartosz Zaczyński RP Team on Nov. 3, 2022

@Lucas Zago Can you show us your directory structure, e.g., by running the tree command as shown in the previous video?

Avatar image for Ariba S

Ariba S on July 8, 2024

was the import error question answered? I get the same issue…

Avatar image for Martin Breuss

Martin Breuss RP Team on July 9, 2024

@Ariba S can you share your directory structure, like @Bartosz Zaczyński suggested? It’ll help to debug what might be going wrong with the imports.

Become a Member to join the conversation.