-1

I am looking through the pytest documentation, and there is a small detail that is driving me crazy.

I'm currently looking at the documentation for this page, and it gives the following example:

import pytest


class Fruit:
    def __init__(self, name):
        self.name = name
        self.cubed = False

    def cube(self):
        self.cubed = True


class FruitSalad:
    def __init__(self, *fruit_bowl):
        self.fruit = fruit_bowl
        self._cube_fruit()

    def _cube_fruit(self):
        for fruit in self.fruit:
            fruit.cube()


# Arrange
@pytest.fixture
def fruit_bowl():
    return [Fruit("apple"), Fruit("banana")]


def test_fruit_salad(fruit_bowl):
    # Act
    fruit_salad = FruitSalad(*fruit_bowl)

    # Assert
    assert all(fruit.cubed for fruit in fruit_salad.fruit)

I get the basic idea of what is happening on this page, but the inclusion of the * in the fruit_bowl argument is confusing to me.

For example, if you just want to initialize the classes by themselves the code would not work:

fruit_bowl = [Fruit("Apple"), Fruit("Banana")]
fruit_salad = FruitSalad(fruit_bowl)

returns the error message:

AttributeError: 'list' object has no attribute 'cube'

In this case replace the *fruit_bowl argument with just fruit_bowl works fine.

Then I realized that fruit_bowl was defined as a function, so I thought that would do the trick, but again just running the code outside of a test returned an error.

If I setup the code like this:

def fruit_bowl():
    return [Fruit("Apple"), Fruit("Banana")]

class Fruit():
    def __init__(self, name):
        self.name = name
        self.cubed = False
        
    def cube(self):
        self.cubed = True

class FruitSalad():
    
    def __init__(self, *fruit_bowl):
        self.fruit_bowl = fruit_bowl
        self._cube_fruit()
        
    def _cube_fruit(self):
        for fruit in self.fruit_bowl:
            fruit.cube()

Then running fruit_salad = FruitSalad(fruit_bowl) gives the error message AttributeError: 'function' object has no attribute 'cube'.

Does this mean the use of the *fruit_bowl argument is specific to how pytest works? Ie, these things will only work if the argument is a function with the @fixture decorator added, or is there some other point I'm missing.

At the moment I find the listed code confusing because the non pytest code does not work as-is, so I struggle to see how I'd implement the use of fixtures with my own work.

Jonathan Bechtel
  • 3,497
  • 4
  • 43
  • 73
  • 1
    `fruit_bowl` the function is shadowed by `fruit_bowl` the argument. And `*` is known as "sequence unpacking". It turns a sequence that would be passed as a single argument into each of its elements being passed as a different argument. – Carcigenicate Aug 08 '21 at 17:55

1 Answers1

1

No, the * argument unpacking isn't integral to Pytest at all. I'd call the argument to FruitSalad fruits, get rid of the *s. (on both declaration and invocation) and annotate it with List[Fruit] to make it crystal clear.

AKX
  • 152,115
  • 15
  • 115
  • 172