DDD & TDD. part II

Disposition

So we have domain classes with minimal set of properties and methods. Also we have several tests on these classes. The goal is to learn how deal with domain classes – i.e. write services which will do complex calculations and modifications. For example count students in selected course.

Domain services

Assume that one of the tasks is count classes for selected course and how many students in them.

Let’s think which domain classes known about each other:

  • “School” knows how many “Class”es it contains.
  • “Class” knows how many students it contains.

I think it will be enough for now. What is the easiest way that will implement most of novice? Add to “School” method:

public int CountStudent(int classNumber) { … }

But it is a bad idea because of next reasons:

  • It leads to growing responsibilities of “School” class and the result is coupling;
  • Became harder to maintain and change “School”;
  • Also can appear issues for test writing.

Growing class’ responsibilities is always bad, because it’s harder and harder to stop this tendency and it became uncontrolled (anti pattern “Superclass”).  If you scared to change code and think in a way “it works – don’t touch it”, it is signal that something wrong in program. I suppose that in this case class can be scrolled up to 5 and more screens. Also it’s hard to test strong coupling classes. The reason is that

  • you have to write a lot of code that prepare data for test passing;
  • It’s easy to lost meaningful part of preparing, the part that we want to test.

Maybe you will remember what are you tried to test at the last week, but I bet you forget it in a month.

From my point of view, more reasonable is to create separate class with one method for now, that will accept year of study. I suggest naming this class “ClassStatistics”.

public class ClassStatistics {
     public int CountClassesForYear(School school, int classYear) {
          return -1;
     }
}

This class doesn’t store any data. Completely. All what is need for work are passed as parameters. We have a class that serve other classes. That’s why they are called “Services”. =)

Please note that the method CountClassesForYear returns impossible in real life value; This feature is necessary for test writing. Usually you should avoid situation when you write test and it become green automatically.

Domain Service Testing

Thus, let’s begin writing test on domain service. Also we’ll see how suitable name and notation of our classes and methods. Create new class and rename to ClassStatisticsTests. Try to give name to test in a way that helps you to understand what was broken if test become red.

[TestMethod]
public void ShouldCalculateCountClassesForYear() { … }

It’s useful to write Assert statement first. This practice allows you to concentrate on a test’s goal and avoid trash variables. So write Assert.<condition> just after you’ll write test a method header.

[TestMethod]
public void ShouldCalculateCountClassesForYear() {
    Assert.AreEqual(0, new ClassStatistics().CountClassesForYear(new School(), 0));
    Assert.AreEqual(2, new ClassStatistics().CountClassesForYear(new School(), 1));
}

I expect that there are zero classes for zero study year and two classes for first year students.

At right this moment, when I write code for test, unreal laziness strike me and I don’t want to test that somebody will use dumb zero study year. And also it’s not clear when I read test again. By the way there come options that somebody can pass as stud y year like 145 or -8. Actually it must be tested also, all these scenarios. Just think how many test and verifications you have to implement. Imagine? Also very lazy to do it?

I suggest refactoring code immediately – creating an enumerator.

public enum EducationYear {
        _01 = 1,
        _02,
        _03,
        _04,
        _05,
        _06,
        _07,
        _08,
        _09,
        _10,
        _11
    }

I want to see methods call like this:

new ClassStatistics().CountClassesForYear(new School(), EducationYear._01)

From my perspective it’s much better! Following this way I can’t pass -5 or 182 study year. Good data passes always and as a benefit easy to read and understand. Change CountClassesForYear signature to get parameter as EducationYear type.

Another one evidence of test’s useful! But at the beginning it all seems to were so logic and good. Try to create all public methods in context of test writing.

Now test looks like this:

[TestMethod]
public void ShouldCalculateCountClassesForYear() {
    var school = new School();

    school.AddClass(new Class{EducationYear = EducationYear._01});
    school.AddClass(new Class{EducationYear = EducationYear._01});
    school.AddClass(new Class{EducationYear = EducationYear._02});
    school.AddClass(new Class{EducationYear = EducationYear._03});

    Assert.AreEqual(2, new ClassStatistics().CountClassesForYear(school, EducationYear._01));
}

Run test, check test’s answer that number of classes with first year students are not equal to 2. To make it green we have to write implementation. Write the most simple implementation that possible to make test green.  Don’t try to guess what you need in future, task and tests tells you.

