Testing bots¶
Why should you test your bots¶
When you start your first bot with maybe one or two files containing dialogs, it may not seem necessary to create a test for that bot. But as your bot grows, the complexity of your bot grows at the same time. This growing complexity also means that there is a higher chance of unexpected things happening when a small change is made. A way to manage this growth in complexity is by creating unit-tests. Each unittest describes a part of the code that is in your bot. The test ensures that when you change code internally, nothing changes for the person talking to the bot.
Test-driven bot development, an example.¶
Test-driven development (TDD) is a development style where the code is driven by (you guessed it): tests. This means that we usually start writing tests before we write the functionality in code. We will create a bot that can perform addition and subtraction written in words.
Instructions are given to the bot in the following manner: "add 10 and 20", "subtract 5 from 10". With this, we have enough to start creating tests for our bot, and from these tests, we will create the actual bot. Tests do not have to be written in the same file, they can be separated into multiple files.
test "the bot greets us with its name" do
expect "Hello I am CompuTron"
expect "What do you want to compute"
end
test "adding two numbers" do
expect "What do you want to compute?"
say "add 5 and 5"
expect "10"
# addition of negative numbers
say "add -5 and -10"
expect "'-15'"
say "add 5.5 and 5"
expect "'10.5'"
end
test "subtracting two numbers" do
expect "What do you want to compute?"
say "subtract 5 of 50"
expect "45"
say "subtract 100 of 50"
expect "'-50'"
# test if we can subtract negative numbers
say "subtract -50 of 50"
expect "100"
end
test "not yet trained" do
expect "What do you want to compute?"
say "multiply 4 with 2"
expect "not trained"
end
The code for the test cases is also written in Bubblescript. Instead of writing
a dialog
{{do..end}} block, we use test do…
to define a test-case.
The string that follows the test
statement
describes what we are testing for, thus giving more context.
Inside the test
block we can use the
expect
statement to define what we expect
the bot to say at that point. The expect
statement is exhaustive, meaning it
will wait for the bot to stop saying things and then check whether the expected
thing was received. At the moment, only say
and ask
can be tested, statements
like show
and events
will be supported in the future.
Running the tests¶
We can run all the tests with Ctrl+Alt+T or by clicking the "Run All" button in the "Tests" panel in the editor. To only run the test in one file, or even a single test case, you can click on the "play" button to the right of the item in the tree navigation.
Below is the implementation of the calculation bot according to the tests that were written for it. The regex in the @number constant matches on negative numbers, and on floating point numbers. When we run all the tests in the studio, they should now all have a green checkmark in front of them.
Try interacting with the bot yourself!
To repeat the last run of the last testcase, press ctrl-alt-T, or cmd-alt-T on mac.
Writing tests¶
To start creating tests, you need to first create the test script to put them in. These test scripts are separate Bubblescript files, which are not used in your normal bot, but only evaluated while the tests are running.
In the build area, click Add script -> Test case; enter the name of the test, and click Add. You now have a file in which you can write your tests.
A best-practice is to organise your tests in folders; to "magically" put your test file in a folder, add the folder name in slashes to the file name. For instance, to put your 'user_tests' file in the 'tests' folder, call the file
tests/user_tests
.
Creating tests by using the preview window¶
While you are developing your bot, you can save the conversation
you've had with a bot so far into a test file. Do this by switching to
the Tests panel while your bot is running in the preview, and then
click the "Create test" button. A test case will be created and it
will be put in the file called tests/auto
.
Creating tests based on conversations¶
We created the tests above by hand, which is something you would do if
your bot has not been interacting with people yet. But, if your bot
has had interactions with people, you can navigate to a conversation
in the Inbox part of the studio and click the "Create test" button on
the right panel. This will take the conversation and convert it to a
test. A test case will be created and it will be put in the file
called tests/auto
.
This way, you can refactor parts of your bot while making sure that this conversation is still a valid one.
Testing quick replies¶
Sometimes you want to check if the bot has set any quick replies after an
ask
. You can do so by inspecting the local variable last_quick_replies
,
which always contains the last quick replies that were sent to the script.
For example, you can test the quick replies of this script:
dialog main do
ask "Do you want it, yes or no?", expecting: ["yes", "no"]
end
Using a test case like this:
test "quick_replies" do
# wait for the `ask`
expect "yes or no"
# last_quick_replies has been set here
assert ["yes", "no"] == last_quick_replies
assert contains(last_quick_replies, "yes")
end
Submitting data in tests¶
Oftentimes, you'll want to request specific data in an ask
, such as a location
or an image. These interactions can be tested as well, by using submit
in your
tests to send example data and continuing the dialog. For example, imagine
writing a bot where you want to request an image from the user in order to
create a damage claim. You can ask for an image as follows:
dialog request_image do
image = ask "Please upload a photo of the damaged item", expecting: :image
say "We're processing your request!"
# perform make_a_damage_claim
end
You can test this code using submit
like so:
test "a damage claim is created after uploading an image", dialog: request_image do
expect "Please upload a photo of the damaged item"
submit attachment(type: "image", url: "https://example.com")
expect "We're processing your request"
# .. assert that a damage claim was created here
end
Here we're using the attachment
builtin function to create a fake image, and
using that to continue the dialog so we can test the code that comes after the
ask
. There are builtin helpers for constructing various types of messages:
submit attachment(type: "image", url: "https://example.com")
submit location(lon: 20, lat: 40)
# Same as `say`
submit message("hello")
# Or with more complex data, such as when using input_method:
submit message("hello", data: "bye")
This can also be used to test dialogs that have intent triggers on them:
@hello intent(match: "hello")
dialog trigger: @hello do
say "hello back"
end
With the test script:
test "it responds nicely to hello" do
submit message("", intent: @hello)
expect("hello back")
end
Here, we overwrite the intent from the message and use that to manually trigger the parts of the code that we want to trigger.
Testing input methods¶
Besides asking for a :location
, :image
, or similar, you can also ask for
more complex input methods such as with forms. These too can be tested with
submit
. For example:
dialog main do
choices = input_method("item_picker",
caption: "Please select",
mode: "multiple",
items: [
[title: "Frontend developer", subtitle: "React, HTML, Javascript", value: "frontend"],
[title: "Backend developer", subtitle: "Elixir, Erlang, bash…", value: "backend"],
[title: "Marketing manager", subtitle: "Hubspot and Facebook marketing", value: "marketing"]
])
ask "Which positions are you interested in?", expecting: choices
say "Looking up positions for #{answer.text}"
end
test "it asks for a desired position" do
choices = input_method("item_picker",
caption: "Please select",
mode: "multiple",
items: [
[title: "Frontend developer", subtitle: "React, HTML, Javascript", value: "frontend"],
[title: "Backend developer", subtitle: "Elixir, Erlang, bash…", value: "backend"],
[title: "Marketing manager", subtitle: "Hubspot and Facebook marketing", value: "marketing"]
])
expect choices
submit message("Frontend developer", data: "frontend", type: "item_picker")
expect "Looking up positions for Frontend developer"
end
Testing tasks¶
The tasks that are created in your bot are also available in the tests.This
means that you can run them by using perform
in your test script. All the
variables that the task might need, including constants are also available in
your test script. The assert
statement can be used to test whether a task has
done its job correctly or not.
The following example demonstrates how to test tasks:
main:
dialog main do
say "Hi"
ask "What is you age?"
perform set_age
end
task set_age do
user.age = answer
end
task birthday do
user.age = user.age + 1
end
tasks_test.test:
test "users age is set correctly" do
answer = 42
perform set_age
assert user.age == 42
end
test "age is incremented by birthday task" do
user.age = 42
perform birthday
assert user.age = 43
end
Because the tasks we test require certain variables to be available, we have to set them explicitly in the test script. If these variables would come from constant, the values for these variables would be available automatically.
Testing tags¶
You can use expect tag: "tagname"
to check that a conversation is setting a certain tag.
dialog main do
tag "hello"
untag "goodbye"
end
test "the conversation is tagged" do
expect tag: "hello"
# and the untagging as well:
expect untag: "goodbye"
end
Testing closing of conversations¶
You can use expect :close
to check that a conversation is closed using the close
statement:
dialog main do
close
end
test "the conversation is immediately closed" do
expect :close
end