I like this convention: http://haacked.com/archive/2012/01/02/structuring-unit-tests.aspx/

However, the results are not optimal.


public class NewTaskStartDateCalculatorFacts
{
   public class TheGetMethod
   {
        public class WhenNoTasksExists
        {
            [Theory, AutoData]
            public void ReturnedValueDatePartEqualsToday(DateTime today)
            {
                 var builder = new NewTaskStartDateCalculator.Builder();
                 var calculator = builder
                                     .WithToday(today)
                                     .Build();
                 var startDate = calculator.Get(); 

                 Assert.Equal(today.Date, startDate.Date);
           }
        }

        public class WhenOneTaskExists
        {
              public class WhenUseTimeIsOn
              {
                   [Theory, AutoData]
                   public void ReturnedValueDatePartEqualsExistingTaskStartDateDatePart(
                                   DateTime task1StartDate )
                   {
                         var builder = new NewTaskStartDateCalculator.Builder();
                         var calculator = builder
                                             .WithKnownTasksStartDates( new List<DateTime> { task1StartDate } )
                                             .WithUseTime()
                                             .Build();

                          var startDate = calculator.Get();

                          Assert.Equal( task1StartDate.Date, startDate.Date );
                    }
              }

         public class WhenUseTimeIsOff
         {
              public static IEnumerable<object[]> TestData1 => new[]
                            {
                                 new object[] { new DateTime( 2017, 1, 2 ), WorkingDays.Standard, new DateTime( 2017, 1, 3 ) }
                             };

             [Theory]
             [MemberData(nameof(TestData1))]
             public void ReturnedValueDatePartEqualsExistingTaskStartDateAdvancedToTheNextWorkingDayDatePart(
                             DateTime task1StartDate,
                             WorkingDays workingDays,
                             DateTime nextWorkingDay)
             {
                     var fakeWorkingDaysProcessor = A.Fake<IWorkingDaysProcessor>();
                     A.CallTo(
                           () => fakeWorkingDaysProcessor.GetNextWorkingDay(task1StartDate, workingDays)
                     ).Returns(nextWorkingDay); 

                     var builder = new NewTaskStartDateCalculator.Builder();
                     var calculator = builder
                                         .WithKnownTasksStartDates(new List<DateTime> { task1StartDate })
                                         .WithWorkingDaysConfiguration(workingDays)
                                         .WithWorkingDaysProcessor(fakeWorkingDaysProcessor)
                                         .Build();

                     var startDate = calculator.Get();

                     Assert.Equal(nextWorkingDay.Date, startDate.Date);
                }
           }
      }

       public class WhenIsPlusEdition
       {
           [Theory, AutoData]
           public void ReturnedValueTimePartEqualsConfiguredWorkdayStart(
                          DateTime today, TimeSpan workDayStart)
           {
                // TODO: the TimeSpan object has milliseconds.
                // Implement a mechanism to generate TimeSpan objects without milliseconds.
                // In addition, analyze the design to ensure the builder properly handles
                // the situation where the input TimeSpan has milliseconds (should it
                // silently cut the milliseconds or throw an exception?)
                workDayStart = workDayStart.WithZeroMilliseconds();
                var builder = new NewTaskStartDateCalculator.Builder();
                var calculator = builder
                                    .WithToday(today)
                                    .WithWorkDayStart(workDayStart)
                                    .WithLicenseIsPlusEdition()
                                    .Build();

                 var startDate = calculator.Get();

                 Assert.Equal(workDayStart, startDate.TimeOfDay);
            }

       }

        public class WhenIsFreeEdition
        {
              [Theory, AutoData]
              public void ReturnedValueTimePartEquals_12_00_AM(DateTime today, DateTime task1StartDate, DateTime fakeNextWorkingDate)
              {
                   var fakeWorkingDaysProcessor = A.Fake<IWorkingDaysProcessor>();
                   A.CallTo(
                       () => fakeWorkingDaysProcessor.GetNextWorkingDay(A<DateTime>.Ignored, A<WorkingDays>.Ignored)
                   ).Returns(fakeNextWorkingDate);

                   var builder = new NewTaskStartDateCalculator.Builder();
                   var calculator = builder
                                       .WithKnownTasksStartDates(new List<DateTime> { task1StartDate })
                                       .WithWorkingDaysProcessor(fakeWorkingDaysProcessor)
                                       .Build();

                   var startDate = calculator.Get();

                   Assert.Equal(DateTimeExtensions.DayStart, startDate.TimeOfDay);
              }
        }
    }
}

