[Tutor] What exactly is "state"?

Danny Yoo dyoo at hashcollision.org
Mon Mar 2 18:34:14 CET 2015


On Mon, Mar 2, 2015 at 8:25 AM, Sydney Shall <s.shall at virginmedia.com> wrote:
> I am a beginner and I am now at the strage of learning to write unittests.
> I have followed the current discussion entitled "How to test a class in
> pyhton", and I am not clear precisely what is meant by state. In its common
> meaning I can see some relevance. But is there a technical aspect to the
> notion. I see it mentioned often and feel rather uncomfortable that I know
> so little about it.


Hi Sydney,

Let's take a concrete example that might help things get started.
Let's say that we're writing a playlist generator for a music
broadcast system.

##########################
class Playlist(object):
    # ...?
##########################

What do we want the Playlist to know?  When we create such a Playlist,
what does the playlist have in hand?


Let's say that it has a complete list of songs when it's being built.

##############################
class Playlist(object):
    def __init__(self, songs):
        self.songs = songs
##############################

That is, 'songs' is a piece of a playlist's state, something that it owns.


So, we can imagine creating a playlist with songs like:

##############################
class Playlist(object):
    def __init__(self, songs):
        self.songs = songs

## Here's an example playlist.
playlist = Playlist(["Happy", "Beethoven's 9th Symphony", "Power"])
##############################

Ok, a bit eclectic.  But let's go with it.


Let's say that we want to add a simple behavior to this class, so that
it can do something useful.  Let's be able to ask the playlist to give
us the *next* song.  What would we like next to do here?  Let's say
that if we call it once on the playlist example above, we'd like to
first see "Happy".  If we call next() again, we'd like to see
"Beethoven", and if we call next() again, we'd like to see "Power".
And for the sake of it, once we're out of songs, let's just see
"*silence*".


We can express the previous paragraph as a unit test!

##############################
import unittest

class PlaylistTest(unittest.TestCase):
    def testSimple(self):
        playlist = Playlist(["Happy", "Beethoven", "Power"])
        self.assertEqual(playlist.next(), "Happy")
        self.assertEqual(playlist.next(), "Beethoven")
        self.assertEqual(playlist.next(), "Power")
        self.assertEqual(playlist.next(), "*silence*")
##############################


In a unit test with a function, all we need to do is call the function
on arguments.  But in a unit test with a class, we need to set up the
background scene.  Here, we need to first create a playlist, and
*then* test it out.

Also, notice the weirdness of calling playlist.next() multple times,
and expecting *different* results.  If you have mathematical training,
this is particularly weird!  It's important to notice this because
this means that playlist's next() method can't behave as a
mathematical function that only pays attention to its immediate
parameters.  It will also need to take into account something else,
something part of the state of the playlist.



We can actually run the tests at this point.  Of course, they'll fail,
but that's ok.  Here is a complete program that we can run:

###################################################
import unittest

class PlaylistTest(unittest.TestCase):
    def testSimple(self):
        playlist = Playlist(["Happy", "Beethoven", "Power"])
        self.assertEqual(playlist.next(), "Happy")
        self.assertEqual(playlist.next(), "Beethoven")
        self.assertEqual(playlist.next(), "Power")
        self.assertEqual(playlist.next(), "*silence*")

class Playlist(object):
    def __init__(self, songs):
        self.songs = songs

if __name__ == '__main__':
    unittest.main()
###################################################



When we run this, we'll see an error.

###################################################
$ python playlist.py
E
======================================================================
ERROR: testSimple (__main__.PlaylistGeneratorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "playlist.py", line 6, in testSimple
    self.assertEqual(playlist.next(), "Happy")
AttributeError: 'Playlist' object has no attribute 'next'

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (errors=1)
###################################################


And in this case, we are overjoyed to see errors.  The tests are
telling us that we haven't implemented a next() method.


For the sake of keeping this message short, let's write a quick and
dirty implementation of one:


###################################################
## Revised implementation of a Playlist:
class Playlist(object):
    def __init__(self, songs):
        self.songs = songs

    def next(self):
        if not self.songs:
            return '*silence*'
        nextSong, self.songs = self.songs[0], self.songs[1:]
        return nextSong
###################################################


The next() method now manages the state of the playlist: it bumps off
the top of the list to be returned to the caller.  If we rerun the
tests at this case, the test case will be happy.


To summarize: when we're writing tests on functions, all we need to do
is express the expected value of the function with respect to the
parameters we're passing in.  Input-output pairs.

But when we're dealing with objects, the concept of "parameter" with
regards to the test is a bit larger in scope: we now need to do some
set-up.  We need to construct the object as well in our test case.
That object will likely manage its own values.

If you have questions, please feel free to ask.


More information about the Tutor mailing list