SES VX Ace

Highly extensible, dynamic scripts for RPG Maker VX Ace.

Tag: Unit Testing

A Case for Unit Testing

by Solistra

Introduction

Unit testing — while a major part of professional Ruby development — has long been ignored within the RPG Maker community since XP’s inclusion of Ruby scripting as a core feature. Unfortunately, the embedded Ruby versions included in all of the recent RPG Makers have not allowed usage of the Ruby standard library or external Ruby gems — which means that RPG Maker has never included a unit testing framework for use by Ruby programmers writing scripts.

The SES Test Case framework for RPG Maker VX Ace was created to solve that problem by providing a relatively simple unit testing framework that can be used from within RPG Maker VX Ace with support for assertions, specifications, mock objects, method stubs, and the ability to capture information written to Ruby’s standard output. With SES Test Case — and the information provided by this tutorial — writing unit tests for your scripts is simple.

What is Unit Testing?

To put it succinctly, unit testing is a way to test individual portions of your code (the units) and ensure that they operate as you intend them to. As such, unit testing is often used to provide a set of expectations about what your code should be doing and asserts that your code is actually doing what you wanted.

However, discussing unit testing in general and presenting the benefits of it are beyond the scope of this tutorial — entire books have been written on the subject, and there are an incredible number of resources online that you can consult for more information about the general idea. This guide is intended to help you get started using the SES Test Case framework and assist you in writing good tests for it.

The SES Test Case Framework

By design, the SES Test Case framework supports testing with both assertion- and expectation-style test cases. In this tutorial, we’ll begin with assertion-style testing and build our knowledge until we work on using expectation-style tests.

Assertion-Style Testing

Defining a test case for use with the framework is simple, as is defining methods that create individual units to be tested. All test cases are subclasses of the SES::Test::Case class (or SES::Test::Spec, but we’ll get to that later), and each unit is a defined instance method beginning with test_. Given that knowledge, let’s create a simple test case with one unit that will always pass:

class ExampleTest < SES::Test::Case
  def test_always_passes
    1 < 2
  end
end

The above code creates a new test case named “ExampleTest” with one unit: test_always_passes. With the code above, the unit will always pass when tested — assuming, of course, that one remains less than two. If you place this code below the SES Test Framework in the Script Editor and begin a new test game, you should receive this output from SES Test Case in the RGSS Console:

Test Case: ExampleTest
  [ OK ] ExampleTest always passes

  1 tests, 1 passed, 0 skipped, 0 failed, 0 errors

Ran 1 tests defined in 1 cases.
Test Summary: .
Elapsed Time: 0.01 second(s)

Note: If you didn’t receive any output, please ensure that the AUTO_RUN constant is set to “true” in the SES Test Case script and that you have the RGSS Console enabled.

Adding to the previous test case, let’s add a unit that will fail, one that will be skipped, and one that will raise an error so that we can see what each of those look like when the test case is run.

class ExampleTest < SES::Test::Case
  def test_always_passes
    1 < 2
  end
  
  def test_always_fails
    1 > 2
  end
  
  def test_always_skips
    skip
  end
  
  def test_always_errors
    'Error'.pop
  end
end

Running the newly written test case, you should receive this output:

