Favour Unit Tests Over Encapsulation

 

Recently I came across the below method. Take a quick look. Do you understand what it does?

  private int CalculateStreak(int streak, IEnumerable<StudentQuiz> quizzes)
  {
     var thisWeekFirstDay = _dtSvc.GetStartOfWeek(_dtSvc.UtcNow);
     var lastWeekQuizzeDates = quizzes.Where(q => q.startedAt >= thisWeekFirstDay.AddDays(-7) && q.startedAt < thisWeekFirstDay)
                                      .GroupBy(q => q.startedAt.Date);
     var thisWeekQuizzeDates = quizzes.Where(q => q.startedAt >= thisWeekFirstDay)
                                      .GroupBy(q => q.startedAt.Date);

     var lastWeekContinuity = lastWeekQuizzeDates.Count();
     var thisWeekContinuity = thisWeekQuizzeDates.Count();

     if (lastWeekContinuity < STREAK_CONTINUITY) streak = 0;
     if (thisWeekContinuity >= STREAK_CONTINUITY) streak++;

     return streak;
  }

The method originates from a service class in an eLearning system. It determines the number of consecutive weeks, a ‘streak’, in which a student has completed at least one quiz. The system quizzes students to test how well they are progressing in their subject. Ideally, students should do quizzes every week but may choose not to. If they have missed a week, they have ‘broken’ their streak, and the streak length will be reset to zero. If they have added another week of completing quizzes, the length of their streak is incremented by one.

The eLearning company has decided to change how streaks are calculated. Unfortunately, this method had no unit tests. Tests are great for ensuring we haven’t broken existing functionality. A useful suite of unit tests lets us know whenever we break existing application behaviour. 

Before making our modifications to this method, we were going to characterise the existing behaviour with unit tests. However, we hit a snag right away: The method is declared as private. We won’t be able to access it in a unit test.

What are we to do?

Simple. We change the method to be public. Now we have access and can write our unit tests.

Hold On. Doesn’t that break Encapsulation?

Yes, it most certainly does. 

Why do we have encapsulation? Well-encapsulated code clarifies the behaviour of a system. For example, a method exposes its signature, i.e. method name, return value and parameters. The detail of how the method does its work is hidden, i.e. encapsulated, in the method body. 

Breaking encapsulation to allow for unit testing does make it slightly harder to reason about the behaviour of the system. However, the unit tests make up for this loss. Explanatory unit tests characterise the action of the system and let us reason about our code more directly. Once we have unit tests in place, we can safely change the structure of the code to reintroduce and even improve encapsulation. For example, rather than retain the streak calculation code in a service class with many responsibilities, we could move it to a new StreakCalculator class. A temporary suspension of strict encapsulation to introduce unit tests will have enabled a superior program structure and encapsulation.

Favour unit tests over encapsulation.

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply