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

Unlock This Lesson

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

Unlock This Lesson

PyTest Fixtures & Fleshing Out Your Stack Class

By implementing the push functionality of the Stack class, you’ll meet PyTest fixtures, which are a great way to provide a fixed baseline upon which tests can reliably and repeatedly executed.

00:00 Next, I want to talk about the .push() method. So, in a stack you have two main methods, .push() and .pop(). .push() puts something onto the stack, and then .pop() removes it from the stack.

00:13 Okay? So, let’s go ahead and create a test—because it’s test driven development—create a test for pushing something onto the stack. So I’m going to say def test_push().

00:30 Now, what I could do in here is I could do—like, to set up this test—

00:37 is I could do what I did in test_constructor(), where I create this stack, s = Stack(). But maybe I have a whole bunch of tests and I keep repeating the same code over and over and over again where I’m saying s = Stack().

00:50 Instead, I can do what’s called a fixture, and so let me show you what fixtures are. They’re a little interesting, but very powerful. They’re really used for when you have some code that is repetitive, that you keep typing over and over again.

01:03 It alleviates that need. So how you do this is you say import pytest—that’s the first thing. And then the second thing is you just create a function.

01:18 I’m going to call this function stack()lowercase, just like that. And it’s a function, so I’m going to say def that, and put the parentheses on there, and I’ll say pass—just for right now. Okay.

01:31 Now, above this function, to make this thing into a fixture, you have to decorate this function with @pytest.fixture, like that. So this turns this from a regular function into what’s called a fixture.

01:48 Now I’m going to show you how this works. Basically, what I’m going to do is all I’m going to do is I’m going to say return Stack(), just like that.

02:00 So what happens here is if I were to call this function stack(), it’s actually going to give me a brand new Stacksort of just like the constructor does, okay?

02:09 So what I’m going to do is I’m going to inject a stack object into my test_push() function. So how this works is I type in stack, just like that.

02:21 And this stack has to be the same name as this stack. And so what happens is if you put this stack right here as a parameter, what will happen is when the test runs, it’ll actually call def stack()it’ll create a Stack object, it’ll return it, and it’ll inject that Stack object into this function.

02:43 And so then you don’t have to do it like what we did on line 11—you don’t have to do that. Once again, It’s not really necessary in this type of test, but you could imagine a test where you’re creating an object and it’s massive, and there’s a lot of arguments to it, and you don’t want to keep typing it over and over again, and so that sort of alleviates that problem. Okay.

03:03 So now I have this stack object. What I would like to do is I would like to push something into it. So I would say stack.push()because .push() is a method on a stack data structure.

03:15 I’m going to push the 3 on, okay?

03:19 And let’s make an assertion. Let’s say, I assert… I can do a couple of things. I could say “I assert that the length of s is equal to 1.

03:38 Okay. And that should make sense. If the stack is initially blank or empty and I put 3 onto the stack, then there’s now one thing inside that stack, and so the length of it should be 1. Okay.

03:52 So, I wrote my test like this, and it’s called test driven development because I wrote my test first and it’s going to fail—oh, this should not be s, this should be stack.

04:03 So, I wrote my test and yeah, it should fail. So let’s go ahead and run this. I’m going to switch back to my terminal, run this code here, and it says 1 failed, 1 passed. The constructor one still passes, so that’s good news for us.

04:20 And the one that failed is the one we just did. test_push, as you can see—that failed. And the reason it failed is it says because the Stack object that got created, it has no attribute 'push'.

04:33 And so what we want to do is we want to create an attribute, or a method, called .push(). So let’s do that. Okay. So, all I have to do is say def push(self)—and as you can see right here, based on the signature of this function, it looks like you’re passing in some item. Now, I don’t know the datatype of this item. I’m passing in a 3, but it could be a dog, or a cat, or a Boolean, or a floating point number, or a string—so, the type doesn’t really matter.

05:06 So I’m not going to call this num, because that’s not actually accurate. It is technically accurate in this one circumstance, but we want this Stack data structure to be generally usable, and so instead of just typing in num, how about we type in item, so it’s just a generic item. Okay.

05:25 So then, after we do this, what we’re going to do is…

05:30 all I want to do is take this item and put it into this list. To do that, all you have to do is self._storage.append(item).

