Welcome to Part III of Adventures with Errbot.
Errbot User Guide has a plugin development section. It has examples of things you might want your plugin to do. I found it helpful to read a bit of the documentation, implement a feature, and then return later when I was implementing another part of the project.
I based this part of the series around two topics in the plugin development section:
The goal is to write a simple plugin from scratch, test it first using Errbot’s
interactive text mode, then the Python test tool
py.test, and finally connect
the project to a continuous integration service, Travis CI.
How do we get started? If you don’t have a development environment yet, no worries. While working on this guide, I built a simple, shareable development environment for Errbot plugins, which:
err(and other dependencies mentioned in Part I) on the virtual machine
pep8(used for testing)
To use this development environment, install Vagrant and VirtualBox first. You will also need Git to be installed on your computer.
I recommend installing the
vagrant-vbguest plugin, to keep VirtualBox Guest
Additions up to date. On the command line, run:
vagrant plugin install vagrant-vbguest
Now it’s time to download the
errbot-dev project. You can either clone it
git clone https://github.com/alimac/errbot-dev.git
Inside the project, there are two files:
Vagrantfile- contains the virtual server configuration and shell commands that will install Err
config.py- a minimal configuration of Err
Once downloaded (and unpacked), use the terminal to go to the
directory and start the virtual server:
cd errbot-dev/ vagrant up
The first time you run
vagrant up, it will take a while. Especially if Vagrant
has to download the (Ubuntu 14.04) base box.
Once the virtual server is ready, use
vagrant ssh to connect to it. Then,
go to the
/vagrant directory. You should be able to start Errbot with
cd /vagrant errbot -T
-T flag means that Errbot will not try to connect to a backend. Instead,
it will give you an interactive prompt where you can type bot commands, (as if
you were connected to an XMPP backend and in a chat. Try
another command. If you type:
You will find out that you are
is Guillaume Binet, the maintainer of Errbot.
To exit the Errbot shell, use
Ctrl + c.
I find it useful to have two simultaneous sessions, one in which I run
errbot -T, and another in which I use
vim to make changes to a plugin’s
code. You can also use a local code editor, on your desktop.
Let’s create a basic plugin for Errbot. A plugin will consist of at least three files:
pluginname.py- the plugin itself
pluginname.plug- a metadata file
test_pluginname.py- tests for the plugin
To start, we will build a simple plugin that replies whenever someone in the chat says “hello”.
/vagrant/errbot-plugins/ create a
HelloBot directory where we will store
the plugin files.
The first file -
hello.plug - will contain basic information about the plugin.
The Name refers to the class name we will use. The module refers to
Our plugin will work with Python 2 or above, and we will include a short description of the plugin in the Documentation section.
[Core] Name = HelloBot Module = hello [Python] Version = 2+ [Documentation] Description = Plugin for Errbot that responds to hello’s.
.py file is where the plugin’s logic will be. This example is similar to
the example given in section 3. Hello, World
of the Errbot plugin development guide. Take a look at it first. Then continue
from errbot import BotPlugin, botcmd class HelloBot(BotPlugin): """'Hello!' plugin for Errbot""" @botcmd def hello(self, msg, args): """Say hello to someone""" return "Hello, " + format(msg.frm)
HelloBot, along with a documentation comment wrapped in three double quotes.
defis how we start our function. This function takes three parameters:
msg(the message that the bot is responding to), and
args(anything that follows the
@botcmd, a decorator. It means that
hellois a command that we can give to the bot.
msg.frmis the identifier of the user who types the
!hellocommand in the chat.
Whew! That’s a lot to take in. If you want to test it out, run:
errbot -T -c /vagrant/config.py
and once Errbot loads, say
!hello and see what happens.
test_hello.py we will add a test to make sure that if someone says
!hello, the bot does respond with what we expect.
Remember how we used
!whoami to find out that in the interactive text mode
our username is
gbin@localhost? We can use this in our tests:
import os import hello from errbot.backends.test import testbot class TestHelloBot(object): extra_plugin_dir = '.' def test_hello(self, testbot): testbot.push_message('!hello') assert 'Hello, gbin@localhost' in testbot.pop_message()
hello.pywe import a few packages:
os- a “portable way of using operating system dependent functionality” (useful for testing)
hello- our plugin
testbot- a test instance of Errbot, to which we will pass commands and check if the output matches our expectations
test_hellofunction that uses the testbot to pass a command -
!hello- and checks that the response matches with the expected reply,
It’s time to run our test. We will use
py.test, a Python testing tool.
/vagrant/err-plugins/HelloBot/ directory, run the following command:
-v flag means that we will see verbose output of each test (rather than
. for successful test and an
F for each failed test). The
-s flag will
conveniently display the errors at the end, so that we don’t have to scroll
through all of the output capture to find them.
If all goes well, you will see something like:
===================== test session starts ============================= platform linux2 -- Python 2.7.6, pytest-2.8.2, py-1.4.30, pluggy-0.3.1 rootdir: /vagrant/err-plugins/HelloBot, inifile: plugins: pep8-1.0.6, xdist-1.13.1 collected 1 items test_hello.py::TestHelloBot::test_hello PASSED =================== 1 passed in 0.79 seconds ==========================
If any of the tests do not pass, you might see:
=========================== FAILURES ================================== _____________________TestHelloBot.test_hello __________________________ self = <test_hello.TestHelloBot object at 0x7fe22149af90> testbot = <errbot.backends.test.TestBot object at 0x7fe220ae01d0> def test_hello(self, testbot): testbot.push_message('!hello') > assert 'Hello, alimac@localhost' in testbot.pop_message() E assert 'Hello, alimac@localhost' in 'Hello, gbin@localhost' E + where 'Hello, gbin@localhost' = <bound method TestBot.pop_message of <errbot.backends.test.TestBot object at 0x7fe220ae01d0>>() E + where <bound method TestBot.pop_message of <errbot.backends.test.TestBot object at 0x7fe220ae01d0>> = <errbot.backends.test.TestBot object at 0x7fe220ae01d0>.pop_message test_hello.py:11: AssertionError ==================== 1 failed in 0.88 seconds =========================
What happened? In the example above, the test failed because the expected
Hello, alimac@localhost) did not match the response the Errbot
It will check that your line lengths are at most 79 characters, that you use the right kind of indentation, etc. The goal here is consistency.
To perform PEP 8 checks, run
py.test with the
--pep8 flag. Here is
an example of what you might see in the output:
_________________________ PEP8-check ________________________________ /vagrant/err-plugins/HelloBot/hello.py:3:1: E302 expected 2 blank lines, found 1 class HelloBot(BotPlugin): ^ /vagrant/err-plugins/HelloBot/hello.py:4:80: E501 line too long (89 > 79 characters) """Example 'Hello!' plugin for Errbot that replies to hello's to chat participants""" ^ ================= 1 failed, 2 passed in 0.90 seconds ================
As a newcomer to Python, I found these consistency checks very helpful.
How do we know what tests to write?
coverage is a tool that measures code
coverage during test execution. We can use it to pinpoint code that is not
covered by tests.
coverage, we have to modify the command we use to run tests:
coverage run --source hello -m py.test --pep8
To check the results, run
coverage report. You might see the following
Name Stmts Miss Cover ------------------------------ hello.py 4 0 100%
When coverage is less than 100%, we would like to see exactly where we are
missing tests. Run the
coverage html command. It will create a directory,
htmlcov, containing web pages showing where the tests are missing.
Navigate to the
errbot-dev/err-plugins/HelloBot/htmlcov directory on your
computer (instead of the virtual machine) and view the contents in a browser.
You should see a page like this:
You can click on each filename to see detailed results. For example, if we
test_hello function, we would see which statements need a test
highlighed in red:
Great! We now have style guide checks, tests, and a measurement of test coverage. What’s next?
Continuous Integration (CI) is a development practice that requires developers to integrate code into a shared repository several times a day. Each check-in is then verified by an automated build, allowing teams to detect problems early.
In final step of this guide we are going to set up an automated build of our project.
By testing our plugin in a disposable environment, we will make sure that there are no unexpected dependencies and that we can reliably build and test our plugin using different Python versions.
We are going to need three things:
Let’s intialize our git repository by running the
git init command in
HelloBot/ directory on our computer (instead of the virtual machine).
git status should show us all of the untracked files:
.cache/ .coverage __pycache__/ hello.plug hello.py hello.pyc htmlcov/ test_hello.py
Some of these files are directories are artifacts, or byproducts of testing
and Python byte code compilation. We can ask git to ignore them by creating a
.gitignore file with the following contents:
.cache/ .coverage __pycache__/ *.pyc htmlcov/
Now we can add the files we care about to the repository and make our first commit:
git add .gitignore hello.plug hello.py test_hello.py git commit -m "Initial commit"
Next, create a GitHub repository and add it as a remote to your local repository. Then, push the project to the remote repository (substitute the URL for your own):
git remote add origin email@example.com:alimac/err-hello.git git push -u origin master
Open a Travis CI account with your GitHub credentials (steps 1 and 2).
Make sure that the repositories have synced, and flip the switch on our
err-hello repository to a green checkmark:
The next step is to add a
.travis.yml configuration file to the repo.
In your local repo, create a file named
.travis.yml and add the following
language: python python: - 2.7 - 3.3 - 3.4 - 3.5 install: - pip install -q err pytest pytest-pep8 --use-wheel - pip install -q coverage --use-wheel script: - coverage run --source hello -m py.test --pep8 notifications: email: false
The configuration file is where we specify the language of our project, which versions of Python we want to test, and how we run the test. We also include any packages that need to be installed.
Travis builds are triggered whenever you push to the remote repository on
.travis.yml to your local repository, commit it, and push to
start the Travis build.
You should see it in action (refresh travis-ci.org page if you don’t see the build):
Click on a build job to view its log:
Now every time we push code to the GitHub repository it will automatically start four build jobs (one for each version of Python).
Our project is missing a
README.md file, but I will leave this part up to you.
Consider adding a build status badge to the README file to show that your build is passing.
You can also integrate your project with Coveralls, an online service that will show test coverage results. Like Travis CI, you can add a test coverage badge to the README indicate how well do your tests cover your codebase.
And that’s all for now. In later parts of this series, I will tackle more advanced plugin functionality by taking a look at the err-factoid and err-request-tracker plugins I built while learning about Errbot.
Was this guide useful? Did you notice any mistakes? Tell me.