Login or Sign Up to become a member!
LessThanDot Sit Logo

LessThanDot

Architecture, Design & Strategy

Less Than Dot is a community of passionate IT professionals and enthusiasts dedicated to sharing technical knowledge, experience, and assistance. Inside you will find reference materials, interesting technical discussions, and expert tips and commentary. Once you register for an account you will have immediate access to the forums and all past articles and commentaries.

LTD Social Sitings

Lessthandot twitter Lessthandot Linkedin Lessthandot friendfeed Lessthandot facebook Lessthandot rss

Note: Watch for social icons on posts by your favorite authors to follow their postings on these and other social sites.

Your profile

    Search

    XML Feeds

    Google Ads

    « Using the structuremap Automockerautomapping fluent nhibernate and VB.Net »
    comments

    Lets start with the requirements.

    • User enters data of Person
    • User is only allowed to enter LastName and FirstName.
    • User is only allowed 30 characters per field.
    • Fields are not allowed to be empty

    Seems simple enough.

    Lets start by creating some unittests to verify the requirements.

    1. [Test]
    2.         public void If_person_LastName_property_sets_and_returns_correct_value()
    3.         {
    4.             Person person = new Person();
    5.             person.LastName = "Test";
    6.             Assert.AreEqual("Test", person.LastName);
    7.         }
    8.  
    9. [Test]
    10.         [ExpectedException(ExceptionName = "System.Exception", ExpectedMessage = "The LastName can not be more than 30 characters long")]
    11.         public void If_person_LastName_property_cannot_be_more_than_30_characters()
    12.         {
    13.             Person person = new Person();
    14.             person.LastName = "TestTestTestTestTestTestTestTest";
    15.         }
    16.  
    17.         [Test]
    18.         [ExpectedException(ExceptionName = "System.Exception", ExpectedMessage = "The LastName can not be empty")]
    19.         public void If_person_LastName_property_cannot_be_empty()
    20.         {
    21.             Person person = new Person();
    22.             person.LastName = "";
    23.         }
    24.  
    25.         [Test]
    26.         [ExpectedException(ExceptionName = "System.Exception", ExpectedMessage = "The LastName can not be empty")]
    27.         public void If_person_LastName_property_cannot_be_null()
    28.         {
    29.             Person person = new Person();
    30.             person.LastName = null;
    31.         }
    32.  
    33.         [Test]
    34.         public void If_person_FirstName_property_sets_and_returns_correct_value()
    35.         {
    36.             Person person = new Person();
    37.             person.FirstName = "Test";
    38.             Assert.AreEqual("Test", person.FirstName);
    39.         }
    40.  
    41.         [Test]
    42.         [ExpectedException(ExceptionName = "System.Exception", ExpectedMessage = "The FirstName can not be more than 30 characters long")]
    43.         public void If_person_FirstName_property_cannot_be_more_than_30_characters()
    44.         {
    45.             Person person = new Person();
    46.             person.FirstName = "TestTestTestTestTestTestTestTest";
    47.         }
    48.  
    49.         [Test]
    50.         [ExpectedException(ExceptionName = "System.Exception", ExpectedMessage = "The FirstName can not be empty")]
    51.         public void If_person_FirstName_property_cannot_be_empty()
    52.         {
    53.             Person person = new Person();
    54.             person.FirstName = "";
    55.         }
    56.  
    57. [Test]
    58.         [ExpectedException(ExceptionName = "System.Exception", ExpectedMessage = "The FirstName can not be empty")]
    59.         public void If_person_FirstName_property_cannot_be_null()
    60.         {
    61.             Person person = new Person();
    62.             person.FirstName = null;
    63.         }

    And hey presto we have a bunch of test that won't go anywhere ;-). So lets make them red shall we meaning we want the thing to at least compile.

    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Text;
    5.  
    6. namespace RaisingEvents.DomainModel
    7. {
    8.     public class Person
    9.     {
    10.         private string lastName;
    11.         private string firstName;
    12.  
    13.         public Person()
    14.         {
    15.         }
    16.  
    17.         public string LastName
    18.         {
    19.             get { }
    20.             set { }
    21.         }
    22.  
    23.         public string FirstName
    24.         {
    25.             get { }
    26.             set { }
    27.         }
    28.     }
    29. }

    Nice red all the way.

    Now lets skip a few (more than a few actually but I'm not writing a book about TDD) steps and make them all green.

    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Text;
    5.  
    6. namespace RaisingEvents.DomainModel
    7. {
    8.     public class Person
    9.     {
    10.         private string lastName;
    11.         private string firstName;
    12.  
    13.         public Person()
    14.         {
    15.         }
    16.  
    17.         public int Id
    18.         {
    19.             get { return id; }
    20.             set { id = value; }
    21.         }
    22.  
    23.         public string LastName
    24.         {
    25.             get { return lastName; }
    26.             set
    27.             {
    28.                 if (String.IsNullOrEmpty(value)) { throw new Exception("The LastName can not be empty"); }
    29.                 if (value.Length > 30) { throw new Exception("The LastName can not be more than 30 characters long"); }
    30.                 lastName = value;
    31.             }
    32.         }
    33.  
    34.         public string FirstName
    35.         {
    36.             get { return firstName; }
    37.             set
    38.             {
    39.                 if (String.IsNullOrEmpty(value)) { throw new Exception("The FirstName can not be empty"); }
    40.                 if (value.Length > 30) { throw new Exception("The FirstName can not be more than 30 characters long"); }
    41.                 firstName = value;
    42.            
    43.             }
    44.         }
    45.     }
    46. }

    Every test goes green. SO we are all happy and exited because we followed the requirements and our domain object is correct... or so we think.

    And we thought wrong, we didn't read the requirements correctly. Or... the above unittests don't reflet the requirements correctly. Why? I hear you think. Because we allowed invalid state to kreep into our little model. LastName nor FirstName should ever be empty, I repeat ever.

    Lets add this test

    1. [Test]
    2.         public void If_person_FirstName_property_cannot_be_null_when_LastName_is_filled()
    3.         {
    4.             Person person = new Person();
    5.             person.LastName = "test";
    6.             Assert.IsNotNull(person.FirstName);
    7.             Assert.Less( person.FirstName.Length,30);
    8.             Assert.AreNotEqual("", person.FirstName);
    9.         }
    10.  
    11.         [Test]
    12.         public void If_person_LastName_property_cannot_be_null_when_firstname_is_filled()
    13.         {
    14.             Person person = new Person();
    15.             person.FirstName = "test";
    16.             Assert.IsNotNull(person.LastName);
    17.             Assert.Less(person.LastName.Length,30);
    18.             Assert.AreNotEqual("", person.LastName);
    19.         }

    That test fails because I am able to set a LastName and not have any firstname. Ofcourse I could wait for the domainobject to be persisted and then find out it was wrong but then that would defeat the purpose of making a Domainmodel. The model should be able to retain a valid state at all times.

    So lets change our DomainModel to reflect the requirements a bit better.

    And for me that would be this.

    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Text;
    5.  
    6. namespace RaisingEvents.DomainModel
    7. {
    8.     public class Person
    9.     {
    10.         private string lastName;
    11.         private string firstName;
    12.  
    13.         public Person(string LastName, string FirstName)
    14.         {
    15.             this.LastName = LastName;
    16.             this.FirstName = FirstName;
    17.         }
    18.  
    19.         public string LastName
    20.         {
    21.             get { return lastName; }
    22.             set
    23.             {
    24.                 if (String.IsNullOrEmpty(value)) { throw new Exception("The LastName can not be empty"); }
    25.                 if (value.Length > 30) { throw new Exception("The LastName can not be more than 30 characters long"); }
    26.                 lastName = value;
    27.             }
    28.         }
    29.  
    30.         public string FirstName
    31.         {
    32.             get { return firstName; }
    33.             set
    34.             {
    35.                 if (String.IsNullOrEmpty(value)) { throw new Exception("The FirstName can not be empty"); }
    36.                 if (value.Length > 30) { throw new Exception("The FirstName can not be more than 30 characters long"); }
    37.                 firstName = value;
    38.            
    39.             }
    40.         }
    41.     }
    42. }

    No more default constructor.

    So that means none of my tests will run anymore.

    I have to change them to this.

    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Text;
    5. using NUnit.Framework;
    6.  
    7. namespace RaisingEvents.DomainModel.Tests
    8. {
    9.     [TestFixture]
    10.     public class TestPerson
    11.     {
    12.         [Test]
    13.         public void If_person_LastName_property_sets_and_returns_correct_value()
    14.         {
    15.             Person person = new Person("LastName","FirstName");
    16.             person.LastName = "Test";
    17.             Assert.AreEqual("Test", person.LastName);
    18.         }
    19.  
    20.         [Test]
    21.         [ExpectedException(ExceptionName = "System.Exception", ExpectedMessage = "The LastName can not be more than 30 characters long")]
    22.         public void If_person_LastName_property_cannot_be_more_than_30_characters()
    23.         {
    24.             Person person = new Person("LastName", "FirstName");
    25.             person.LastName = "TestTestTestTestTestTestTestTest";
    26.         }
    27.  
    28.         [Test]
    29.         [ExpectedException(ExceptionName = "System.Exception", ExpectedMessage = "The LastName can not be empty")]
    30.         public void If_person_LastName_property_cannot_be_empty()
    31.         {
    32.             Person person = new Person("LastName", "FirstName");
    33.             person.LastName = "";
    34.         }
    35.  
    36.         [Test]
    37.         [ExpectedException(ExceptionName = "System.Exception", ExpectedMessage = "The LastName can not be empty")]
    38.         public void If_person_LastName_property_cannot_be_null()
    39.         {
    40.             Person person = new Person("LastName", "FirstName");
    41.             person.LastName = null;
    42.         }
    43.  
    44.         [Test]
    45.         public void If_person_FirstName_property_sets_and_returns_correct_value()
    46.         {
    47.             Person person = new Person("LastName", "FirstName");
    48.             person.FirstName = "Test";
    49.             Assert.AreEqual("Test", person.FirstName);
    50.         }
    51.  
    52.         [Test]
    53.         [ExpectedException(ExceptionName = "System.Exception", ExpectedMessage = "The FirstName can not be more than 30 characters long")]
    54.         public void If_person_FirstName_property_cannot_be_more_than_30_characters()
    55.         {
    56.             Person person = new Person("LastName", "FirstName");
    57.             person.FirstName = "TestTestTestTestTestTestTestTest";
    58.         }
    59.  
    60.         [Test]
    61.         [ExpectedException(ExceptionName = "System.Exception", ExpectedMessage = "The FirstName can not be empty")]
    62.         public void If_person_FirstName_property_cannot_be_empty()
    63.         {
    64.             Person person = new Person("LastName", "FirstName");
    65.             person.FirstName = "";
    66.         }
    67.  
    68.         [Test]
    69.         [ExpectedException(ExceptionName = "System.Exception", ExpectedMessage = "The FirstName can not be empty")]
    70.         public void If_person_FirstName_property_cannot_be_null()
    71.         {
    72.             Person person = new Person("LastName", "FirstName");
    73.             person.FirstName = null;
    74.         }
    75.  
    76.         [Test]
    77.         public void If_person_FirstName_property_cannot_be_null_when_LastName_is_filled()
    78.         {
    79.             Person person = new Person("LastName", "FirstName");
    80.             person.LastName = "test";
    81.             Assert.IsNotNull(person.FirstName);
    82.             Assert.Less( person.FirstName.Length,30);
    83.             Assert.AreNotEqual("", person.FirstName);
    84.         }
    85.  
    86.         [Test]
    87.         public void If_person_LastName_property_cannot_be_null_when_firstname_is_filled()
    88.         {
    89.             Person person = new Person("LastName", "FirstName");
    90.             person.FirstName = "test";
    91.             Assert.IsNotNull(person.LastName);
    92.             Assert.Less(person.LastName.Length,30);
    93.             Assert.AreNotEqual("", person.LastName);
    94.         }
    95.     }
    96. }

    Now I'm more satisfied with my test meeting the requirements.

    So the lesson of the day. Always think carefuly and read carefuly what it says on the package.

    Is this the best and only way of doing things? No way. I'm sure we can come up with beter ways. What if we need an empty constructor for serializing? Is this still POCO? No because of the lack of empty constructor. But sometimes we need to compromise a little to get work done or we use less rigid DTO objects in the rest of the application. I wasn't looking for a compromise. Just a way to meet the requirements.

    About the Author

    User bio imageChristiaan is a forensic technician who programs on the side, although my function description says that I do IT-things for 90% of the time . I'm an avid VB.NET fan and I use lots of the ALT.Net techniques, like unit-testing, nhibernate, logging, IoC, ...
    Social SitingsTwitterLinkedInHomePageLTD RSS Feed
    c#, ddd
    Instapaper

    4 comments

    Comment from: Fredrik Kalseth [Visitor] · http://iridescence.no
    Fredrik Kalseth If you're concerned about keeping the object in a valid state at all times, instead of allowing the user to change Firstname and Lastname separately you could introduce a changeName(string firstname, string lastname) method. Alternatively, you could create a value object called Name, so that to change the name you would do:

    person.Name = new Name("Name", "Surname");
    02/03/09 @ 03:45
    Comment from: Christiaan Baes (chrissie1) [Member]
    Christiaan Baes (chrissie1) Yep, All possible and valid solutions to the same problem.
    02/03/09 @ 05:58
    Comment from: Michael Smith [Visitor]
    Michael Smith +1 on Frederik's suggestion. Name is a value object and it should be impossible to create it in an invalid state.
    02/04/09 @ 03:13
    Comment from: Christiaan Baes (chrissie1) [Member]
    Christiaan Baes (chrissie1) But in this case Person is already the value object in a sense, since it only contains name and firstname. Creating another object in this case just seems overkill. And would actualy be a copy of the person object. So the solution for the Name value object would be the same as for the Person object.

    Perhaps I should have called it Name object in the firstplace ;-).

    02/04/09 @ 03:45

    Leave a comment


    Your email address will not be revealed on this site.

    Your URL will be displayed.
    (Line breaks become <br />)
    (Name, email & website)
    (Allow users to contact you through a message form (your email will not be revealed.)