05:45 So, even though these people are using a stack and they’re using .push() and .pop()internally, we’re using a list. But we could be using a binary tree, or a linked list, or any other sort of exotic data structure.

05:58 The purpose of this is that they don’t necessarily know or even care what the internal data structure looks like—just as long as it works. Just as long as it has a .push() and a .pop(), that’s all that matters to them. Okay.

06:09 So we’ve added one item to this stack and…

06:14 let’s see what happens to this test. So, I come back here and run the test again, and it looks like it passed. So, I was able to successfully add an item to the test.

06:25 Now what I could do is I could add another item. I could say stack.push()—push a 5 in, and then I could assert that the length of the stack is 2.

06:43 So we could make two assertions. We could push one thing—is the length 1? Push another thing—is the length 2?

06:52 And then we could run that test.

06:56 And that passes as well. Okay! So this looks pretty good. So far, we have our constructor, we have push functionality, we have this fixture that’s dynamically injecting this object during test time, so that that’s pretty nice. The last thing we need to do, of course, is implement pop functionality.

chrisstinemetz on Dec. 2, 2019

In VS code if you want to fix the import error you can add your virtual environment to workspace settings.

Open the settings.json file in .vscode directory

Then add the following:

{
    "python.venvPath": "~/patht/to/venvDirectory"
}

chrisstinemetz on Dec. 2, 2019

Correction, use this:

{
    "python.pythonPath": "/path/to/your/venv/bin/python",
}

Wesley on Aug. 7, 2020

Question! Are these ‘unit tests’ or ‘integration tests’? I couldn’t figure out the difference from the written tutorial. These seem like unit tests to me, but I don’t really know what the difference is functionally.

Bartosz Zaczyński RP Team on Aug. 7, 2020

The main goal of unit testing is to verify a single component’s correctness in isolation from the outer world. Integration tests, on the other hand, let you check if those individual components can work together.

Both kinds of tests serve a different purpose, so you should exercise both.

For example, you may assert that each cabinet’s drawer can be successfully opened and closed, but when you put two of them at a corner, you won’t be able to open both at once, because the open one would block the other.

Testing a stack that doesn’t interact with other components is a unit test, I’d say. A “component” or “unit” typically translates to a Python class.

RobyB on Nov. 26, 2020

In the middle of this video, when we are passing from: write the test for push, and then, write the push method: My pytest.fixture is this:

@pytest.fixture
def stack():
    return stack.Stack()

I got this different error:

AttributeError: ‘function’ object has no attribute ‘Stack’

but If I rename the fixture into another name, like stack_def

@pytest.fixture
def stack_def():
    return stack.Stack()

and then I use also this new name inside test_push:

def test_push(stack_def):
    stack_def.push(3)
    assert len(stack_def) == 1

I solve the previous error, and I get the normal one, about the attribute ‘push’found:

AttributeError: ‘Stack’ object has no attribute ‘push’

So, it seams we are overriding the name stack, someway ? and we can’t use a fixture with the same name of the class?

I have Python 3.6 and a virtual environment.

Bartosz Zaczyński RP Team on Nov. 27, 2020

@RobyB That’s right. More specifically, it’s a name collision because you try to use the same name for two different purposes:

  1. Your fixture function
  2. A module, which defines the Stack type

You have several options to mitigate that. For example, you could rename the module at the import time:

import stack as pile

@pytest.fixture
def stack():
    return pile.Stack()

Alternatively, you could import Stack without importing the enclosing module:

from stack import Stack

@pytest.fixture
def stack():
    return Stack()

A common naming convention in Python that lets you avoid collisions like that is to append a trailing underscore (_) to one of the names, e.g.

import stack

@pytest.fixture
def stack_():
    return stack.Stack()

dsgfdsfdsfvsdfvsdfv on March 16, 2021

This isn’t TDD though is it, the point of TDD is to shape code through tests which are driven by problem analysis.

TDD life-cycle

  1. Get requirements.
  2. Design components.
  3. Write tests for all components.
  4. Make sure all tests fail.
  5. Write bare minimum of code to pass tests.
  6. Refactor, test, repeat.

Am I wrong?

Become a Member to join the conversation.