An alternative I’m considering is simply naming each test using the schema Fact{number}.


public class NewTaskStartDateCalculatorFactsv2
{
     ///
     ///  <summary>
     /// When:
     /// - no tasks exist
     /// Then:
     /// - resulted "start date date part" is "today (as in DateTime.Today)".
     /// </summary>
     [Theory, AutoData]
     public void Fact001( DateTime today )
     {
           var builder = new NewTaskStartDateCalculator.Builder();
           var calculator = builder
                               .WithToday( today )
                               .Build();
var startDate = calculator.Get();

            Assert.Equal( today.Date, startDate.Date );
     }

     ///
     /// <summary>
     /// When:
     /// - one task exists
     /// And
     /// - "use time" is on
     /// Then
     /// - resulted "start date date part" equals "existing task start date date part"
     /// </summary>
     [Theory, AutoData]
     public void Fact002( DateTime task1StartDate )
     {
         var builder = new NewTaskStartDateCalculator.Builder();
         var calculator = builder
                             .WithKnownTasksStartDates( new List<DateTime> { task1StartDate } )
                             .WithUseTime()
                             .Build();

         var startDate = calculator.Get();

         Assert.Equal( task1StartDate.Date, startDate.Date );
     }

     public static IEnumerable<object[]> TestData1 => new[]
     {
          new object[] { new DateTime( 2017, 1, 2 ), WorkingDays.Standard, new DateTime( 2017, 1, 3 ) }
     };
///
     ///<summary>
     /// When:
     /// - one task exists
     /// And
     /// - "use time" is off
     /// Then:
     /// - resulted "start date date part" equals "existing start date advanced to the next working day" "date part"
     /// </summary>
     [Theory]
     [MemberData( nameof( TestData1 ) ) ]
     public void Fact003(
                  DateTime task1StartDate,
                  WorkingDays workingDays,
                  DateTime nextWorkingDay )
     {
         var fakeWorkingDaysProcessor = A.Fake<IWorkingDaysProcessor>();
         A.CallTo(
               () => fakeWorkingDaysProcessor.GetNextWorkingDay( task1StartDate, workingDays )
         ).Returns( nextWorkingDay );

         var builder = new NewTaskStartDateCalculator.Builder();
         var calculator = builder
                              .WithKnownTasksStartDates(new List<DateTime> { task1StartDate })
                              .WithWorkingDaysConfiguration( workingDays )
                              .WithWorkingDaysProcessor( fakeWorkingDaysProcessor )
                              .Build();

         var startDate = calculator.Get();

         Assert.Equal( nextWorkingDay.Date, startDate.Date );
     }

     ///
     ///<summary>
     /// When
     /// - license is plus
     /// Then
     /// - resulted "start date time part" equals "configured workday start"
     /// </summary>
     /// <param name="today"></param>
     /// <param name="workDayStart"></param>
     [Theory, AutoData ]
     public void Fact004(
            DateTime today, TimeSpan workDayStart )
     {
          // TODO: the TimeSpan object has milliseconds.
          // Implement a mechanism to generate TimeSpan objects without milliseconds.
          // In addition, analyze the design to ensure the builder properly handles
          // the situation where the input TimeSpan has milliseconds (should it
          // silently cut the milliseconds or throw an exception?)
          workDayStart = workDayStart.WithZeroMilliseconds();
          var builder = new NewTaskStartDateCalculator.Builder();
          var calculator = builder
                             .WithToday( today )
                             .WithWorkDayStart( workDayStart )
                             .WithLicenseIsPlusEdition()
                             .Build();

          var startDate = calculator.Get();

          Assert.Equal( workDayStart, startDate.TimeOfDay );
      }

      ///
      ///<summary>
      /// When
      /// - license is Free
      /// Then
      /// - resulted "start date time part" equals "12:00 AM"
      /// </summary>
      [Theory, AutoData]
      public void Fact005( DateTime today, DateTime task1StartDate, DateTime fakeNextWorkingDate )
      {
           var fakeWorkingDaysProcessor = A.Fake<IWorkingDaysProcessor>();
           A.CallTo(
                () => fakeWorkingDaysProcessor.GetNextWorkingDay( A<DateTime>.Ignored, A<WorkingDays>.Ignored )
           ).Returns( fakeNextWorkingDate );

           var builder = new NewTaskStartDateCalculator.Builder();
           var calculator = builder
                               .WithKnownTasksStartDates( new List<DateTime>{ task1StartDate } )
                               .WithWorkingDaysProcessor( fakeWorkingDaysProcessor )
                               .Build();

           var startDate = calculator.Get();

           Assert.Equal( DateTimeExtensions.DayStart, startDate.TimeOfDay );
      }
}

