in
Forums
Blogs
Files
Devexpress.Com
ClientCenter
Support Center
DevExpress Channel

Gary's Blog

XAF RWA Code #1

Now that we’ve done our design, it’s time to code it up. So first thing to do is to add an empty solution to Visual Studio. Next, add to that a new test project (yes we’re going to to a bit of TDD). Now we are going to add the following test

[TestMethod]
public void TestDiaryHasName() {
    using (UnitOfWork uow = new UnitOfWork())
    {
        CourseDiary diary = new CourseDiary(uow);
        diary.Name = "Test Diary";

        Assert.IsNotNull(diary.Name);
    }           
}

This test will test whether or not our CourseDiary is capable of storing a name variable. When you type / paste this code then you will notice lots of problems with it in the first instance and the code will not compile. Let’s carry out some steps to ensure that it does build – using the TDD philosophy of doing the least thing that will make the test pass.

Firstly add an XAF Windows Forms Application and in the Solution1.Module project add a domain object named CourseDiary. In CourseDiary add a string property (CR shortcut xps) named “Name”. Now go back to your test project and add references to the following assemblies:

  • Solution1.Module
  • DevExpress.Data.V8.2
  • DevExpress.Persistent.BaseImpl.v8.2
  • DevExpress.Xpo.v8.2

Now add any using statements required. Having done that your test should compile, run and pass. If not, then fix any problems that the compiler or test runner throws your way and try again. Once you have gotten this test working we can move on to the next test.

The next test will test the CourseDiary’s ability to store a description variable and it looks like this:

[TestMethod]
public void TestDiaryHasDescription() {
    using (UnitOfWork uow = new UnitOfWork())
    {
        CourseDiary diary = new CourseDiary(uow);
        diary.Description = "Description";

        Assert.IsNotNull(diary.Description);
    } 
}

Again the test will not compile until you add the Description property to the CourseDiary object. Having done the simplest thing that will make the test pass, your test should now compile, run and pass. Now repeat this process for the StartDate, EndDate and Outages properties, creating the following tests (The CR shortcut for a DateTime property is xpd8):

[TestMethod]
public void TestDiaryHasStartDate() {
    using (UnitOfWork uow = new UnitOfWork()) {
        CourseDiary diary = new CourseDiary(uow);
        diary.StartDate = DateTime.Parse("01/04/2009 00:00:01");

        Assert.IsNotNull(diary.StartDate);
    }
}

[TestMethod]
public void TestDiaryHasEndDate() {
    using (UnitOfWork uow = new UnitOfWork()) {
        CourseDiary diary = new CourseDiary(uow);
        diary.EndDate = DateTime.Parse("31/10/2009 23:59:59");

        Assert.IsNotNull(diary.EndDate);
    }
}

[TestMethod]
public void TestDiaryHasOutages() {
    using (UnitOfWork uow = new UnitOfWork()) {
        CourseDiary diary = new CourseDiary(uow);
        
        Assert.IsNotNull(diary.Outages);
    }
}

The Outages property is a special case because we first have to add a new domain object named Outage and then add a collection property to the CourseDiary (CR shortcut xpcl) like so:

[Association("CourseDiary-Outages")]
public XPCollection<Outage> Outages {
    get {
        return GetCollection<Outage>("Outages");
    }
}

And an attribute property to the Outage pointing back to the CourseDiary (CR shortcut xpa) like so:

private CourseDiary _CourseDiary;
[Association("CourseDiary-Outages")]
public CourseDiary CourseDiary {
    get {
        return _CourseDiary;
    }
    set {
        SetPropertyValue("CourseDiary", ref _CourseDiary, value);
    }
}

Once you have complete this work, place the cursor in the test class name in the editor, right click and select “run tests”. All your tests should now run and pass, like so:

image

Now add the following test for the Course Object:

[TestMethod]
public void TestCourseHasName() {
    using (UnitOfWork uow = new UnitOfWork()) {
        Course course = new Course(uow);
        course.Name = "Test Course";

        Assert.IsNotNull(course.Name);
    }
}

[TestMethod]
public void TestCourseHasDescription() {
    using (UnitOfWork uow = new UnitOfWork()) {
        Course course = new Course(uow);
        course.Description = "Test course description";

        Assert.IsNotNull(course.Description);
    }
}

[TestMethod]
public void TestCourseHasDiary() {
    using (UnitOfWork uow = new UnitOfWork()) {
        Course course = new Course(uow);
        CourseDiary diary = new CourseDiary(uow);
        course.Diary = diary;
        
        Assert.IsNotNull(course.Diary);
    }
}

Add the appropriate properties to allow the tests to pass, remembering that CourseDiary has a 1:M relationship with Course which is expressed as follows on the Course:

private CourseDiary _Diary;
[Association("CourseDiary-Courses")]
public CourseDiary Diary {
    get {
        return _Diary;
    }
    set {
        SetPropertyValue("Diary", ref _Diary, value);
    }
}

and as follows on the CourseDiary:

[Association("CourseDiary-Courses")]
public XPCollection<Course> Courses {
    get {
        return GetCollection<Course>("Courses");
    }
}

