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:
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
Seeing that it is, we are now safe to run our solution and see what happens:
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!