Test Case: ExampleTest
  [ OK ] ExampleTest always passes
  [FAIL] ExampleTest always fails
  [SKIP] ExampleTest always skips
  [ERR!] ExampleTest always errors
         -- undefined method `pop' for "Error":String

  4 tests, 1 passed, 1 skipped, 1 failed, 1 errors

Ran 4 tests defined in 1 cases.
Test Summary: .FSE
Elapsed Time: 0.01 second(s)

As you can see, each unit defined is reported with a descriptive string when run through the SES Test Case framework. For units that pass, you’ll receive an [ OK ] message; for tests that fail, a [FAIL] message; and so on. In addition to this, the Test Summary shown at the bottom provides a succinct overview of all units that were run. Likewise, . signifies a passing unit, F a failing one, and… well, you get the idea.

Note: Notice that when an error is encountered during a test that the error message raised is written to the console directly underneath the test unit that raised it — allowing you to debug errors that may occur while writing units.

Of course, in reality, you won’t be testing whether or not one is less than two — more than likely, you’ll want to test that a particular class or module that you wrote functions as you intend for it to. Fortunately, the SES Test Framework only runs instance methods beginning with test_ as units, allowing you to write any additional helper methods you need in order to facilitate your testing. As an example, let’s write a simple test case for the Game_Actor class:

class GameActorTest < SES::Test::Case
  def name
    'Game_Actor'
  end
  
  def subject
    @subject ||= Game_Actor.new(subject_id)
  end
  
  def subject_id
    1
  end
  
  def test_initializes_with_given_id
    subject.id == subject_id
  end
end

When you run this new test case, you should receive the following output for it:

Test Case: Game_Actor
  [ OK ] Game_Actor initializes with given id

  1 tests, 1 passed, 0 skipped, 0 failed, 0 errors

If you’re observant, you may have noticed that I slipped something else in there besides simple helper methods — the name method. If you define a name method for your test case, the return value of that method will be converted into a string and used as the name of your test case by the SES Test Case framework. This allows you to create far more descriptive names for your test cases than, for example, “GameActorTest.”

In addition to the name method, there are two other methods which you can use with specific meaning: setup and teardown. The method body defined in the setup method is run before each defined unit, and the method body of teardown is run immediately afterwards. As an example, let’s rewrite our test case for Game_Actor:

class GameActorTest < SES::Test::Case
  def name
    'Game_Actor'
  end
  
  def setup
    @subject_id = 1
    @subject = Game_Actor.new(@subject_id)
  end
  
  def test_initializes_with_given_id
    @subject.id == @subject_id
  end
end

If you run this code, you should receive the exact same output that you did before — but there are some major differences. Before, we defined an instance method named subject which assigned the @subject instance variable to an instance of Game_Actor if @subject was nil; now, the setup method creates a new Game_Actor instance for each unit. In addition to this, we defined the @subject_id instance variable in setup and used it in our unit test rather than having the subject’s ID in yet another method.

Of course, there are also bound to be cases where you simply aren’t finished writing your test case and want it to be skipped entirely when the SES Test Case framework is run (don’t worry, I won’t tell anyone that your tests aren’t ready yet). Fortunately, this is also easy to do… all you have to do is call the skip! method in the body of your test case’s class.

class GameActorTest < SES::Test::Case
  skip!
end

This will cause the entire case to be completely ignored — it won’t show up in the output, and the case won’t be run at all. And with that said, you’ve received the whirlwind tour of assertion-style testing with the SES Test Case framework — congratulations. You certainly know enough now to test your code effectively, but there’s still a lot more to learn.

Expectation-Style Testing

Now that you know how to perform assertion-style tests, we can start covering how to write tests in the expectation style. While the results are the same, writing expectation-style tests tends to be a bit more terse and descriptive at the same time — rather than defining methods, you write out your expectations about how the code you are testing should behave.

In the SES Test Case framework, all expectation-style tests (also known as “specifications” or the more colloquial “specs”) are subclasses of the SES::Test::Spec class (which is actually a subclass of SES::Test::Case itself). The primary difference between the two is that the SES::Test::Spec class includes its own domain-specific language for writing test cases. Having said that, let’s jump into a specification and see what it looks like:

class ExampleSpec < SES::Test::Spec
  it 'always passes' do 1 < 2 end
end

Running that specification, you should receive essentially the same output as your first assertion-style test case:

Test Case: ExampleSpec
  [ OK ] ExampleSpec always passes

  1 tests, 1 passed, 0 skipped, 0 failed, 0 errors

As you can see, though, the expectation style of writing units is (arguably) a bit more readable as a sentence and certainly more indicative of your expectations. After all, “it always passes.” Of course, specifications really wouldn’t be all that useful if all they did was create units, so let’s look at some more expectation-style test case code.

class GameActorSpec < SES::Test::Spec
  describe 'Game_Actor' do Game_Actor.new(subject_id) end
  let :subject_id do 1 end
  
  it 'initializes with the given id' do
    subject.id == subject_id
  end
end

Well, we introduced a few new things there. Most notably, we introduced the describe class method, which names your test case and creates a subject method for you automatically — you pass the name you wish to give your test case as the argument and supply the subject by passing a block (the return value of the block becomes your subject).

In addition, we also introduced the let class method — essentially, this method creates reader methods which return the return value of the block that you pass to let. In the example code above, you can see that let defines a reader method named subject_id which returns 1 and is used by the describe method to generate a new Game_Actor instance with subject_id as the argument to Game_Actor.new.

Of course, you could define the setup and teardown methods to run code before and after each unit test in a plain SES::Test::Case — and you can also do so in a specification with the before and after class methods. To recreate the example we did with assertion-style tests and Game_Actor, we’d do this:

class GameActorSpec < SES::Test::Spec
  describe 'Game_Actor' do Game_Actor.new(subject_id) end
  let :subject_id do 1 end
  before { @_subject = Game_Actor.new(subject_id) }
  
  it 'initializes with the given id' do
    subject.id == subject_id
  end
end

There’s just one new thing to note here — in the before method, we assign a new instance of Game_Actor the @_subject instance variable, not @subject. This is because specifications automatically create instance variables starting with “@_” when the let method is used, and — you guessed it — the describe method uses let to create your subject. This way, the subject method created by let actually references the @_subject instance variable. Remember that — let creates instance variables starting with “@_”.

With that, we’ve covered both assertion- and expectation-style testing. Remember, the SES::Test::Spec class is just a subclass of SES::Test::Case, so if you make use of the specification class, you can mix and match the styles as much as you like.

Conclusion

Hopefully this tutorial has informed you about how to create test cases and specifications for use with the SES Test Case framework. Now it’s up to you to write good tests for your code — the benefits are vast, and the effort is minimal when integrated with your normal script-writing workflow.

However, there is still quite a bit of the SES Test Case framework that this tutorial did not cover. For any further information about the framework and its capabilities, consult the inline script documentation for the framework — it’s very extensive and meant to be as helpful as possible.

Good luck with your tests!

SES Test Case

by Solistra

SES Test Case results.

Summary

This script provides a simple unit testing framework for RPG Maker VX Ace with very simple expectation-style formatting for test cases. Essentially, this script allows you to use test-driven development from within RPG Maker VX Ace without depending on an external Ruby installation. This is primarily a scripter’s tool.

Features

  • Unit testing capabilities for RPG Maker VX Ace.
  • Tests may be written externally or within the Ace Script Editor.
  • Supports both assertion- and expectation-style tests.
  • Assertions and expectations may be mixed freely.
  • Support for simple mock objects.
  • Method stubbing, allowing tests for unpredictable methods.
  • Capturing of standard output as a string.

Script

This script is available from the SES source repository. The specific file to download is ‘lib/test-case.rb’.

Tests for the framework are available from ‘test/’ in the source repository. The tests for SES Test Case may be placed under the core framework script in the VX Ace editor, or may be stored as external tests in ‘System/Tests’ (relative to your project’s root directory).

Installation

Place this script below the SES Core (v2.0) script (if you are using it) or the Materials header, but above all other custom scripts. This script does not require the SES Core (v2.0), but it is recommended.

Place this script above the SES Console if you are using it.

License

This script is made available under the terms of the MIT Expat license.

Design a site like this with WordPress.com
Get started