Karak Locutus of WheelMUD
 Advanced Member
 Posts:748

 | | 22 Aug 2011 12:57 AM |
| (Topic branched from http://www.wheelmud.net/Forums/tabi...ault.aspx) What I think I'd actually like to do is use unit testing to assist with developing/iterating new behaviors (and thereafter to help keep them working). I'd been putting that off until one of the people who really wanted us to have strong unit testing actually committed some strong unit tests (but that fell through a couple times). *sigh* It's an area I'm curious about but never really seen done in practice, and all the blogs I read about it present it in an unintentionally-intimidating way; I don't get the naming conventions, etc. I'm 99% certain my first unit tests will be garbage in the eyes of anyone who does it professionally and that's been enough disincentive for me to not bother. I'd much rather see how it's done in practice, not just blog theory and then try it myself, but I think I might have to bite the bullet and write that throw-away garbage to get my feet wet. Or maybe there's a good primer out there, but I haven't found it yet. Anyway, the theory that gets me excited is this: (Context: I'm fixing/iterating OpensClosesBehavior). Instead of firing up the whole server and creating a new character/etc, I could just fire up some test(s) that do something like: Setup: var roomA = new Thing(new RoomBehavior { name = "Room A" }); var roomB = new Thing(new RoomBehavior { name = "Room B" }); var door = new Thing(new ExitBehavior { name = "door" }, new OpensClosesBehavior()); add exit to both rooms var movingActor = new Thing(new MovableBehavior { name = "MovingActor" }); add movingActor to roomA Test 1 (verify actor can't move through exit if it is closed): exitBehavior.MoveThrough(movingActor); verify movingActor.Parent is still roomA Test 2 (verify actor can open the openable): opensClosesBehavior.Open(movingActor); verify opensClosesBehavior.IsOpen is true Test 3 (verify actor can move through an opened exitable): exitBehavior.MoveThrough(movingActor); verify movingActor.Parent is now roomB ... then the natural progression of this when implementing/testing LocksUnlocksBehavior afterwards would be to use the same initial setup but then add a LocksUnlocksBehavior to the exit as well, spawn a key item into the room, etc. Obviously having so much setup be the same indicates code re-use would be ideal, but is that reasonable for unit tests? So many other questions... These go in their own files? Static shared setup methods in yet another file? Does each test leave it's state like I think, so each Test I listed there would be it's own [test] method? Would there then be a way to have the LocksUnlocksBehavior start from THAT state even if it's in their own .cs file; would that be a good idea? How do i name these tests? Am I forgetting important tests, or testing too much in each step? Is this even leveraging unit testing well? I have no idea. Thoughts, anyone? | | |
|
|
Fastalanasa Grumpy Half-Elf
 Advanced Member
 Posts:961

 | | 22 Aug 2011 11:10 AM |
| I've been in the same boat as yourself. I haven't found a mentor, so I just started experimenting with my TextFileSplitter utility. I've learned quite a bit, but I still wish I had a mentor that has actually done this in a production environment. So let me go through your questions...
I'm using the good ole NUnit from http://www.nunit.org. I'm using the ReSharper unit test runner in Visual Studio.
but is that reasonable for unit tests?
Yes, these look quite reasonable to me. This has always been in the context of the project you're working with.
These go in their own files?
The rule of thumb that I've seen is to create a new test file per class.
Static shared setup methods in yet another file?
This depends on what you want to accomplish. I've seen this usage in projects where the test setup is fairly complicated. I had to create a class to handle just setup duties in my TextFileSplitter unit test project. I needed to feed each text file splitting strategy a specific text file, and some command-line parameters.
My suggestion is that if more than one test class uses the same setup routine, just abstract it away into its own static class, and have the different tests create their test setups from there.
Does each test leave it's state like I think, so each Test I listed there would be it's own [test] method?
The break down that you have above looks about right. You create the desired behavior state, then test it.
How do i name these tests?
IIRC, you just do the VerbActionConditionBeingTested naming. Makes it easy to group tests this way.
Ultimately, you'll just have to get your hands dirty. Just create a new file, add the first failing test, and go from there. | | | |
|
Karak Locutus of WheelMUD
 Advanced Member
 Posts:748

 | | 22 Aug 2011 03:04 PM |
| I bit the bullet and started following one of the crappy tutorials to get my feet wet. I'm using the built-in VS unit testing tech. I found that individual test methods do not leave their state behind, between tests, which is what I guess I meant on one of my questions - which makes sense since you can generally select "just run test 3" from the test harness UI. So I have a couple obvious options here: * I could have one long method perform a lot of tests like the 3 I mentioned, asserting every step of the way - this feels like it violates test principals that I've read about, but I'm not certain yet. * I could have Test2() call Test1() at the start to try to get the desired starting state. Test3 would similarly call Test2. (Not that I'll name them that, but I haven't rered any test naming conventions blog posts yet.) Also feels wrong, since if a basic test in Test1 fails, then we generate 3 test failures for one problem. Maybe that's the right result though since it really does block tons more functionality than an edg case at Test3. * Duplicate a bunch of slightly-varied setup code each test or... * Create a ton of tiny little ~3 line setup help methods, which both also feel wrong in a more general coding practices sense. Since all the options feel wrong, I guess I'll go ahead and try each one and try to find a nice balance of code reuse and interdependence and such.  | | | |
|
Karak Locutus of WheelMUD
 Advanced Member
 Posts:748

 | | 22 Aug 2011 10:28 PM |
| Checked in my first ever unit tests. If you get a chance to check it out, feedback would be nice. CruiseControl also claims the build broke, but just looks like some weird PDB access issue on the builder, for the main solution. I just fixed the express solution; removed the old tests project from it, but did not add the new one since I think Express does not support unit testing? Should the test projects be there anyway? EDIT: After another rebuild attempt of main, now autobuilder's saying it can't find Microsoft.VisualStudio.QualityTools.UnitTestFramework on HD... hmm. EDIT: There are now TestExitBehavior and TestMultipleParentsBehavior. The second are correctly failing right now, exposing some problems with the way I tried to reuse code in Thing Adds and Removes that I'll have to tackle when I'm more awake. Looks like the tests are already doing what they're supposed to - finding bugs and making it easier to step through and debug them. "Ctrl-R,T is totally the new F5". | | | |
|
Fastalanasa Grumpy Half-Elf
 Advanced Member
 Posts:961

 | | 23 Aug 2011 09:01 AM |
| Posted By Karak on 22 Aug 2011 11:28 PM
Checked in my first ever unit tests. If you get a chance to check it out, feedback would be nice. CruiseControl also claims the build broke, but just looks like some weird PDB access issue on the builder, for the main solution. I just fixed the express solution; removed the old tests project from it, but did not add the new one since I think Express does not support unit testing? Should the test projects be there anyway? Good questions. I don't know the answer to them yet. EDIT: Visual Studio 2010 Express does not support unit testing in the IDE. Here's more information on this: greenicicleblog.com/2010/04/13/visu...ts-please/ SharpDevelop 4 and 4.1 have a built in test runner for NUnit. Posted By Karak on 22 Aug 2011 11:28 PM
EDIT: After another rebuild attempt of main, now autobuilder's saying it can't find Microsoft.VisualStudio.QualityTools.UnitTestFramework on HD... hmm. Are you using MSTest? It may make it harder for people using Express and SharpDevelop. Also, I would need to install Visual Studio on the build machine, which I'm not too keen on. I'll look at resolving this issue. My suggestion would be to use one of the free/open source XUnit frameworks out there. EDIT: Ah yes, you are using MSTest. I tried running the tests, but you forgot to checkin the following files: WheelMUD.vsmdi
Local.testsettings
TraceAndTestImpact.testsettings EDIT: MSTest is tightly coupled with Visual Studio. I would need a Visual Studio Team edition license on the build server, or try a giant hack like this guy: www.shunra.com/shunrablog/index.php...al-studio/ I don't want to kill your momentum, but my suggestion of using something other than MSTest will work to our advantage in the long run. The nunit.framework.dll assembly is already in the _references folder. I will take care of adding NUnit to the tools directory tonight. I will update everything to NUnit 2.5.10. EDIT: Ok I was wrong about needed an additional license. Here's a blog post directly from Microsoft: blogs.msdn.com/b/jeffbe/archive/200...chine.aspx Posted By Karak on 22 Aug 2011 11:28 PM
EDIT: There are now TestExitBehavior and TestMultipleParentsBehavior. The second are correctly failing right now, exposing some problems with the way I tried to reuse code in Thing Adds and Removes that I'll have to tackle when I'm more awake. Looks like the tests are already doing what they're supposed to - finding bugs and making it easier to step through and debug them. "Ctrl-R,T is totally the new F5". Kewl! I'll play with it when I have some time. | | | |
|
Fastalanasa Grumpy Half-Elf
 Advanced Member
 Posts:961

 | | 23 Aug 2011 10:26 PM |
| Alright, I created a new project in the Tests folder called WheelMUD.Core.Tests. I converted the TestExitBehavior to NUnit format, and called it ExitBehaviorTest in the new project. Hopefully, this will give you an idea on how to do the tests using NUnit. Feel free to organize and/or rename WheelMUD.Core.Tests to whatever you want.  | | | |
|
Karak Locutus of WheelMUD
 Advanced Member
 Posts:748

 | | 23 Aug 2011 11:45 PM |
| I was wondering why add the NUnit version, but I guess it's probably the only way for VS Express users to be able to run tests?
I bet we can come up with a good way to write one set of tests code in one library that will run in either test harness. Mainly I think it'll just be a matter of wrapping the assert methods so the correct one is called depending on the launching assembly, and the test writer would just have to slap both test attributes on their methods. I think I'll give it a shot after seeing the NUnit tests in action. | | | |
|
Fastalanasa Grumpy Half-Elf
 Advanced Member
 Posts:961

 | | 24 Aug 2011 12:07 AM |
| Posted By Karak on 24 Aug 2011 12:45 AM
I was wondering why add the NUnit version, but I guess it's probably the only way for VS Express users to be able to run tests?
I bet we can come up with a good way to write one set of tests code in one library that will run in either test harness. Mainly I think it'll just be a matter of wrapping the assert methods so the correct one is called depending on the launching assembly, and the test writer would just have to slap both test attributes on their methods. I think I'll give it a shot after seeing the NUnit tests in action. It's a system admin issue. Since MSTest is tightly coupled with Visual Studio, VS Express and SharpDevelop users won't be able to run those tests. It also means that I would have to install Visual Studio 2010 on the build server. Which I would rather not have to do.  VS Express users are out of luck in any case. They can't run test inside the IDE. They would have to fork out money for Visual Studio Professional, if they want to run unit tests written for MSTest. At least with NUnit, they can run the test runners (gui and console versions) that come with it. | | | |
|
Karak Locutus of WheelMUD
 Advanced Member
 Posts:748

 | | 24 Aug 2011 12:40 AM |
| Makes sense. I'll do my best to see how we can support both with minimal duplicate code before we make a billion unit tests. Sealed is evil! 95% of the uses I see it applied to do not make sense In this case I wanted to abstract away the attributes so we would only add one non-conflicting namespace and then our test methods would look like:
using WheelMUD.Tests;
[TestMethodVS][TestMethodNUnit]
public void TestMethod() {... Assert.IsTrue(...) ...}
where TestMethodVS, TestMethodNUnit, and our own Assert would've been exposed through WheelMUD.Tests. On to plan 2: find a new name for "Assert" and leave the attributes alone. Starting with Verify, like "Verify.IsTrue... Verify.AreSame..." has a nice ring without getting too wordy, but perhaps AssertEx or DynamicAssert or something would more explicitly describe how it's redirecting to the methods of the hosting test harness.
That said, I made a working prototype but I'm finding it a touch harder to detect which framework launched the test than expected; the executing/calling assemblies of both just give info for the test projet, and they both even employ basically the same app domain name, etc. Time to sleep - I'll come back to this another night. | | | |
|
Fastalanasa Grumpy Half-Elf
 Advanced Member
 Posts:961

 | | 24 Aug 2011 09:35 AM |
| Gah, that sounds like a lot of work!  How about this... Concentrate on the NUnit test for public consumption, as part of the main solution. Then create a new solution with stuff that has MSTest stuff. Then you have the best of both worlds.  | | | |
|
Karak Locutus of WheelMUD
 Advanced Member
 Posts:748

 | | 24 Aug 2011 11:10 AM |
| Actually it's not ending up as a lot of work, since the Assert surfaces are pretty much identical between these two - Verify will just have a series of private static Actions which are set up at static init time to point to either the MS or NUnit version. If there are important variations, some clever lambdas will probably do the trick. With C# 4 I think I can get away with roughly one method declared per name, like public static void AreSame(object expected, object actual, string message = null, params object[] parameters) to support all AreSame methods. I'd rather not go down the road of mass code duplication. Anyway, I woke up this morning thinking "Process.GetCurrrentProcess!" which should be exactly what I'm looking for to distinguish which set of methods to point to. | | | |
|
Karak Locutus of WheelMUD
 Advanced Member
 Posts:748

 | | 25 Aug 2011 12:54 AM |
| Worked out great so far. Dual-framework support for a single set of test cases is in now.  | | | |
|
Fastalanasa Grumpy Half-Elf
 Advanced Member
 Posts:961

 | | 25 Aug 2011 10:40 AM |
| That works well. Thanks!
Just added support for the ReSharper test runner. This has been checked in.
Still need to figure out how to solve the issue of missing MSTest bits on the build server. The build is failing over there. | | | |
|
Fastalanasa Grumpy Half-Elf
 Advanced Member
 Posts:961

 | | 26 Aug 2011 08:15 PM |
| Got the tests working on the build server. Ended up installing Visual Studio 2010. Oh Well.
Now we just need you to fix the failing tests, and it will be business as usual. | | | |
|
Karak Locutus of WheelMUD
 Advanced Member
 Posts:748

 | | 05 Jan 2012 01:48 AM |
| Now we have tests for MultipleParentsBehavior and OpensClosesBehavior. Open/close is functional but lacks correct output like "you open X", or when moving through a closed exit, the "you cannot move through X because it is closed" is not displayed to the user. (The cancel print is marked as a todo in EventBase.cs at least.) I'm still rather pleased with using tests to do this work. Certainly saves on time not having to repeatedly launch/wait/connect/login/setup. Found and fixed a big problem with event cascading today too since closed exits weren't causing cancelled move events  | | | |
|
Fastalanasa Grumpy Half-Elf
 Advanced Member
 Posts:961

 | | 07 Jan 2012 11:56 AM |
| That's pretty awesome!
I'm going to see if I can look through the code this weekend. Been dealing with some health issues and coding burn out, but this looks interesting enough to get me going. | | | |
|
Karak Locutus of WheelMUD
 Advanced Member
 Posts:748

 | | 22 Jan 2012 02:12 AM |
| Added some tests of the eventing itself to TestOpensClosesBehavior, found and fixed a few eventing issues and cleaned it up. Various prints are happening now, but still some issues with how they display (IE twice, and without proper $keyword replacement). Eventually we'll want the room rendering of the exits to also consider whether those are closed and render those appropriately. Need to think about how to tackle that. | | | |
|