Now that we have built our Course and our CourseDiary classes, it’s time to look at the Outage that we stubbed out in order to complete the CourseDiary class. Let’s go ahead and create tests and properties for Name, Description, StartDate and StopDate following the pattern above.

Now we can turn out attention to the constraints we had on our objects. One of constraints we had was that a Course had to have a CourseDiary. So to enforce that we can decorate the Diary property on the Course with the RuleRequiredField attribute, like so:

private CourseDiary _Diary;
[RuleRequiredField("Course must have Diary", DefaultContexts.Save)]
[Association("CourseDiary-Courses")]        
public CourseDiary Diary {
    get {
        return _Diary;
    }
    set {
        SetPropertyValue("Diary", ref _Diary, value);
    }
}

Having added a reference to the appropriate assembly (DevExpress.Persistent.Base.v8.2),  we can write a test to ensure that rule is enforced like this:

[TestMethod]
public void TestCourseMustHaveDiary() {
    using (UnitOfWork uow = new UnitOfWork()) {
        Course course = new Course(uow);
        RuleSet ruleSet = new RuleSet();
        RuleSetValidationResult rsvr = ruleSet.ValidateTarget(course, DefaultContexts.Save);
        Assert.AreEqual(false,rsvr.IsValid);
    }
}

Another constraint that we had was that we can’t delete a CourseDiary if it is referenced by a Course object. The first thing we have to do to enforce this is to add the RuleIsReferenced attribute to the CourseDiary class, like so:

[RuleIsReferenced("No delete if referenced",DefaultContexts.Delete,typeof(Course),"Diary",InvertResult=true)]

Notice the use of the InvertResult parameter which makes this rule “say” IsNotReferenced, which is what we want in this case. Now that we have decorated the class with this attribute, we can go ahead and write a test for it:

[TestMethod]
public void TestDiaryCantBeDeletedWhenReferenced() {
    using (UnitOfWork uow = new UnitOfWork())
    {
        Course course = new Course(uow);
        CourseDiary diary = new CourseDiary(uow);
        course.Diary = diary;
        RuleSet ruleSet = new RuleSet();
        RuleSetValidationResult rsvr = ruleSet.ValidateTarget(diary, DefaultContexts.Delete);
        Assert.AreEqual(false, rsvr.IsValid);
    }            
}

Another constraint that we had was that we cannot edit a CourseDiary in such a way as to invalidate a Booking. At this point in time we don’t have the Booking class as this UC does not cover it. In such a scenario the developer, using TDD, would seek to mock out the the Booking class. In this case, however, I feel that to go off into the realms of mocks might cloud what we are doing here (if anyone feels strongly that I’m wrong, feel free to comment and I will consider using mocks in a later post), it would certainly make this post, which is now reaching epic proportion, significantly longer.

So having decided to leave any dependency on the Booking class until we actually have that class, we are finished with our constraints. All that remains now is for us to add tests to Create, Edit and Delete our Course, CourseDiary and Outage classes (in the appropriate test classes of course) and we are done. I shan’t bother describing the tests here, they follow the pattern already set, I will link to the solution from this post and you can download and examine the these tests at your leisure.

Having created these tests, let’s run all the test to see if our code is still working

image

Seeing that it is, we are now safe to run our solution and see what happens:

image

Yep, seems to be working fine!

Okay so we’ve completed our use case and it’s time to ship our code to the testing server where our users can play with it. Is our code perfect? Well no, there are a lot of things I’ve left for our users to “find” and we will fix those in the refactoring phase. In the meantime, why don’t you leave a comment with the things that you find that need to be fixed? To do that you’ll need the solution of course. Have fun, and I’ll see you next time!

Technorati tags: ,
Digg This
Published Oct 10 2008, 04:04 PM by Gary Short (Developer Express)
Filed under:
Technorati tags: Real World App

Comments

 

Colin Mackay said:

Tut! tut! not specifying the culture on the date. All your North American readers won't get the TestDiaryHasEndDate to run properly. You need to read Guy Smith-Ferrier's book on Internationalisation.

October 10, 2008 12:16 PM
 

Colin Mackay said:

Tut! tut! not specifying the culture on the date. All your North American readers won't get the TestDiaryHasEndDate to run properly. You need to read Guy Smith-Ferrier's book on Internationalisation.

October 10, 2008 12:16 PM
 

Gary Short (Developer Express) said:

Good spot Colin, so there's one thing that'll need fixed in the refactoring :-)

October 10, 2008 12:52 PM
 

Your 2nd favourite developer said:

Aaah, now I see why you can't get there from here... from the TDD the primary concern is whether or not there is a course and not whether or not there are bookings in the diary for the course.  User logic could be:  The diary is only linked to the course, but there are no bookings, so why can't I delete it?

From the user point of view, deleting the bookings and then the course then allows you to delete the diary is an extra step they would find difficult to rationalise.  

BTW you wanted realism, so I thought I would be your PITA user :)

October 10, 2008 4:04 PM
 

Bernard Simmons said:

Something is missing...

