The Mud Connector

Author Topic: Testing in LP muds (same ideas are applicable to any software project)  (Read 1933 times)

Maeglin - RealmsMUD

  • New to TMC
  • *
  • Posts: 12
    • View Profile
    • Realmsmud
One of the (in my opinion) critical, but far too often overlooked aspects of software development lies in quality assurance - specifically, ensuring that that software you've written does what it's supposed to do and (even more importantly) when changes are made, things aren't inadvertently broken (ie: regression testing).

When I started rewriting the lib for RealmsMUD, one of the first things I did was implement a simple testing framework. Initially, I would run the tests manually whilst logged in, but it quickly became apparent that long-term, that was an untenable approach. I decided that it'd be nice to execute the tests outside of the running mud. Version 1 (which I'd suggest as a starting point for anyone who wants to try this) of this concept was to simply make use of preload_objects in the driver and call exit when this executed. Then, for any tests, I'd set up the init file with the list of everything I wanted to execute. The driver would load them (and in master.c, I changed the preload stuff to also call executeTests on anything that inherited my test fixture.). The one down side to this is that you will almost certainly have to give your driver's eval limit a hefty boost or it's going to puke if you have any meaningful number of tests in a single file.

I've since hacked the driver (well, my heavily hacked ldmud-3.5.0 driver) so that it can take a single file and evaluate it without having to fall to preload on master.c. I also set up a Jenkins server and set up some hooks in my revision control system such that any time source code is checked in, all of the mudlib's tests are executed and if they all pass, then an option to deploy the build to the live mud is made available.

What I'm supplying should be looked at with the following caveat: I've only tested this on ldmud 3.5.0 built in compat mode. Everything being done should be generic enough to work anywhere, but I make no promises.

Anyway, here's the test fixture:

Code: [Select]
//*****************************************************************************
// Copyright (c) 2017 - Allen Cummings, RealmsMUD, All rights reserved. See
//                      the accompanying LICENSE file for details.
//*****************************************************************************
#include <functionlist.h>

string Pass = "[  PASSED  ] ";
string Fail = "[  FAILED  ] ";

int CurrentTestPassed = 0;
int AnyFailure = 0;
string *ignoreList = ({ "__INIT", "Init", "Setup", "CleanUp" });

/////////////////////////////////////////////////////////////////////////////
public void Init()
{
}

/////////////////////////////////////////////////////////////////////////////
public void Setup()
{
}

/////////////////////////////////////////////////////////////////////////////
public void CleanUp()
{
}

/////////////////////////////////////////////////////////////////////////////
public int executeTests()
{
    Init();
    mixed *tests = functionlist(this_object(), RETURN_FUNCTION_NAME | NAME_INHERITED);
    tests -= ignoreList;

    debug_message(sprintf("\nTesting %s\n", file_name()));
    foreach(string test in tests)
    {
        Setup();
        CurrentTestPassed = 1;

        call_other(this_object(), test);
        debug_message(sprintf("%s %s\n", CurrentTestPassed ? Pass : Fail, test));
        CleanUp();
    }
    debug_message(sprintf("Test executed: %s -> %s\n", file_name(),
        AnyFailure ? Fail : Pass));

    return AnyFailure;
}

/////////////////////////////////////////////////////////////////////////////
public void validateExpect(mixed val1, mixed val2, string msg)
{
    if (!CurrentTestPassed)
    {
        AnyFailure = 1;
        debug_message(Fail + (stringp(msg) ? msg : "") + " -> Actual: " + val2 +
            ", Expected: " + val1 + "\n");
    }
}

/////////////////////////////////////////////////////////////////////////////
public int sortArray(mixed a, mixed b)
{
    string compA;
    string compB;

    if (mappingp(a) && mappingp(b))
    {
        compA = this_object()->convertDataToString(a);
        compB = this_object()->convertDataToString(b);
    }
    else
    {
        compA = to_string(a);
        compB = to_string(b);
    }

    return compA > compB;
}

/////////////////////////////////////////////////////////////////////////////
public string convertDataToString(mixed data)
{
    string ret = "";

    if (objectp(data))
    {
        ret += program_name(data);
    }
    else if (pointerp(data) && sizeof(data))
    {
        ret += "({ ";
        data = sort_array(data, "sortArray");
        foreach(mixed element in data)
        {
            ret += convertDataToString(element) + ", ";
        }
        ret += "})";
    }
    else if (mappingp(data))
    {
        ret += "([ ";
        mixed *indices = sort_array(m_indices(data), "sortArray");
        foreach(mixed index in indices)
        {
            ret += convertDataToString(index) + ": " + convertDataToString(data[index]) + ", ";
        }
        ret += "])";
    }
    else
    {
        ret += to_string(data);
    }
    return ret;
}

