Skip to content

Supybot Website

Sections
Personal tools
You are here: Home » Documentation » Supybot Help Center » Tutorials » Advanced Plugin Testing » Plugin Tests

Plugin Tests

Document Actions
How to write tests for commands in your plugins.

Strike

The complete guide to writing tests for your plugins.
Page 2 of 4.

Introduction

This tutorial assumes you've read through the plugin author tutorial, and that you used supybot-plugin-create to create your plugin (as everyone should). So, you should already have all the necessary imports and all that boilerplate stuff in test.py already, and you have already seen what a basic plugin test looks like from the plugin author tutorial. Now we'll go into more depth about what plugin tests are available to Supybot plugin authors.

Plugin Test Case Classes

Supybot comes with two plugin test case classes, PluginTestCase and ChannelPluginTestCase. The former is used when it doesn't matter whether or not the commands are issued in a channel, and the latter is used for when it does. For the most part their API is the same, so unless there's a distinction between the two we'll treat them as one and the same when discussing their functionality.

The Most Basic Plugin Test Case

At the most basic level, a plugin test case requires three things:

  • the class declaration (subclassing PluginTestCase or ChannelPluginTestCase)
  • a list of plugins that need to be loaded for these tests (does not include Owner, Misc, or Config, those are always automatically loaded) - often this is just the name of the plugin that you are writing tests for
  • some test methods

Here's what the most basic plugin test case class looks like (for a plugin named MyPlugin):

    class MyPluginTestCase(PluginTestCase):
        plugins = ('MyPlugin',)

        def testSomething(self):
            # assertions and such go here

Your plugin test case should be named TestCase as you see above, though it doesn't necessarily have to be named that way (supybot-plugin-create puts that in place for you anyway). As you can see we elected to subclass PluginTestCase because this hypothetical plugin apparently doesn't do anything channel-specific.

As you probably noticed, the plugins attribute of the class is where the list of necessary plugins goes, and in this case just contains the plugin that we are testing. This will be the case for probably the majority of plugins. A lot of the time test writers will use a bot function that performs some function that they don't want to write code for and they will just use command nesting to feed the bot what they need by using that plugin's functionality. If you choose to do this, only do so with core bot plugins as this makes distribution of your plugin simpler. After all, we want people to be able to run your plugin tests without having to have all of your plugins!

One last thing to note before moving along is that each of the test methods should describe what they are testing. If you want to test that your plugin only responds to registered users, don't be afraid to name your test method testOnlyRespondingToRegisteredUsers or testNotRespondingToUnregisteredUsers. You may have noticed some rather long and seemingly unwieldy test method names in our code, but that's okay because they help us know exactly what's failing when we run our tests. With an ambiguously named test method we may have to crack open test.py after running the tests just to see what it is that failed. For this reason you should also test only one thing per test method. Don't write a test method named testFoobarAndBaz. Just write two test methods, testFoobar and testBaz. Also, it is important to note that test methods must begin with test and that any method within the class that does begin with test will be run as a test by the supybot-test program. If you want to write utility functions in your test class that's fine, but don't name them something that begins with test or they will be executed as tests.

Including Extra Setup

Some tests you write may require a little bit of setup. For the most part it's okay just to include that in the individual test method itself, but if you're duplicating a lot of setup code across all or most of your test methods it's best to use the setUp method to perform whatever needs to be done prior to each test method.

The setUp method is inherited from the whichever plugin test case class you chose for your tests, and you can add whatever functionality you want to it. Note the important distinction, however: you should be adding to it and not overriding it. Just define setUp in your own plugin test case class and it will be run before all the test methods are invoked.

Let's do a quick example of one. Let's write a setUp method which registers a test user for our test bot:

    def setUp(self):
        ChannelPluginTestCase.setUp(self)  # important!!
        # Create a valid user to use
        self.prefix = 'foo!bar@baz'
        self.feedMsg('register tester moo', to=self.nick, frm=self.prefix))
        m = self.getMsg()  # Response to registration.

Now notice how the first line calls the parent class's setUp method first? This must be done first. Otherwise several problems are likely to arise. For one, you wouldn't have an irc object at self.irc that we use later on nor would self.nick be set.

As for the rest of the method, you'll notice a few things that are available to the plugin test author. self.prefix refers to the hostmask of the hypothetical test user which will be "talking" to the bot, issuing commands. We set it to some generically fake hostmask, and then we use feedMsg to feed it a private message (using the bot's nick, accessible via self.nick) to register the username "tester" with the password "moo". We have to do it this way (rather than what you'll find out is the standard way of issuing commands to the bot in test cases a little later) because registration must be done in private. And lastly, since feedMsg doesn't dequeue any messages from the bot after getting fed a message, we perform a getMsg to get the response. You're not expected to know all this yet, but do take note of it since using these methods in test-writing is not uncommon. These utility methods as well as all of the available assertions are covered in the next section.

So, now in any of the test methods we write, we'll be able to count on the fact that there will be a registered user "tester" with a password of "moo", and since we changed our prefix by altering self.prefix and registered after doing so, we are now identified as this user for all messages we send unless we specify that they are coming from some other prefix.

The Opposite of Setting-up: Tearing Down

If you did some things in your setUp that you want to clean up after, then this code belongs in the tearDown method of your test case class. It's essentially the same as setUp except that you probably want to wait to invoke the parent class's tearDown until after you've done all of your tearing down. But do note that you do still have to invoke the parent class's tearDown method if you decide to add in your own tear-down stuff.

Setting Config Variables for Testing

Before we delve into all of the fun assertions we can use in our test methods it's worth noting that each plugin test case can set custom values for any Supybot config variable they want rather easily. Much like how we can simply list the plugins we want loaded for our tests in the plugins attribute of our test case class, we can set config variables by creating a mapping of variables to values with the config attribute.

So if, for example, we wanted to disable nested commands within our plugin testing for some reason, we could just do this:

    class MyPluginTestCase(PluginTestCase):
        config = {'supybot.commands.nested': False}

        def testThisThing(self):
            # stuff

And now you can be assured that supybot.commands.nested is going to be off for all of your test methods in this test case class.

Created by Strike
Contributors : Strike
Last modified 19:38 April 26, 2006
 

Powered by Plone

This site conforms to the following standards: