Stendhal Quest Testing

From Arianne
Jump to navigation Jump to search



Stendhal Quests

Generate a chat log

We need chat logs of players doing each Stendhal Quest so that we can create tests for them. So, it's easy to help us because all you need to do, is do the quest and then save the chat log for us. There are some ways you can help us make the best possible test:

  • don't say yes to everything immediately. try saying no and then come back and say yes
  • try to fool the npc. if he asked you for an item, say you had it when you didn't really! or if you were supposed to kill something, say yes you did, before you really did.
  • if it's repeatable, try to come back and do it too early, and then again later
  • if it's not repeatable, try to come back and ask for a quest again anyway!

Some quests have already got a test written for them because chat logs have been provided. Please take a look at the report from Jenkins - quests which are all or mostly green are done, and quests which are all or mostly red need a chatlog provided.

Tip: If you test on a local server, you can use the /teleportto npc command to travel faster. See the administration page for other useful commands.

Run ChatTestCreator

Once you have the chatlog you can use games.stendhal.tools.test.ChatTestCreator to make a test. We assume you are using eclipse. First, copy the chat log into the project folder of your Stendhal project. For this tutorial we are using File:Test Gamechat.log - save it and rename it to gamechat.log like your own chatlogs would be, if you want to follow the tutorial exactly.

Next make sure the character who made the chatlog is added to the list of testers.

Open src/games/stendhal/tools.test/LineAnalyser.java and add your name to playerNames.

Open src/games/stendhal/tools.test/ChatTestCreator.java in the editor in Eclipse then go to the green arrow button for running an application.

 Run Configurations ... 
 Arguments tab
 Program arguments: gamechat.log
 Click Run

If all is well you should get some text output into your Console starting with

package games.stendhal.server.maps.quests;

import static org.junit.Assert.assertEquals;
import games.stendhal.server.core.engine.SingletonRepository;

and it will be plain with no coloured highlighting.

Copy and paste all that text into a new file which you should save in tests/games/stendhal/server/maps/quests/. Call it the same name as the class file for your Quest, with 'Test' at the end. We are testing RainbowBeans.java, so we name the file, RainbowBeansTest.java.

Get the test to compile

Now there will be some red underlined errors in the file, for each part which is TODO. Don't worry about that! We are going to fix each one.

public class TODO_Test extends ZonePlayerAndNPCTestImpl {

	private Player player = null;
	private SpeakerNPC npc = null;
	private Engine en = null;

