Dev:Unit Tests
From WCell
Many developers heard of it, some approve of it, some say its bogus, and yet its prooven that Unit Tests not only make developing a whole lot easier and faster, but they are also the most effective method to prevent users from having a bad experience with your product.
Contents |
[edit] The Promise
By utilizing Unit Tests, the developer can:
- ensure that tested functionality behaves as expected and thus is bug-free (of course this requires a lot of test-cases for a big project)
- easily test simple changes in code, by defining and running a test for that specific part of code
- See: Continuous Integration
- WCell is not yet adopting all of the principles of Continuous Integration, but is aiming towards it and will soon introduce a Build Server
[edit] Resources
- Wikipedia
- Extreme Programming is "actually a deliberate and disciplined approach to software development"
- Extreme Programming shows why and where to use Unit Testing and provides several examples of where Unit Tests in combination with other methods of Quality Assurance made the life of developers (and correspondingly end-users) so much easier
- Continuous Integration is based on Extreme Programming
[edit] WCell's advanced testing environment
WCell's Tests can be found in the Test/-Folder (specifically the RealmServer's Test Folder). You can use and run tests with Visual Studio Professional, Team Suite or other IDEs (not VS Express though).
[edit] Simple Object Pools
Object Pools generate and recycle default objects that are used within the ingame world. They can be used to create TestCharacters, TestAccounts etc (more to follow).
[edit] FakeClient and the Packet Analyzer
The FakeClient allows you to emulate a connected client without actually having to connect to the server. This can be used in combination with the Packet Analyzer and the PacketUtil class to easily define expected server-client communication (see below for examples).
[edit] Examples
A simple test (the final version of this test has more Assertions) for Group -Inviting, -Accepting and -Creation (see here for the original source file):
- Class definition:
[TestClass] public class GroupTest : PacketTestBase
- Used fields:
static TestCharacter m_chr1, m_chr2;
- Initialize (called once, before the first test of this class) - Make sure the Characters are in world and alive and register handlers for the packets that we want to inspect.
[ClassInitialize()]
public static void Initialize(TestContext testContext)
{
m_chr1 = Setup.AllianceCharacterPool.Create();
m_chr2 = Setup.AllianceCharacterPool.Create();
m_chr1.EnsureInWorldAndLiving();
m_chr2.EnsureInWorldAndLiving();
Assert.AreNotEqual(m_chr1.Name, m_chr2.Name);
// Register the handlers so we can access these kinds of packets later, using the GetLastReceivedSMSG* methods
AddRealmSMSGHandler(RealmServerOpCode.SMSG_PARTY_COMMAND_RESULT);
AddRealmSMSGHandler(RealmServerOpCode.SMSG_GROUP_LIST);
}
- Test Creation:
[TestMethod]
public void TestGroupCreation()
{
// invite
Invite(m_chr1, m_chr2);
// accept
Accept(m_chr1, m_chr2);
}
- Invite someone, by letting the FakeClient receive the corresponding packet and check what the server responds (utilizing the Packet Analyzer):
static void Invite(TestCharacter inviter, TestCharacter invitee)
{
var packet = new RealmPacketOut(RealmServerOpCode.CMSG_GROUP_INVITE);
packet.WriteCString(invitee.Name);
inviter.FakeClient.ReceiveCMSG(packet, true);
// invited notification
var parsedPacket = GetLastReceivedSMSG(RealmServerOpCode.SMSG_PARTY_COMMAND_RESULT);
var inviteeName = parsedPacket["Name"].StringValue;
Assert.AreEqual(invitee.Name, inviteeName);
Assert.AreEqual(GroupResult.NoError, parsedPacket["Result"].Value);
}
- Accept the invitation by sending another packet and check again for responses and the Group-objects of the 2 Characters:
static void Accept(TestCharacter leader, TestCharacter newMember)
{
bool newGroup = newMember.Group == null;
var packet = new RealmPacketOut(RealmServerOpCode.CMSG_GROUP_ACCEPT);
newMember.FakeClient.ReceiveCMSG(packet, true);
// adding the new member:
Assert.IsNotNull(leader.Group);
Assert.AreEqual(leader.Group, newMember.Group);
if (newGroup)
{
var packetInfo1 = GetLastReceivedSMSGInfo(RealmServerOpCode.SMSG_GROUP_LIST);
// New Group: First list is sent to the creator
Assert.AreEqual(leader.FakeClient, packetInfo1.Client);
var response1 = packetInfo1.Packet;
// should only contain the new member
var member1 = response1["Members"][0]["MemberName"].StringValue;
Assert.AreEqual(newMember.Name, member1);
Assert.AreEqual(leader.EntityId, response1["Leader"].EntityIdValue);
Assert.AreEqual(EntityId.Zero, response1["MasterLooter"].EntityIdValue);
}
var packetInfo = GetLastReceivedSMSGInfo(RealmServerOpCode.SMSG_GROUP_LIST);
Assert.AreEqual(newMember.FakeClient, packetInfo.Client);
var response = packetInfo.Packet;
// access the first member's name (which should be the leader)
var member = response["Members"][0]["MemberName"].StringValue;
Assert.AreEqual(leader.Name, member);
}
[edit] Results
[stub]