I get the following exception:

Failed TestDiaryHasName RWA.Test Test method RWA.Test.UnitTest1.TestDiaryHasName threw exception:  DevExpress.Xpo.DB.Exceptions.UnableToOpenDatabaseException: Unable to open database. Connection string: 'Provider=Microsoft.Jet.OLEDB.4.0;Mode=Share Deny None;data source=C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\UnitTestAdapterDomain_ForC:\VS2008\RWA\TestResults\Bernard.Simmons_BERNARD-S 2008-10-10 15_02_30\Out\RWA.Test.mdb;user id=Admin;password=;'; Error: 'System.Data.OleDb.OleDbException: Not a valid file name.

...

but... if I add

           using (UnitOfWork uow = new UnitOfWork())  {

               uow.ConnectionString = "Integrated Security=SSPI;Pooling=false;Data Source=(local);Initial Catalog=Solution1";

....

All works just fine.

Using DevExpress 8.2.4

Otherwise this looks like it's going to be very helpful. Waiting for next post.

October 10, 2008 4:08 PM
 

Alain Bismark said:

your path is incorrect to mdb, check the C:\ appear twice

C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\UnitTestAdapterDomain_ForC:\VS2008\RWA\TestResults\Bernard.Simmons_BERNARD-S 2008-10-10 15_02_30\Out\RWA.Test.mdb

October 11, 2008 7:46 PM
 

drew.. said:

Adding to the pita category.. this TDD is something that i don't groove on. I mean seriously, are you going to add a test for every single property you add? I guess being a small shop where minimizing admin overhead is important, i have a certain take on this, but this just seems wasteful. No disrespect is intended Gary, it is only TDD i am not liking.

October 12, 2008 11:24 PM
 

Bernard Simmons said:

Alain,

I know that the path is incorrect but, I did not specify this connection string. I just followed Gary's steps, step by step. Nor can I find in my code where it's specified. Now, there must be something on my system that is corrupted. Any ideas where to look for this?

Bernard

October 13, 2008 10:16 AM
 

Gary Short (Developer Express) said:

Don't worry Drew, no disrespect taken. :-)

As for TDD and overhead; well I guess we just don't see it the same way. I'll have a test for every public property and method, and I'll not see it as an overhead 'cos you have to test your code regardless, right?

Further, using TDD means I'm not tempted to over-engineer my project, as I only write the code that will satisfy the tests. This means that projects are simplified and are therefore easier to maintain, and that code is more granular which promotes reuse. This is where the true overhead reduction happens, and why small shops, like yours, should embrace TDD.

October 14, 2008 6:01 AM
 

Robert said:

Gary,

I'm new to TDD and can see the benefits but I can also see that it doesn't necessarily mean that you won't over engineer the project. (just each bit of it)  Each of us would approach a project from our own prospective and could come up with a different solution.  I look at what you have done so far and I would probably eliminate the CourseDiary and just make some of its properties part of the course itself or even what I would call the Course Calendar.  But that is where having multiple sets of eyes / inputs come into play.  And why requirements gathering is so important.  Perhaps it would be better to say that TDD helps to deliver the minimal amount of code to fulfill the requirements?

Keep up the good work!

Thanks,

Robert

October 14, 2008 11:16 AM
 

Gary's Blog said:

In my last post we created the code to satisfy the first use case and JIT design document. Now that our

October 14, 2008 12:17 PM
 

drew.. said:

thanks Gary, but.. and i stress *but*, using CodeRush, and the simple paradigm we have for properties that are table fields at the end of the day, makes this level of TDD seem like commenting every line of code. To carry the TDD paradigm a step further, can we really be sure our tests are correct? Perhaps a test of the tests?  ;)  I am stretching this of course, but as for basic properties, i would rather trust instinct and proper coding than to create tests for each. Personal opinion i guess, and i am always willing to re-entertain thoughts as time goes on.. cheers.

October 14, 2008 6:02 PM
 

Roman Eremin said:

Drew,

try to look at the test code as an answer to question "how will i know when i'm done?"

You might end up with testing of "obvious" things like "course should have an assignable name", but this is good - this is your specification and documentation with one additional benefit - it will tell you when something is broken.

Regarding spending time on tests - I prefer spending time here then later in the debugger.

October 15, 2008 6:05 AM
 

Garth Henderson said:

A couple of wishlist items for RWA:

1) It would be very helpful if identical functionality would be implemented in both win/web apps rather than separate functionality.

2) To include event driven scheduling.  A golf course must manage tee times and available slots.  This is similar to inventory.

3) To manage accounting with event processing.  Sometimes guests will prepay and other times they will only leave a deposit.  When they complete their game, they may have additional fees and equipment deposit returns.  There is an inventory of carts, clubs, etc. that needs to be managed.  

As an FYI, most golf reservation systems also include GPS functionality to track where the party on the course during the game.

October 20, 2008 3:06 PM

Leave a Comment

(required)  
(optional)
(required)  
Verification code: Required
   
Add
Copyright © 1998-2008 Developer Express Inc.
ALL RIGHTS RESERVED