/////////////////////////////////////////////////////////////////////////////
public varargs void ExpectEq(mixed val1, mixed val2, string msg)
{
    string parsedVal1 = convertDataToString(val1);
    string parsedVal2 = convertDataToString(val2);

    CurrentTestPassed = parsedVal1 == parsedVal2;
    validateExpect(parsedVal1, parsedVal2, msg);
}

/////////////////////////////////////////////////////////////////////////////
public varargs void ExpectSubStringMatch(string val1, string val2, string msg)
{
    CurrentTestPassed = sizeof(regexp(({ val2 }), val1));
    validateExpect(val1, val2, msg);
}

/////////////////////////////////////////////////////////////////////////////
public varargs void ExpectNotEq(mixed val1, mixed val2, string msg)
{
    string parsedVal1 = convertDataToString(val1);
    string parsedVal2 = convertDataToString(val2);

    CurrentTestPassed = parsedVal1 != parsedVal2;
    validateExpect(parsedVal1, parsedVal2, msg);
}

/////////////////////////////////////////////////////////////////////////////
public varargs void ExpectTrue(mixed val1, string msg)
{
    CurrentTestPassed = val1;
    validateExpect("true", "false", msg);
}

/////////////////////////////////////////////////////////////////////////////
public varargs void ExpectFalse(mixed val1, string msg)
{
    CurrentTestPassed = !val1;
    validateExpect("false", "true", msg);
}

Maeglin - RealmsMUD

  • New to TMC
  • *
  • Posts: 12
    • View Profile
    • Realmsmud
Building tests is pretty straightforward, but since it's caused some confusion with other wizzes on Realms, I figured I'd give an explanation of what you should do.

Try to avoid testing more than one thing in any given method and make sure you name you test methods in a way that it's immediately obvious what you're doing. Good: QuestIsInProgressReturnsFalseWhenQuestHasBeenCompleted, Bad: ThingDoesStuff, Worst: X

Make use of setup methods to keep your actual tests succinct. When you make tests, you need to set up a bunch of objects so that you can properly exercise them. If you need to clone a player, give them a sword, clone a monster, and make them fight, don't "muddy" the test with that - put it in a separate method and call it from the test.

You should have system tests that encompass interactions between many objects. You should also have unit tests that test a single thing in a vacuum. These unit tests should use fake/facade items "around them" to limit what you're testing and to simplify the test.

Here's a couple example tests. This first one exercises the quest module in the player:

Due to post size limits, quite a few of the tests in this fixture were cut out...

Code: [Select]
inherit "/lib/tests/framework/testFixture.c";
#include "/lib/include/inventory.h"

object Quests;
object QuestItem;

/////////////////////////////////////////////////////////////////////////////
void Setup()
{
    Quests = clone_object("/lib/realizations/player");
    Quests->Name("Bob");

    QuestItem = clone_object("/lib/tests/support/quests/fakeQuestItem.c");
}