	private String questSlot;
	private static final String ZONE_NAME = "admin_test";

	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
		QuestHelper.setUpBeforeClass();
		setupZone(ZONE_NAME);
	}

	public TODO_Test() {
		super(ZONE_NAME, TODO_NPC_Name);
	}

	@Before
	public void setUp() {
		final StendhalRPZone zone = new StendhalRPZone(ZONE_NAME);
		new TODO_NPC().configureZone(zone, null);	

		AbstractQuest quest = new TODO_Quest();
		quest.addToWorld();

		questSlot = quest.getSlotName();

		player = PlayerTestHelper.createPlayer("bob");
	}

        @Test
	public void testQuest() {
		npc = SingletonRepository.getNPCList().get(TODO_NPC_Name);

TODO_Test and TODO_Test()

This is the class name of your test file. Which should be the class name of the quest file you are testing, plus 'Test'. Ours is RainbowBeansTest.

TODO_NPC()

Locate the maps file for the NPC in your quest (hint: search the src/games/stendhal/server/maps folder for their name) then use the class name of their maps file

We searched Pdiddi and found he is defined in src/games/stendhal/server/maps/semos/pad/DealerNPC.java - so we replaces TODO_NPC() with DealerNPC() and added the import needed.

If you have more than one NPC active in this quest you need to add them! Just copy and paste the line and repeat for each NPC.

TODO_Quest

This is the class name of the quest file you are testing. For us, that's RainbowBeans.

TODO_NPC_Name

Fill in here, the name of the first NPC you spoke to for the quest. Put it in quotes: "Pdiddi" in the constructor RainbowBeansTest(). And once in the testQuest() function: SingletonRepository.getNPCList().get("Pdiddi");

ZONE_NAME

You can replace the default zone name "admin_test" by the zone, in which the quest actually happens. You can get this name just by looking in the display of the game client while playing the quest. Using the correct zone name is important if there is a teleport etc in the zone, which is used in the quest.

Now the errors should be gone:

public class RainbowBeansTest extends ZonePlayerAndNPCTestImpl {

	private Player player = null;
	private SpeakerNPC npc = null;
	private Engine en = null;

	private String questSlot;
	private static final String ZONE_NAME = "admin_test";

	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
		QuestHelper.setUpBeforeClass();
		setupZone(ZONE_NAME);
	}

	public RainbowBeansTest() {
		super(ZONE_NAME, "Pdiddi");
	}

	@Before
	public void setUp() {
		final StendhalRPZone zone = new StendhalRPZone("admin_test");
		new DealerNPC().configureZone(zone, null);	

		AbstractQuest quest = new RainbowBeans();
		quest.addToWorld();

		questSlot = quest.getSlotName();

		player = PlayerTestHelper.createPlayer("bob");
	}

        @Test
	public void testQuest() {
		npc = SingletonRepository.getNPCList().get("Pdiddi");

Run the test and make corrections

Now right click the file:

Run As ...
Junit test

TODO: I think this needs more explanation

It will fail, don't worry!

The trace for our example says

expected:<...undry knowin' wot I []deal in.> but was:<...undry knowin' wot I [#]deal in.>

The problem is that the chatlog didn't preserve any of those special #hash. So it looks like a mistake. Just modify the test by adding in the hash as needed:

assertEquals("SHHH! Don't want all n' sundry knowin' wot I #deal in.", getReply(npc));

Once all those are fixed we try running the test again. The next error is another unexpected response:

 org.junit.ComparisonFailure: expected:<[Nosy, aint yer? I deal in rainbow beans. You take some, and who knows where the trip will take yer. It'll  cost you 2000 money. You want to buy some?]> but was:<[It's not stuff you're ready for, pal. Now get out of 'ere! An don't you come back till you've got more hairs on that chest!]>

The player who tested it had enough level to buy the rainbow beans but this player is just a zero level player created for the test.

So we need to increase the level before we say hi, and we might as well check it's enough using an Assert (add the imports if needed)

 // make them at least level 30
 player.addXP(248800);
 assertThat(player.getLevel(), greaterThanOrEqualTo(30));

If you're having trouble with imports just add, with the imports,

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

The next error is that we don't have the money to pay for the beans. So, after saying "yes" we want the beans, we need to give the money.

 PlayerTestHelper.equipWithMoney(player, 2000);

The next error that comes is because when we tested we manually changed the quest slot so that we could come back and try again, to repeat it:

 org.junit.ComparisonFailure: expected:<[Oi, you. Back for more rainbow beans?]> but was:<[Alright? I hope you don't want more beans. You can't take more of that stuff for at least another 6 hours.]>

So we need to do that to our player, as the comment hints:

// [11:36] Admin superkym changed your state of the quest 'rainbow_beans' from 'bought;1289129695296;taken;-1' to 'bought;0;taken;0'
player.setQuest(questSlot,"bought;0;taken;0");

Finally after fixing one more 'you don't have the cash error', your test should pass!

The completed test looks like:

package games.stendhal.server.maps.quests;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static utilities.SpeakerNPCTestHelper.getReply;
import games.stendhal.server.core.engine.SingletonRepository;
import games.stendhal.server.core.engine.StendhalRPZone;
import games.stendhal.server.entity.npc.SpeakerNPC;
import games.stendhal.server.entity.npc.fsm.Engine;
import games.stendhal.server.entity.player.Player;
import games.stendhal.server.maps.semos.pad.DealerNPC;

import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import utilities.PlayerTestHelper;
import utilities.QuestHelper;
import utilities.ZonePlayerAndNPCTestImpl;

public class RainbowBeansTest extends ZonePlayerAndNPCTestImpl {
 
	private Player player = null;
	private SpeakerNPC npc = null;
	private Engine en = null;
 
	private String questSlot;
	private static final String ZONE_NAME = "admin_test";
 
	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
		QuestHelper.setUpBeforeClass();
		setupZone(ZONE_NAME);
	}
 
	public RainbowBeansTest() {
		super(ZONE_NAME, "Pdiddi");
	}

	@Before
	public void setUp() {
		final StendhalRPZone zone = new StendhalRPZone("admin_test");
		new DealerNPC().configureZone(zone, null);	
		

		AbstractQuest quest = new RainbowBeans();
		quest.addToWorld();
		
		questSlot = quest.getSlotName();
		
		player = PlayerTestHelper.createPlayer("bob");
	}

	@Test
	public void testQuest() {
		
		npc = SingletonRepository.getNPCList().get("Pdiddi");
		en = npc.getEngine();


		// -----------------------------------------------

		// [11:33] Please note: The test database will be reset every day and all PROGRESS WILL BE LOST.
		// [11:33] Synchronized
		// [11:33] Your admin level is only 400, but a level of 900 is required to run "alterquest".
		// [11:34] Admin superkym changed your state of the quest 'rainbow_beans' from 'bought;1264104887559;taken;1264104889247' to 'null'
		
		// make them at least level 30
		player.addXP(248800);
		assertThat(player.getLevel(), greaterThanOrEqualTo(30));
		
		en.step(player, "hi");
		assertEquals("SHHH! Don't want all n' sundry knowin' wot I #deal in.", getReply(npc));
		en.step(player, "deal");
		assertEquals("Nosy, aint yer? I deal in rainbow beans. You take some, and who knows where the trip will take yer. It'll cost you 2000 money. You want to buy some?", getReply(npc));
		en.step(player, "no");
		assertEquals("Aight, ain't for everyone. Anythin else you want, you say so.", getReply(npc));
		en.step(player, "help");
		assertEquals("To be honest mate I can't help you with much, you're better off in the city for that.", getReply(npc));
		en.step(player, "quest");
		assertEquals("Haven't got anything for you, pal.", getReply(npc));
		en.step(player, "job");
		assertEquals("I think you already know what I do.", getReply(npc));
		en.step(player, "offer");
		assertEquals("Ha! The sign on the door's a cover! This is no inn. If you want a drink, you better go back into town.", getReply(npc));
		en.step(player, "bye");
		assertEquals("Bye.", getReply(npc));
		
		PlayerTestHelper.equipWithMoney(player, 2000);
		
		en.step(player, "hi");
		assertEquals("SHHH! Don't want all n' sundry knowin' wot I #deal in.", getReply(npc));
		en.step(player, "deal");
		assertEquals("Nosy, aint yer? I deal in rainbow beans. You take some, and who knows where the trip will take yer. It'll cost you 2000 money. You want to buy some?", getReply(npc));
		en.step(player, "yes");
		assertEquals("Alright, here's the beans. Once you take them, you come down in about 30 minutes. And if you get nervous up there, hit one of the green panic squares to take you back here.", getReply(npc));
		en.step(player, "bye");
		assertEquals("Bye.", getReply(npc));
		
		en.step(player, "hi");
		assertEquals("Alright? I hope you don't want more beans. You can't take more of that stuff for at least another 6 hours.", getReply(npc));
		en.step(player, "bye");
		assertEquals("Bye.", getReply(npc));
		
		// [11:36] Admin superkym changed your state of the quest 'rainbow_beans' from 'bought;1289129695296;taken;-1' to 'bought;0;taken;0'
		player.setQuest(questSlot,"bought;0;taken;0");
		
		en.step(player, "hi");
		assertEquals("Oi, you. Back for more rainbow beans?", getReply(npc));
		en.step(player, "no");
		assertEquals("Aight, ain't for everyone. Anythin else you want, you say so.", getReply(npc));
		en.step(player, "bye");
		assertEquals("Bye.", getReply(npc));
		
		PlayerTestHelper.equipWithMoney(player, 2000);
		
		en.step(player, "hi");
		assertEquals("Oi, you. Back for more rainbow beans?", getReply(npc));
		en.step(player, "yes");
		assertEquals("Alright, here's the beans. Once you take them, you come down in about 30 minutes. And if you get nervous up there, hit one of the green panic squares to take you back here.", getReply(npc));
		en.step(player, "bye");
		assertEquals("Bye.", getReply(npc));
	}
}

Tests with more than one NPC

In some quests you have to speak to more than one NPC. In that case you need to tell the quest that the NPC (the engine) changed. Remember you need to set them up with a

new TODO_NPC().configureZone(zone, null);

line for each NPC.

The easiest way to make this work is like this:

  // says Bye to first NPC
  en.step(player, "bye Carmen");
  assertEquals("Bye - nice to meet you!", getReply(npc));

   // add these two lines to change to the next NPC  
  npc = SingletonRepository.getNPCList().get("Nishiya");
  en = npc.getEngine();

  en.step(player, "hi Nishiya");
  assertEquals("Hello", getReply(npc));

Now the variables en and npc are associated with the new NPC.

Advanced testing: more than one dialog

By now I'm going to assume that you are comfortable with basic tests, that you can test dialog, andthat you can modify the player by equipping him with items or setting his quest slot, if you need to.

But there is more to test than just dialog. Rewards are given (xp, items) - quest slots are updated - items are removed.

So here are some examples. Here, we just bought some rainbow beans. So he should have them. And we'd only given the player 2000 money, so now we think he should have none.

 assertTrue(player.isEquipped("rainbow beans"));
 assertFalse(player.isEquipped("money"));

You could put this after the 'Bye' from the block where we bought them.

What about getting xp or karma? That's not relevant to this quest but here's an example from the SadScientist test:

 // store the p and karma values before getting reward
 final int xp = player.getXP();
 final double karma = player.getKarma();

 en.step(player, "hi");
 assertEquals("Here are the black legs. Now I beg you to wear them. The symbol of my pain is done. Fare thee well.", getReply(npc));
 // [23:05] kymara earns 10000 experience points.
 assertThat(player.getXP(), greaterThan(xp));
 assertThat(player.getKarma(), greaterThan(karma));

The final two asserts just check that the xp and the karma did increase (though they don't check the amount)

After a quest was completed the quest slot should be "done" normally. So we might check

 assertTrue(questSlot.equals("done"));

and you can do other checks on the value of the quest slot, throughout.

Advanced Testing: breaking up into several small tests

Splitting up the test means making several mini tests inside the file. So far we just have one huge block:

	@Test
	public void testQuest() {

If your quest has several stages it could be a good idea to split the test for readability. Or, when it has several NPCs, we often start a new mini test inside the file for each NPC.

TODO: use e.g. Take Gold For Grafindle as an example

Trouble shooting

ChatTestCreator

Problem: ArrayIndexOutOfBoundsException
java games.stendhal.tools.test.ChatTestCreator chatlog.txt [chatlogtest.java]
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
at games.stendhal.tools.test.ChatTestCreator.main(ChatTestCreator.java:75)

Solution: Put gamechat.log into the Program arguments.

Problem: FileNotFoundException
Exception in thread "main" java.io.FileNotFoundException: gamechat.log (No such file or directory) 

Solution: copy gamechat.log into the project folder for your eclipse workspace.

Problem: Test doesn't look anything like example, there's no en.step(player lines and only assertEquals

Solution: add the character name for the player who made the test to the LineAnalyser file

Running Tests

Problem:NullPointerException
java.lang.NullPointerException
at utilities.PlayerTestHelper.equipWithStackableItem(PlayerTestHelper.java:217)
at utilities.PlayerTestHelper.equipWithMoney(PlayerTestHelper.java:190)

Solution: put the stendhal project folder on your classpath for running tests - for Eclipse users see Running JUnit Tests in Eclipse.