Another option:

public class NewTaskStartDateCalculatorWhenNoTasksExistsShould
{
     [Theory, AutoData]
     public void ReturnToday(DateTime today)
     {
          var builder = new NewTaskStartDateCalculator.Builder();
          var calculator = builder
                             .WithToday(today)
                             .Build();

          var startDate = calculator.Get();

          Assert.Equal(today.Date, startDate.Date);
     }
}

public class NewTaskStartDateCalculatorWhenOneTaskExistsAndUseTimeIsOnShould
{
     [Theory, AutoData]
     public void ReturnExistingTaskStartDateDatePart(
                  DateTime task1StartDate )
     {
         var builder = new NewTaskStartDateCalculator.Builder();
         var calculator = builder
                             .WithKnownTasksStartDates( new List<DateTime> { task1StartDate } )
                             .WithUseTime()
                             .Build();

         var startDate = calculator.Get();

         Assert.Equal( task1StartDate.Date, startDate.Date );
     }
}

public class NewTaskStartDateCalculatorWhenOneTaskExistsAndUseTimeIsOffShould
{
    public static IEnumerable<object[]> TestData1 => new[]
    {
        new object[] { new DateTime( 2017, 1, 2 ), WorkingDays.Standard, new DateTime( 2017, 1, 3 ) }
     };

     [Theory]
     [MemberData(nameof(TestData1))]
     public void ReturnExistingTaskStartDateAdvancedToTheNextWorkingDay(
                    DateTime task1StartDate,
                    WorkingDays workingDays,
                    DateTime nextWorkingDay)
     {
         var fakeWorkingDaysProcessor = A.Fake<IWorkingDaysProcessor>();
         A.CallTo(
              () => fakeWorkingDaysProcessor.GetNextWorkingDay(task1StartDate, workingDays)
         ).Returns(nextWorkingDay);

         var builder = new NewTaskStartDateCalculator.Builder();
         var calculator = builder
                             .WithKnownTasksStartDates(new List<DateTime> { task1StartDate })
                             .WithWorkingDaysConfiguration(workingDays)
                             .WithWorkingDaysProcessor(fakeWorkingDaysProcessor)
                             .Build();

         var startDate = calculator.Get();

         Assert.Equal(nextWorkingDay.Date, startDate.Date);
    }
}
public class NewTaskStartDateCalculatorWhenIsPlusEditionShould
{
    [Theory, AutoData]
    public void ReturnConfiguredWorkdayStart( DateTime today, TimeSpan workDayStart )
{
// TODO: the TimeSpan object has milliseconds.
// Implement a mechanism to generate TimeSpan objects without milliseconds.
// In addition, analyze the design to ensure the builder properly handles
// the situation where the input TimeSpan has milliseconds (should it
// silently cut the milliseconds or throw an exception?)
workDayStart = workDayStart.WithZeroMilliseconds();

var builder = new NewTaskStartDateCalculator.Builder();
var calculator = builder
.WithToday(today)
.WithWorkDayStart(workDayStart)
.WithLicenseIsPlusEdition()
.Build();

var startDate = calculator.Get();

Assert.Equal(workDayStart, startDate.TimeOfDay);
}
}

public class NewTaskStartDateCalculatorWhenIsFreeEditionShould
{
[Theory, AutoData]
public void Return_12_00_AM(DateTime today, DateTime task1StartDate, DateTime fakeNextWorkingDate)
{
var fakeWorkingDaysProcessor = A.Fake<IWorkingDaysProcessor>();
A.CallTo(
() => fakeWorkingDaysProcessor.GetNextWorkingDay(A<DateTime>.Ignored, A<WorkingDays>.Ignored)
).Returns(fakeNextWorkingDate);

var builder = new NewTaskStartDateCalculator.Builder();
var calculator = builder
.WithKnownTasksStartDates(new List<DateTime> { task1StartDate })
.WithWorkingDaysProcessor(fakeWorkingDaysProcessor)
.Build();

var startDate = calculator.Get();

Assert.Equal(DateTimeExtensions.DayStart, startDate.TimeOfDay);
}
}
Advertisements