Unit Test Library

jaynabonne
I have worked for a new company for a few months now, and I've been indoctrinated into "Test Driven Development" and the use of test frameworks to unit test code, to make sure it doesn't rot or develop inexplicable bugs. In my Quest work, I began to crave something similar - so I wrote one. It's not very full-featured (only three types of assertions at the moment), but it does provide a general framework you can hang tests off of. Even if you don't practice TDD, you might to be able to write automated tests for your code.

The attached zip has the simple TestLib.aslx file. There is a also a sample app included, which is from some code I've been working on. I was too lazy to try to strip it out or come up with a simpler version, so there's a bit of stuff in there (don't be scared!). But at least you can see and run some real-world tests. The main file to load is TestLib_Sample.aslx. The rest are libraries.

The idea behind this is to write automated unit tests - tests that exercise various aspects of the code. It's probably more useful when making libraries or utility functions, but I think it's also applicable to more game-wide testing - though I haven't gotten there myself yet.

To begin, you create a test suite. This is a Quest object that will contain the individual tests. Usually a test suite will test one aspect of the system (e.g. a single library, function or other area of functionality). The tests within a suite are typically related.

Example:

<object name="Some_Tests">
<setup type="script">
// Put code here to be run before each test.
</setup>
<teardown type="script">
// Put code here to be run after each test.
</teardown>
<object name="Test that something is true">
<test type="script">
// Test code goes here
</test>
</object>
<object name="Test that something else is true">
<test type="script">
// Test code goes here
</test>
</object>
</object>

The overall object "Some Tests" defines the set of tests (or the "test suite"). You can optionally have "setup" and "teardown" scripts which are, respectively, run before and after each test. This is meant to hold code common to all tests.

The child objects are the tests themselves. Each has a "test" script which is run. To run the tests, execute:

TestLib_RunTests(Some_Tests)

There is also a command included in the library which allows you to type:

runtests Some_Tests

at the Quest command prompt to run the tests. Note that, due to how Quest handles output, you will not see any output until the tests have finished running!

What does a test look like? The usual approach to a test is to set up some conditions and then assert that some other conditions hold or don't hold. For example, here is a test from ResponseLib_Tests:

<object name="GetContextResponses returns an empty list when there are no responders with responses">
<test type="script">
this.list = ResponseLib_GetContextResponses("some topics")
TestAssertEq(this, "ListCount(this.list)", 0)
</test>
</object>

The setup for the test is to call GetContextResponders. Then the test asserts a condition, in this case that ListCount(this.list) = 0.

Test output will look something like this:

> runtests ResponseLib_Tests
Running tests for ResponseLib_Tests...
You are in an EmptyRoom.
GetContextResponses returns an empty list when there are no responders with responses: success
GetContextResponders returns the player: success
GetContextResponders returns the player room: success
GetContextResponders returns a responder in the player room: success
GetContextResponses returns a response with no topic for any topic: success
GetContextResponses returns all responses matching a specified topic: success
GetContextResponses filters responses based on the group required topics: success
GetContextResponses does notfilter responses based on the group non required topics: success
===========================================
Tests run: 8
Passed: 8
Failed: 0



When tests fail, they will tell you why - that's the reason the expressions are passed as strings. Passing tests are in green and failing tests are in red. At the bottom of each test suite run is the overall statistics - run, passed, and failed.

There are three current assertions you can use in tests:

- TestAssert(context, expression): The expectation in this case is that the expression is true. It takes two parameters: the test (or test context) and the condition expression to test. A condition is passed as a string, which the test library then "eval"s. Any local variables in the test script are not accessible. So to make them visible, a context object of some kind is passed in, which becomes "this" when the condition is evaluated. I typically use the current test being run for the context since it's handy, but you can use what you like.

- TestAssertEq(context, expression, value): The expectation is that the expression, when evaluated, equals the value passed. The expression is a string, and the value is some actual value to compare the result of the expression to. The context is handled as described for TestAssert.

- TestAssertNotEq(context, expression, value): The expectation is that the expression, when evaluated, does not equal the value passed. The expression is a string, and the value is some actual value to compare the result of the expression to. The context is handled as described for TestAssert.

The sample code provides some comprehensive examples of the test library being used. There are two test suites provided, a partial one for the response library (not finished) and one for a "response" which allows one NPC to lead the PC or another NPC to a target room. The latter test suite shows some experimentation I was doing with Quest's ability to use full, spaced names to enhance the readability of the tests. For example, one test looks like this:

    <object name="LeadCharacterResponse keeps leader quiet while leading">
<test type="script">
Move follower to leader
Leader acts
Clear Dialogue
Move follower to leader
Leader acts
Assert nobody spoke
</test>
</object>

where things like "Move follower to leader" and "Leader acts" are actual function names that hide the messy details.

If you decide to try this out, please let me know how it goes, especially if you would like to use it for something that doesn't seem to quite work. I think this could be expanded to where you could automate your entire game (seems doable in theory), but that would require having the test lib not use msg directly and then provide a hook whereby msg output could be captured and asserted against.

george
Really cool Jay, I'll definitely check this out. Have you ever looked at the skein in the inform 7 IDE? I wonder if an (improved) version could work with Quest.

jaynabonne
I've seen it briefly in the first chapter of the docs, but I haven't tried it or have any in-depth knowledge. (I've made two aborted tries at Inform. They say the third time's the charm... Part of it is the documentation that comes with it. Perhaps I should look for some tutorials instead.)

I'd be interested in trying to develop anything that can make games better or easier to develop. I'll put that on my list. :)

This topic is now closed. Topics are closed after 14 days of inactivity.

Support

Forums