public int CountClassesForYear(School school, EducationYear classYear) {
    return 2;
}

Launch test. It works, test is green! It shows us that test was wrote inaccurate. We have to check out another value that can proove calculation origin of the result, not a hardcoded number. Add one more Assert.

Assert.AreEqual(1, new ClassStatistics().CountClassesForYear(school, EducationYear._02));

Run test, result is red. You have to write real implementation without any ruses.

public int CountClassesForYear(School school, EducationYear classYear) {
    return (from c in school.Classes
              where c.EducationYear == classYear
              select c).Count();
}

Again run test and finaly it’ll became green. Great, that means that everything calculating right. After each new test I advice you to run all tests in solution or at least all tests in the same class. Doing this you can find possible erorrs on the very early stage.

The next task is to count students in classes for one education year.  Begin write code from a test. (To be honest it’s quite difficult in psychological aspect to write test on nonexistence class. But much easier when class already exists and there are at least one method.)

[TestMethod]
public void ShouldCalculateStudentsInClassesForYear() {
    var school = new School();

    var class1A = new Class{EducationYear = EducationYear._01};
    var class1B = new Class{EducationYear = EducationYear._01};
    var class2A = new Class{EducationYear = EducationYear._02};

    school.AddClass(class1A);
    school.AddClass(class1B);
    school.AddClass(class2A);

    var student = new Student("", "");
    class1A.AddStudent(student);
    class1A.AddStudent(student);
    class1B.AddStudent(student);
    class2A.AddStudent(student);

    var statistics = new ClassStatistics();
    Assert.AreEqual(3, statistics.CountStudentsForYear(school, EducationYear._01));
    Assert.AreEqual(1, statistics.CountStudentsForYear(school, EducationYear._02));
}

Choose the name for the new method and if you feel satisfaction with selected name, create method with the same signature as previouse one.

public int CountStudentsForYear(School school, EducationYear classYear) {
    return -1;
}

With presented implementation test is red, and numbers in assert don’t match. It’s good, it’s expected error. If we receive NullReferenceExeption or something like that it means we have to pay additional attention to test initialization.

Write the method implementation and run test again.

public int CountStudentsForYear(School school, EducationYear classYear) {
    return (from c in school.Classes
              where c.EducationYear == classYear
              select c).Sum(c=> c.Students.Count);
}

Test is green! We are stars! =) Look closer to the both methods and you’ll see that there is main part that can be extracted. Here we go, it called refactoring – improving readability and reduce copy-paste code. Extract similar part in private method.

private IEnumerable<Class> GetClassesForYear(School school, EducationYear classYear) {
    return from c in school.Classes
             where c.EducationYear == classYear
             select c;
}

Now methods can be writen in single row and became more understandable. It’s a pure english!

public int CountClassesForYear(School school, EducationYear classYear) {
    return GetClassesForYear(school, classYear).Count();
}

public int CountStudentsForYear(School school, EducationYear classYear) {
    return GetClassesForYear(school, classYear).Sum(c=> c.Students.Count);
}

After the code changing, it will be nice to check out that methods works as was planned. Run all tests. Tests are green. Right now you’ve performed full classic cycle of test writing: Write test, it’s red, fix, the test is green, refactoring, green again. The code became straight and nice looking, and you don’t worry about these methods.

You can continue implementing tasks in same way.

Resume:

Domain classes – contains concret information about stuff, that play a main role in application. They are represents noun from the job description.

Domain services – serve domain classes, perform complex calculation, data transformation and so on. They can return results to each other, run in sequence, whatever you want! All core logic executed in domain services.

A few more words about process organization

I think that it’s not a news for you, but anyway, I’d like to advise you to create project’s folder (Add > New Folder) for services and any common parts of code. The same is for Test project. Create folder Domain and subfolder Services. Most likely that you’ll have tests on another application part in the Test project and such organization will help you to find necessary class easily.

When you note that you prepare same stuff for different tests within one test class, you should extract them to separate method and marl it with attribute [TestInitialized]. This method will run before each test within test class.

[TestInitialize]
public void TestInitialize() {
    //your test init
}

In the next part…

I would tell you how domain linked with GUI, with a data layer. Draw the whole picture of interaction between application’s libraries/modules and how to test this interaction.

Source code

Question and wishes are welcome!

Hard’n’heavy!

Tagged , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>