/////////////////////////////////////////////////////////////////////////////
void CleanUp()
{
    destruct(Quests);
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsInProgressReturnsFalseWhenQuestIsInvalid()
{
    ExpectFalse(Quests->questIsInProgress("bad/quest.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsInProgressReturnsFalseWhenQuestIsNotStarted()
{
    ExpectFalse(Quests->questIsInProgress("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsInProgressReturnsTrueWhenQuestHasBeenStarted()
{
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectTrue(Quests->questIsInProgress("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsInProgressReturnsFalseWhenQuestHasBeenCompleted()
{
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));

    // Progress the quest to completion
    ExpectTrue(QuestItem->receiveEvent(Quests, "meetTheKing"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "ignoreTheKing"));

    ExpectFalse(Quests->questIsInProgress("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsActiveReturnsFalseWhenQuestIsInvalid()
{
    ExpectFalse(Quests->questIsActive("bad/quest.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsActiveReturnsFalseWhenQuestIsNotStarted()
{
    ExpectFalse(Quests->questIsActive("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsActiveReturnsTrueWhenQuestHasBeenActivated()
{
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectTrue(Quests->questIsActive("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsActiveReturnsFalseWhenQuestHasBeenDeactivated()
{
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectTrue(Quests->deactivateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(Quests->questIsActive("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsActiveReturnsTrueWhenQuestHasBeenReactivated()
{
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectTrue(Quests->deactivateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(Quests->questIsActive("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectTrue(Quests->activateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectTrue(Quests->questIsActive("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsActiveReturnsFalseWhenQuestHasBeenCompleted()
{
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));

    // Progress the quest to completion
    ExpectTrue(QuestItem->receiveEvent(Quests, "meetTheKing"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "ignoreTheKing"));

    ExpectFalse(Quests->questIsActive("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsCompletedReturnsFalseWhenQuestIsInvalid()
{
    ExpectFalse(Quests->questIsCompleted("bad/quest.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsCompletedReturnsFalseWhenQuestIsNotStarted()
{
    ExpectFalse(Quests->questIsCompleted("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsCompletedReturnsFalseWhenQuestHasBeenStartedButNotCompleted()
{
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(Quests->questIsCompleted("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsCompletedReturnsTrueWhenQuestHasBeenCompleted()
{
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));

    // Progress the quest to completion
    ExpectTrue(QuestItem->receiveEvent(Quests, "meetTheKing"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "ignoreTheKing"));

    ExpectTrue(Quests->questIsCompleted("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestStateReturnsFalseWhenQuestIsInvalid()
{
    ExpectFalse(Quests->questState("bad/quest.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestStateReturnsFalseWhenQuestIsNotStarted()
{
    ExpectFalse(Quests->questState("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestStateReturnsCorrectQuestState()
{
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectEq("meet the king", Quests->questState("lib/tests/support/quests/fakeQuestItem.c"));

    ExpectTrue(QuestItem->receiveEvent(Quests, "meetTheKing"));
    ExpectEq("met the king", Quests->questState("lib/tests/support/quests/fakeQuestItem.c"));

    ExpectTrue(QuestItem->receiveEvent(Quests, "serveTheKing"));
    ExpectEq("serve the king", Quests->questState("lib/tests/support/quests/fakeQuestItem.c"));

    ExpectTrue(QuestItem->receiveEvent(Quests, "maybeNobodyWillNotice"));
    ExpectEq("king is dead", Quests->questState("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void ActiveQuestsReturnsCorrectListOfQuests()
{
    ExpectEq(({}), Quests->activeQuests());

    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectEq(({ "lib/tests/support/quests/fakeQuestItem.c" }), Quests->activeQuests());

    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/mockQuest.c"));
    ExpectEq(({ "lib/tests/support/quests/fakeQuestItem.c", "lib/tests/support/quests/mockQuest.c" }),
        Quests->activeQuests());

    ExpectTrue(Quests->deactivateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectEq(({ "lib/tests/support/quests/mockQuest.c" }), Quests->activeQuests());

    ExpectTrue(Quests->activateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectEq(({ "lib/tests/support/quests/fakeQuestItem.c", "lib/tests/support/quests/mockQuest.c" }),
        Quests->activeQuests());
}

/////////////////////////////////////////////////////////////////////////////
void CompletedQuestsReturnsCorrectListOfQuests()
{
    ExpectEq(({}), Quests->activeQuests());

    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectEq(({ }), Quests->completedQuests());

    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/anotherQuest.c"));
    ExpectEq(({ "lib/tests/support/quests/anotherQuest.c" }), Quests->completedQuests());

    ExpectTrue(Quests->deactivateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectEq(({ "lib/tests/support/quests/anotherQuest.c" }), Quests->completedQuests());

    ExpectTrue(QuestItem->receiveEvent(Quests, "meetTheKing"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "ignoreTheKing"));
    ExpectEq(({ "lib/tests/support/quests/fakeQuestItem.c", "lib/tests/support/quests/anotherQuest.c" }),
        Quests->completedQuests());
}

/////////////////////////////////////////////////////////////////////////////
void QuestsInProgressReturnsCorrectListOfQuests()
{
    ExpectEq(({}), Quests->activeQuests());

    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectEq(({ "lib/tests/support/quests/fakeQuestItem.c" }), Quests->questsInProgress());

    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/mockQuest.c"));
    ExpectEq(({ "lib/tests/support/quests/fakeQuestItem.c", "lib/tests/support/quests/mockQuest.c" }),
        Quests->questsInProgress());

    ExpectTrue(Quests->deactivateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectEq(({ "lib/tests/support/quests/fakeQuestItem.c", "lib/tests/support/quests/mockQuest.c" }),
        Quests->questsInProgress());

    ExpectTrue(QuestItem->receiveEvent(Quests, "meetTheKing"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "ignoreTheKing"));
    ExpectEq(({ "lib/tests/support/quests/mockQuest.c" }), Quests->questsInProgress());
}

/////////////////////////////////////////////////////////////////////////////
void ActivateQuestReturnsFalseWhenQuestIsInvalid()
{
    ExpectFalse(Quests->activateQuest("bad/quest.c"));
    ExpectFalse(Quests->questIsActive("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void ActivateQuestReturnsFalseWhenQuestIsNotStarted()
{
    ExpectFalse(Quests->activateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(Quests->questIsActive("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void ActivateQuestReturnsTrueWhenQuestHasBeenStarted()
{
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectTrue(Quests->questIsActive("lib/tests/support/quests/fakeQuestItem.c"), "begun quest is active");
    ExpectTrue(Quests->deactivateQuest("lib/tests/support/quests/fakeQuestItem.c"), "quest deactivated");
    ExpectFalse(Quests->questIsActive("lib/tests/support/quests/fakeQuestItem.c", "deactivated quest returns not active"));
    ExpectTrue(Quests->activateQuest("lib/tests/support/quests/fakeQuestItem.c", "activate the quest"));
    ExpectTrue(Quests->questIsActive("lib/tests/support/quests/fakeQuestItem.c"), "re-activated quest is active");
}

/////////////////////////////////////////////////////////////////////////////
void ActivateQuestReturnsFalseWhenQuestHasBeenCompleted()
{
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "meetTheKing"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "ignoreTheKing"));

    ExpectFalse(Quests->activateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(Quests->questIsActive("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void BeginQuestFiresOnQuestStartedEvent()
{
    object events = clone_object("/lib/tests/support/events/questEventsSubscriber.c");
    Quests->registerEvent(events);
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectEq(([ "onQuestStarted":"lib/tests/support/quests/fakeQuestItem.c"]),
        events->quests());
}

/////////////////////////////////////////////////////////////////////////////
void BeginQuestFiresProperEventsWhenInitialStateIsCompletionState()
{
    object events = clone_object("/lib/tests/support/events/questEventsSubscriber.c");
    Quests->registerEvent(events);
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/anotherQuest.c"));
    ExpectEq((["onQuestStarted":"lib/tests/support/quests/anotherQuest.c",
        "onQuestCompleted": "lib/tests/support/quests/anotherQuest.c",
        "onQuestSucceeded": "lib/tests/support/quests/anotherQuest.c"]),
        events->quests());
}

/////////////////////////////////////////////////////////////////////////////
void AdvanceQuestStateFiresEachTimeStateAdvances()
{
    object events = clone_object("/lib/tests/support/events/questEventsSubscriber.c");
    Quests->registerEvent(events);

    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(member(events->quests(), "onQuestAdvancedState"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "meetTheKing"));
    ExpectTrue(member(events->quests(), "onQuestAdvancedState"));

    events->clearEvents();
    ExpectFalse(member(events->quests(), "onQuestAdvancedState"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "ignoreTheKing"));
    ExpectTrue(member(events->quests(), "onQuestAdvancedState"));
}

/////////////////////////////////////////////////////////////////////////////
void AdvanceQuestToFailStateFiresOnQuestCompletedAndOnQuestFailed()
{
    object events = clone_object("/lib/tests/support/events/questEventsSubscriber.c");
    Quests->registerEvent(events);

    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(member(events->quests(), "onQuestCompleted"));
    ExpectFalse(member(events->quests(), "onQuestFailed"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "meetTheKing"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "ignoreTheKing"));
    ExpectTrue(member(events->quests(), "onQuestCompleted"));
    ExpectTrue(member(events->quests(), "onQuestFailed"));
}

/////////////////////////////////////////////////////////////////////////////
void AdvanceQuestToFailStateDoesNotFireOnQuestSucceeded()
{
    object events = clone_object("/lib/tests/support/events/questEventsSubscriber.c");
    Quests->registerEvent(events);

    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(member(events->quests(), "onQuestSucceeded"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "meetTheKing"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "ignoreTheKing"));
    ExpectFalse(member(events->quests(), "onQuestSucceeded"));
}

/////////////////////////////////////////////////////////////////////////////
void AdvanceQuestToSuccessStateFiresOnQuestCompletedAndOnQuestSucceeded()
{
    object events = clone_object("/lib/tests/support/events/questEventsSubscriber.c");
    Quests->registerEvent(events);

    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(member(events->quests(), "onQuestCompleted"));
    ExpectFalse(member(events->quests(), "onQuestSucceeded"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "meetTheKing"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "serveTheKing"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "hailToTheKing"));
    ExpectTrue(member(events->quests(), "onQuestCompleted"));
    ExpectTrue(member(events->quests(), "onQuestSucceeded"));
}

/////////////////////////////////////////////////////////////////////////////
void AdvanceQuestToSuccessStateDoesNotFireOnQuestFailed()
{
    object events = clone_object("/lib/tests/support/events/questEventsSubscriber.c");
    Quests->registerEvent(events);

    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(member(events->quests(), "onQuestFailed"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "meetTheKing"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "serveTheKing"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "hailToTheKing"));
    ExpectFalse(member(events->quests(), "onQuestFailed"));
}

/////////////////////////////////////////////////////////////////////////////
void ActivateQuestFiresOnQuestActivatedWhenItSucceeds()
{
    object events = clone_object("/lib/tests/support/events/questEventsSubscriber.c");
    Quests->registerEvent(events);

    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(member(events->quests(), "onQuestActivated"));
    ExpectTrue(Quests->activateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectTrue(member(events->quests(), "onQuestActivated"));
}

/////////////////////////////////////////////////////////////////////////////
void ActivateQuestDoesNotFireOnQuestActivatedWhenItFails()
{
    object events = clone_object("/lib/tests/support/events/questEventsSubscriber.c");
    Quests->registerEvent(events);

    ExpectFalse(member(events->quests(), "onQuestActivated"));
    ExpectFalse(Quests->activateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(member(events->quests(), "onQuestActivated"));
}

/////////////////////////////////////////////////////////////////////////////
void DeactivateQuestFiresOnQuestActivatedWhenItSucceeds()
{
    object events = clone_object("/lib/tests/support/events/questEventsSubscriber.c");
    Quests->registerEvent(events);

    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(member(events->quests(), "onQuestDeactivated"));
    ExpectTrue(Quests->deactivateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectTrue(member(events->quests(), "onQuestDeactivated"));
}

/////////////////////////////////////////////////////////////////////////////
void DeactivateQuestDoesNotFireOnQuestActivatedWhenItFails()
{
    object events = clone_object("/lib/tests/support/events/questEventsSubscriber.c");
    Quests->registerEvent(events);

    ExpectFalse(member(events->quests(), "onQuestDeactivated"));
    ExpectFalse(Quests->deactivateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(member(events->quests(), "onQuestDeactivated"));
}

Maeglin - RealmsMUD

  • New to TMC
  • *
  • Posts: 12
    • View Profile
    • Realmsmud
This second one exercises the quest state machine. Note the last couple tests (QuestSucceededReturnsTrueWhenQuestCompletesAsSuccess, for example) as they use ancillary methods and fake objects that transition the quest to its various states.

Again, to keep things slightly less large, most of the tests have been cut out of this:

Code: [Select]
inherit "/lib/tests/framework/testFixture.c";
#include "/lib/include/inventory.h"

object QuestItem;
object King;
object Quester;

/////////////////////////////////////////////////////////////////////////////
void SetUpQuestItem()
{
    QuestItem->testAddState("meet the king", "I've been asked to meet the king!");

    QuestItem->testAddState("met the king",
        "I met King Tantor the Unclean of Thisplace. He seems to like me.");
    QuestItem->testAddTransition("meet the king", "met the king", "meetTheKing",
        "/lib/tests/support/quests/testKingObject.c");

    QuestItem->testAddState("serve the king",
        "The king asked me - ME - to be his personal manservant. Yay me!");
    QuestItem->testAddTransition("met the king", "serve the king", "serveTheKing");

    QuestItem->testAddState("ignore the king",
        "I told the king to piss off. I have socks to fold.");
    QuestItem->testAddTransition("met the king", "ignore the king", "ignoreTheKing");
    QuestItem->testAddEntryAction("ignore the king", "killTheKing");
    QuestItem->testAddFinalState("ignore the king", "failure");

    QuestItem->testAddState("save the king",
        "Earl the Grey tried to kill the king but I gutted him like a fish.");
    QuestItem->testAddTransition("serve the king", "save the king", "hailToTheKing");
    QuestItem->testAddFinalState("save the king", "success");

    QuestItem->testAddState("king is dead",
        "I must lay off the sauce - and the wenches. King Tantor is dead because of my night of debauchery.");
    QuestItem->testAddTransition("serve the king", "king is dead", "maybeNobodyWillNotice");
    QuestItem->testAddFinalState("king is dead", "failure");

    King = clone_object("/lib/tests/support/quests/testKingObject.c");
    QuestItem->testRegisterQuestActor(King);
    King->SetQuestItem(QuestItem);
    King->SetQuester(Quester);

    object room = clone_object("/lib/environment/room.c");
    move_object(King, room);

    QuestItem->testSetInitialState("meet the king");
    Quester->StartQuest(QuestItem);
}

/////////////////////////////////////////////////////////////////////////////
void Init()
{
    ignoreList += ({ "SetUpQuestItem" });
}

/////////////////////////////////////////////////////////////////////////////
void Setup()
{
    Quester = clone_object("/lib/tests/support/services/combatWithMockServices.c");
    Quester->ToggleMockQuests();

    QuestItem = clone_object("/lib/tests/support/quests/testQuestItem.c");
}

/////////////////////////////////////////////////////////////////////////////
void CleanUp()
{
    destruct(King);
    destruct(QuestItem);
    destruct(Quester);
}

/////////////////////////////////////////////////////////////////////////////
void SetNameThrowsWhenNameNotValid()
{
    string err = catch (QuestItem->setName(3));
    ExpectEq("*ERROR - questItem: the name must be a string.", err);
}

/////////////////////////////////////////////////////////////////////////////
void AddTransitionSilentlySucceedsWhenEverythingValidates()
{
    QuestItem->testAddState("a", "do a stuff");
    QuestItem->testAddState("b", "do b stuff");
    QuestItem->testAddTransition("a", "b", "someEvent", "/lib/realizations/living.c");
}

/////////////////////////////////////////////////////////////////////////////
void QuestStoryReturnsCorrectNarrativeForQuestStatesCompleted()
{
    SetUpQuestItem();
    ExpectEq("I've been asked to meet the king! I met King Tantor the Unclean of Thisplace. He seems to like me. The king asked me - ME - to be his personal manservant. Yay me! I told the king to piss off. I have socks to fold. [Failure]",
        QuestItem->questStory(({"meet the king", "met the king", "serve the king", "ignore the king"})));
}

/////////////////////////////////////////////////////////////////////////////
void CanTransitionQuestStates()
{
    SetUpQuestItem();

    ExpectEq("meet the king", Quester->questState(program_name(QuestItem)));

    King->DoMeetTheKingStuff();
    ExpectEq("met the king", Quester->questState(program_name(QuestItem)));
}

/////////////////////////////////////////////////////////////////////////////
void DoesNotTransitionIfNotInProperStateForEvent()
{
    SetUpQuestItem();

    ExpectEq("meet the king", Quester->questState(program_name(QuestItem)));

    King->ConfuseTheForcesOfEvil();
    ExpectEq("meet the king", Quester->questState(program_name(QuestItem)));
}

/////////////////////////////////////////////////////////////////////////////
void OnEnterEventsFireWhenTransitionOccurs()
{
    SetUpQuestItem();
    QuestItem->testAddEntryAction("met the king", "doEnterStuff");
    ExpectEq("meet the king", Quester->questState(program_name(QuestItem)));
    ExpectTrue(sizeof(QuestItem->actionList()) == 0);

    King->DoMeetTheKingStuff();
    ExpectEq("met the king", Quester->questState(program_name(QuestItem)));
    ExpectTrue(sizeof(QuestItem->actionList()) == 1);
    ExpectTrue(member(QuestItem->actionList(), "Enter stuff has been done.") == 0);
}

/////////////////////////////////////////////////////////////////////////////
void OnExitEventsFireWhenTransitionOccurs()
{
    SetUpQuestItem();
    QuestItem->testAddExitAction("meet the king", "doExitStuff");
    ExpectEq("meet the king", Quester->questState(program_name(QuestItem)));
    ExpectTrue(sizeof(QuestItem->actionList()) == 0);

    King->DoMeetTheKingStuff();
    ExpectEq("met the king", Quester->questState(program_name(QuestItem)));
    ExpectTrue(sizeof(QuestItem->actionList()) == 1);
    ExpectTrue(member(QuestItem->actionList(), "Exit stuff has been done.") == 0);
}

/////////////////////////////////////////////////////////////////////////////
void OnEnterAndExitEventsFireInCorrectOrder()
{
    SetUpQuestItem();
    QuestItem->testAddExitAction("meet the king", "doExitStuff");
    QuestItem->testAddEntryAction("met the king", "doEnterStuff");

    ExpectEq("meet the king", Quester->questState(program_name(QuestItem)));
    ExpectTrue(sizeof(QuestItem->actionList()) == 0);

    King->DoMeetTheKingStuff();
    ExpectEq("met the king", Quester->questState(program_name(QuestItem)));
    ExpectTrue(sizeof(QuestItem->actionList()) == 2);
    ExpectTrue(member(QuestItem->actionList(), "Exit stuff has been done.") == 0);
    ExpectTrue(member(QuestItem->actionList(), "Enter stuff has been done.") == 1);
}

/////////////////////////////////////////////////////////////////////////////
void OnEnterDoesNotFireEventWhenNoneSet()
{
    SetUpQuestItem();

    ExpectEq("meet the king", Quester->questState(program_name(QuestItem)));
    ExpectFalse(King->checkNotification());

    King->DoMeetTheKingStuff();
    ExpectEq("met the king", Quester->questState(program_name(QuestItem)));
    ExpectFalse(King->checkNotification());
}

/////////////////////////////////////////////////////////////////////////////
void OnEnterFiresEventWhenItHasBeenSet()
{
    QuestItem->testAddState("meet the king", "I've been asked to meet the king!");

    QuestItem->testAddState("met the king",
        "I met King Tantor the Unclean of Thisplace. He seems to like me.", "someEvent");
    QuestItem->testAddTransition("meet the king", "met the king", "meetTheKing");

    King = clone_object("/lib/tests/support/quests/testKingObject.c");
    object subscriber = clone_object("/lib/tests/support/quests/someEventHandler.c");
    King->registerEvent(subscriber);
    QuestItem->testRegisterQuestActor(King);
    King->SetQuestItem(QuestItem);
    King->SetQuester(Quester);

    object room = clone_object("/lib/environment/room.c");
    move_object(King, room);

    QuestItem->testSetInitialState("meet the king");
    Quester->StartQuest(QuestItem);
    ExpectTrue(QuestItem->beginQuest(Quester));

    ExpectEq("meet the king", Quester->questState(program_name(QuestItem)));
    ExpectFalse(subscriber->checkNotification());

    King->DoMeetTheKingStuff();
    ExpectEq("met the king", Quester->questState(program_name(QuestItem)));
    ExpectEq("someEvent", subscriber->checkNotification());
}

/////////////////////////////////////////////////////////////////////////////
void OnEnterFiresIfSetForInitialState()
{
    QuestItem->testAddState("meet the king", "I've been asked to meet the king!");
    QuestItem->testAddEntryAction("meet the king", "doEnterStuff");

    King = clone_object("/lib/tests/support/quests/testKingObject.c");
    QuestItem->testRegisterQuestActor(King);
    King->SetQuestItem(QuestItem);
    King->SetQuester(Quester);

    object room = clone_object("/lib/environment/room.c");
    move_object(King, room);

    QuestItem->testSetInitialState("meet the king");
    ExpectFalse(member(QuestItem->actionList(), "Enter stuff has been done.") == 0);
    Quester->StartQuest(QuestItem);

    ExpectTrue(QuestItem->beginQuest(Quester));
    ExpectEq("meet the king", Quester->questState(program_name(QuestItem)));
    ExpectTrue(member(QuestItem->actionList(), "Enter stuff has been done.") == 0);
}

/////////////////////////////////////////////////////////////////////////////
void EntryEventFiresIfSetForInitialState()
{
    QuestItem->testAddState("meet the king", "I've been asked to meet the king!", "someEvent");

    King = clone_object("/lib/tests/support/quests/testKingObject.c");
    object subscriber = clone_object("/lib/tests/support/quests/someEventHandler.c");
    King->registerEvent(subscriber);
    QuestItem->testRegisterQuestActor(King);
    King->SetQuestItem(QuestItem);
    King->SetQuester(Quester);

    object room = clone_object("/lib/environment/room.c");
    move_object(King, room);

    QuestItem->testSetInitialState("meet the king");
    ExpectFalse(subscriber->checkNotification());
    Quester->StartQuest(QuestItem);

    ExpectTrue(QuestItem->beginQuest(Quester));
    ExpectEq("meet the king", Quester->questState(program_name(QuestItem)));
    ExpectEq("someEvent", subscriber->checkNotification());
}

/////////////////////////////////////////////////////////////////////////////
void CanBeginQuestReturnsTrueIfNoPrerequisitesSet()
{
    SetUpQuestItem();

    ExpectTrue(QuestItem->canBeginQuest(Quester));
}

/////////////////////////////////////////////////////////////////////////////
void CanBeginQuestReturnsFalseIfPrerequisitesNotMet()
{
    SetUpQuestItem();
    QuestItem->testAddPrerequisite("long sword", (["type":"skill", "value" : 10]));

    ExpectFalse(QuestItem->canBeginQuest(Quester));
}

/////////////////////////////////////////////////////////////////////////////
void CanBeginQuestReturnsTrueIfPrerequisitesMet()
{
    SetUpQuestItem();
    QuestItem->testAddPrerequisite("long sword", (["type":"skill", "value" : 10]));
    Quester->Str(20);
    Quester->addSkillPoints(100);
    Quester->advanceSkill("long sword", 10);
    ExpectTrue(QuestItem->canBeginQuest(Quester));
}

/////////////////////////////////////////////////////////////////////////////
void BeginQuestReturnsFalseIfPrerequisitesNotMet()
{
    SetUpQuestItem();
    QuestItem->testAddPrerequisite("long sword", (["type":"skill", "value" : 10]));

    ExpectFalse(QuestItem->beginQuest(Quester));
}

/////////////////////////////////////////////////////////////////////////////
void BeginQuestReturnsTrueIfPrerequisitesMet()
{
    SetUpQuestItem();
    QuestItem->testAddPrerequisite("long sword", (["type":"skill", "value" : 10]));
    Quester->Str(20);
    Quester->addSkillPoints(100);
    Quester->advanceSkill("long sword", 10);
    ExpectTrue(QuestItem->beginQuest(Quester));
}

/////////////////////////////////////////////////////////////////////////////
void QuestInCompletionStateReturnsTrueWhenQuestCompletesAsFailure()
{
    SetUpQuestItem();

    ExpectFalse(QuestItem->questInCompletionState(Quester->questState(program_name(QuestItem))));

    King->DoMeetTheKingStuff();
    ExpectFalse(QuestItem->questInCompletionState(Quester->questState(program_name(QuestItem))));

    King->TimeToFoldSocks();
    ExpectEq("ignore the king", Quester->questState(program_name(QuestItem)));
    ExpectTrue(QuestItem->questInCompletionState(Quester->questState(program_name(QuestItem))));
}

/////////////////////////////////////////////////////////////////////////////
void QuestSucceededReturnsFalseWhenQuestCompletesAsFailure()
{
    SetUpQuestItem();

    ExpectFalse(QuestItem->questSucceeded(Quester));

    King->DoMeetTheKingStuff();
    King->TimeToFoldSocks();
    ExpectTrue(QuestItem->questInCompletionState(Quester->questState(program_name(QuestItem))));
    ExpectFalse(QuestItem->questSucceeded(Quester));
}

/////////////////////////////////////////////////////////////////////////////
void QuestInCompletionStateReturnsTrueWhenQuestCompletesAsSuccess()
{
    SetUpQuestItem();

    ExpectFalse(QuestItem->questInCompletionState(Quester->questState(program_name(QuestItem))));

    King->DoMeetTheKingStuff();
    ExpectFalse(QuestItem->questInCompletionState(Quester->questState(program_name(QuestItem))));

    King->SureIWillServe();
    ExpectFalse(QuestItem->questInCompletionState(Quester->questState(program_name(QuestItem))));

    King->ConfuseTheForcesOfEvil();
    ExpectTrue(QuestItem->questInCompletionState(Quester->questState(program_name(QuestItem))));
}

/////////////////////////////////////////////////////////////////////////////
void QuestSucceededReturnsTrueWhenQuestCompletesAsSuccess()
{
    SetUpQuestItem();

    ExpectFalse(QuestItem->questSucceeded(Quester));

    King->DoMeetTheKingStuff();
    ExpectFalse(QuestItem->questSucceeded(Quester));

    King->SureIWillServe();
    ExpectFalse(QuestItem->questSucceeded(Quester));

    King->ConfuseTheForcesOfEvil();
    ExpectTrue(QuestItem->questSucceeded(Quester));
}

Maeglin - RealmsMUD

  • New to TMC
  • *
  • Posts: 12
    • View Profile
    • Realmsmud
Execution, then, will show the test executed and whether or not it passed. If it fails, it will report how it failed:

Code: [Select]
Compiling file: /lib/tests/items/shopSelectorTest.c
Testing lib/tests/items/shopSelectorTest
[  PASSED  ]  TopLevelMenuDisplaysCorrectly
[  PASSED  ]  DescriptionsAreDisplayed
[  PASSED  ]  SelectingPurchaseDisplaysBuyMenu
[  PASSED  ]  SelectingSellItemsDisplaysSellMenu
[  PASSED  ]  SelectingExitExitsSelector
Test executed: lib/tests/items/shopSelectorTest -> [  PASSED  ]
 -> built in 57 ms
Compiling file: /lib/tests/items/buyItemSelectorTest.c
Testing lib/tests/items/buyItemSelectorTest
[  PASSED  ]  TopLevelMenuDisplaysCorrectly
[  PASSED  ]  SelectingExitExitsTheMenu
[  PASSED  ]  DescribeItemDisplaysCorrectMessage
[  FAILED  ]  -> Actual: Buy Items - Select an item to buy:
-=-=-=-=-=-=-= Name =-=-=-=-=-=-=- Cost -=-=-= Item Details =-=-=-=-=-=-=-=-=-
[1]  - Bastard sword                650    Attack:  5, Damage: 12, Defense:  3
[2]  - Bastard sword of Good       5650    Attack:  5, Damage: 12, Defense:  3
[3]  - Broad sword of Good        20824    Attack:  4, Damage: 10, Defense:  5
[4]  - Broad sword                  500    Attack:  4, Damage: 10, Defense:  4
[5]  - Broad sword                 5500    Attack:  4, Damage: 10, Defense:  4
[6]  - Claymore                     750    Attack:  5, Damage: 16, Defense:  4
[7]  - Claymore of Energy         13674    Attack:  7, Damage: 18, Defense:  6
[8]  - Great sword of Energy      70312    Attack: 12, Damage: 22, Defense: 11
[9]  - Harpe of Poison            13375    Attack: 10, Damage: 14, Defense:  5
[10] - Katana of Air               3189    Attack: 11, Damage: 11, Defense:  3
[11] - Long sword                   450    Attack:  5, Damage: 10, Defense:  2
[12] - Long sword of Energy       12950    Attack:  5, Damage: 10, Defense:  2
[13] - Machete of Evil            12825    Attack:  4, Damage:  5, Defense:  2
[14] - Machete of Water           17825    Attack:  4, Damage:  5, Defense:  2
[15] - Sabre of Undead            26124    Attack:  7, Damage: 10, Defense:  6
[16] - Sabre of Acid              70724    Attack: 11, Damage: 14, Defense: 10
[17] - Scimitar of Water          17914    Attack:  6, Damage:  9, Defense:  2
[18] - Scimitar of Earth          34574    Attack:  7, Damage: 10, Defense:  4
[19] - Short sword                  350    Attack:  5, Damage:  6, Defense:  3
[20] - Short sword of Air          2839    Attack:  4, Damage:  5, Defense:  3
[21] - Return to previous menu
You must select a number from 1 to 21.
For details on a given choice, type 'describe X' (or '? X') where
X is the option about which you would like further details.
, Expected: You must select a number from 1 to 21
[  FAILED  ]  SelectSubMenuDisplaysBuyList
[  PASSED  ]  DescribeShowsItemDetails
[  PASSED  ]  PurchaseWithInsufficientFundsFails
[  PASSED  ]  PurchaseWithSufficientFundsSubtractsMoneyAndAddsItemToPlayerInventory
[  PASSED  ]  PurchaseOfPermanentItemDoesNotRemoveFromStore
[  PASSED  ]  PurchaseOfNonPermanentItemRemovesItFromStore
Test executed: lib/tests/items/buyItemSelectorTest -> [  FAILED  ]
 -> built in 527 ms