During my rather long wait for the plane in Dublin this morning, I had the opportunity to revisit Toby and his company's EDM. Unfortunately, by the time I got to some stories, I was actually in the air with no internet access and as such had to just leave them for now. Regardless, here's an initial stab at the user stories I mentioned a while back (with perhaps a couple additions.) I'm now wondering if these wouldn't be better expressed with Behave# :)

(Aside: The code is going up on Codeplex as soon as the project is approved)

[TestFixture]
public class Class1
{
  
//* The administrator can login to the system.
  [Test]
  
public void CanLogIntoSystemAsAnAdministrator()
  {
      
EDMSystem system = new EDMSystem();

      system.LoginAs(
UserType.Administrator);

      
Assert.Equal<UserType>(
             UserType.Administrator, system.CurrentUser);
  }

  
//* The administrator can create a project.
  [Test]
  
public void TheAdministratorCanCreateAProject()
  {
      
EDMSystem system = new EDMSystem();
      system.LoginAs(
UserType.Administrator);

      system.CreateProject(0,
"my first project");

      
Assert.Equal<int>(1, system.ProjectCount);
  }

  
//* Non-administrative users can login to the system.
  [Test]
  
public void CanLogIntoTheSystemAsANonAdministrativeUser()
  {
      
EDMSystem system = new EDMSystem();

      system.LoginAs(
UserType.Bob);

      
Assert.Equal<UserType>(UserType.Bob, system.CurrentUser);
  }

  
//* A user can find a project by number.
  [Test]
  
public void CanFindProjectByNumber()
  {
      
EDMSystem system = new EDMSystem();
      system.LoginAs(
UserType.Administrator);
      system.CreateProject(1,
"my first project");
      system.LoginAs(
UserType.Bob);

      
Project foundProject = system.FindProject(1);

      
Assert.Equal<int>(1, foundProject.Number);
      
Assert.Equal<string>("my first project", foundProject.Name);
  }

  
//* A user can find a project by name.
  [Test]
  
public void CanFindProjectByName()
  {
      
EDMSystem system = new EDMSystem();
      system.LoginAs(
UserType.Administrator);
      system.CreateProject(1,
"my first project");
      system.LoginAs(
UserType.Bob);

      
Project foundProject = system.FindProject("my first project");

      
Assert.Equal<int>(1, foundProject.Number);
      
Assert.Equal<string>("my first project", foundProject.Name);
  }

  
//* A user can add a drawing to the system.
  [Test]
  
public void CanAddADrawingToAProject()
  {
      
EDMSystem system = new EDMSystem();
      system.LoginAs(
UserType.Administrator);
      
Project project = system.CreateProject(1,
                                    "my first project");
      system.LoginAs(
UserType.Bob);

      project.AddDrawing(
"blah");

      
Assert.Equal<int>(1, project.DrawingCount);
  }

  
//* A user can view the list of drawings for a project
  [Test]
  
public void CanViewTheListOfDrawingsForAProject()
  {
      
EDMSystem system = new EDMSystem();
      system.LoginAs(
UserType.Administrator);
      
Project project = system.CreateProject(1,
                                      "my first project");
      project.AddDrawing(
"blah");
      project.AddDrawing(
"blah too");

      
ReadOnlyCollection<Drawing> drawings = project.Drawings;

      
Assert.Equal<int>(2, drawings.Count);
      
Assert.Equal<string>("blah", drawings[0].Name);
      
Assert.Equal<string>("blah too", drawings[1].Name);
  }

  
// A user can filter the list of drawings by ???

  //* A user can enter (or modify) the date when their department (user) received a drawing.
  [Test]
  
public void CanEnterWhenDepartmentReceivedDrawing()
  {
      
EDMSystem system = new EDMSystem();
      system.LoginAs(
UserType.Administrator);
      
Project project = system.CreateProject(1,
                                      "my first project");
      system.LoginAs(
UserType.Bob);
      
Drawing drawing = project.AddDrawing("blah");
      
DateTime referenceDate = new DateTime(1979, 1, 21);

      drawing.WasReceived(system.CurrentUser, referenceDate);

      
Assert.Equal<DateTime>(referenceDate, drawing.Received(system.CurrentUser));
  }

  
// A user cannot modify when a different department's received a drawing.
  [Test]
  
public void CannotEnterWhenAnotherDepartmentReceivedDrawing()
  {
      
EDMSystem system = new EDMSystem();
      system.LoginAs(
UserType.Administrator);
      
Project project = system.CreateProject(1,
                                       "my first project");
      system.LoginAs(
UserType.Bob);
      
Drawing drawing = project.AddDrawing("blah");
      
DateTime referenceDate = new DateTime(1979, 1, 21);
      drawing.WasReceived(system.CurrentUser, referenceDate);

      system.LoginAs(
UserType.Joe);

      
Assert.Throws<InvalidOperationException>(delegate { drawing.WasReceived(UserType.Bob, DateTime.Now); });
  }

  
//* The administrator can edit all the details for a drawing. (??)
  //* A user can add drawings to the system sequentially without having to re-enter all the data each time.

  //* A user can up-rev a drawing (create a new revision by only changing the revision number and dates)
  [Test]
  
public void UserCanUpRevADrawing()
  {
      
EDMSystem system = new EDMSystem();
      system.LoginAs(
UserType.Administrator);
      
Project project = system.CreateProject(1,
                                       "my first project");
      system.LoginAs(
UserType.Bob);
      
Drawing drawing = project.AddDrawing("blah");
      
DateTime referenceDate = new DateTime(2007, 4, 1);

      
Drawing newDrawing = project.UpRevDrawing(drawing,
                                        "A053", referenceDate);

      
Assert.Equal<int>(2, project.DrawingCount);
      
Assert.Equal<Drawing>(newDrawing, project.Drawings[1]);
      
Assert.Equal<string>("A053", newDrawing.RevisionNumber);
  }

  
//* A user can edit the dates for a group of drawings for their department.
  [Test]
  
public void UserCanEnterWhenDepartementReceivedASetOfDrawings()
  {
      
EDMSystem system = new EDMSystem();
      system.LoginAs(
UserType.Administrator);
      
Project project = system.CreateProject(1,
                                      
"my first project");
      system.LoginAs(
UserType.Bob);
      
Drawing drawing = project.AddDrawing("blah");
      
Drawing drawing2 = project.AddDrawing("blah 2");
      
Drawing drawing3 = project.AddDrawing("blah 3");
      
DateTime referenceDate = new DateTime(1979, 1, 21);
      
List<Drawing> drawings =
                       
new List<Drawing>(project.Drawings);

      drawings.ForEach(
delegate(Drawing d)
           { d.WasReceived(system.CurrentUser, referenceDate); });

      
Assert.Equal<DateTime>(referenceDate,
                        drawings[0].Received(system.CurrentUser));
      
Assert.Equal<DateTime>(referenceDate,
                        drawings[1].Received(system.CurrentUser));
      
Assert.Equal<DateTime>(referenceDate, 
                        drawings[2].Received(system.CurrentUser));
  }

  
//* The administrator can archive a project.
  [Test]
  
public void AdministratorCanArchiveAProject()
  {
      
EDMSystem system = new EDMSystem();
      system.LoginAs(
UserType.Administrator);
      
Project project = system.CreateProject(0,
                                      "my first project");

      project.Archive(system.CurrentUser);

      
Assert.True(project.IsArchived);
  }

  
//* The administrator can un-archive a project.
  [Test]
  
public void AdministratorCanUnArchiveAProject()
  {
      
EDMSystem system = new EDMSystem();
      system.LoginAs(
UserType.Administrator);
      
Project project = system.CreateProject(0,
                                      "my first project");
      project.Archive(system.CurrentUser);

      project.UnArchive(system.CurrentUser);

      
Assert.False(project.IsArchived);
  }
}

public class EDMSystem : IUserService
{
  
public EDMSystem()
  {
      projectDictionary =
new Dictionary<int, Project>();
      projectDictionaryByName =
new Dictionary<string, Project>();
  }

  
public UserType CurrentUser
  {
      
get { return currentUser; }
  }

  
public int ProjectCount
  {
      
get { return 1; }
  }

  
public void LoginAs(UserType userType)
  {
      currentUser = userType;
  }

  
public Project CreateProject(int projectNumber,
                               string projectName)
  {
      
Project project = new Project(projectNumber,
                                    projectName,
this);
      projectDictionary.Add(projectNumber, project);
      projectDictionaryByName.Add(projectName, project);

      
return project;
  }

  
public Project FindProject(int projectNumber)
  {
      
return projectDictionary[projectNumber];
  }

  
public Project FindProject(string projectName)
  {
      
return projectDictionaryByName[projectName];
  }

  
private UserType currentUser;
  
private Dictionary<int, Project> projectDictionary;
  
private Dictionary<string, Project> projectDictionaryByName;
}

public class Project
{
  
public Project(int number, string name,
                
IUserService userService)
  {
      
this.number = number;
      
this.name = name;
      
this.userService = userService;

      drawings =
new List<Drawing>();
  }

  
public string Name
  {
      
get { return name; }
  }

  
public int Number
  {
      
get { return number; }
  }

  
public int DrawingCount
  {
      
get { return drawings.Count; }
  }

  
public ReadOnlyCollection<Drawing> Drawings
  {
      
get { return drawings.AsReadOnly(); }
  }

  
public bool IsArchived
  {
      
get { return isArchived; }
  }

  
public Drawing AddDrawing(string drawingName)
  {
      
Drawing drawing = new Drawing(drawingName, userService);
      drawings.Add(drawing);
      
return drawing;
  }

  
public Drawing UpRevDrawing(Drawing drawing,
                             
string revisionNumber,
                              
DateTime referenceDate)
  {
      
Drawing upRevvedDrawing = new Drawing(
                                   drawing.Name, userService);
      upRevvedDrawing.RevisionNumber = revisionNumber;

      drawings.Add(upRevvedDrawing);

      
return upRevvedDrawing;
  }

  
public void Archive(UserType userType)
  {
      
if (userType == UserType.Administrator)
      {
          isArchived =
true;
      }
  }

  
public void UnArchive(UserType userType)
  {
      
if (userType == UserType.Administrator)
      {
          isArchived =
false;
      }
  }

  
private string name;
  
private int number;
  
private List<Drawing> drawings;
  
private IUserService userService;
  
public bool isArchived;
}

public class Drawing
{
  
public Drawing(string name, IUserService userService)
  {
      
this.name = name;
      
this.userService = userService;

      receivedByUser =
new Dictionary<UserType, DateTime>();
  }

  
public string Name
  {
      
get { return name; }
  }

  
public string RevisionNumber
  {
      
get { return revisionNumber; }
      
set { revisionNumber = value; }
  }

  
public void WasReceived(UserType userType,
                          DateTime referenceDate)
  {
      
if (userType != userService.CurrentUser)
      {
          
throw new InvalidOperationException();
      }

      receivedByUser[userType] = referenceDate;
  }

  
public DateTime Received(UserType userType)
  {
      
return receivedByUser[userType];
  }

  
private string name;
  
private Dictionary<UserType, DateTime> receivedByUser;
  
private IUserService userService;
  
private string revisionNumber;
}

public enum UserType
{
  Administrator,
  Bob,
  Joe
}

public interface IUserService
{
  
UserType CurrentUser { get; }
}

 

There are a few things I want to point out about this code:

  • It's already gone through a few refactorings. When I reached the story about disallowing users to modify other users (groups?) received date I had to introduce IUserSerivce and change a few constructors.
  • I realise my constructors can be changed with Dependency Injection from ObjectBuilder (CAB) when we reach that stage. For now, we're doing it the simple way.
  • There is no real storage mechanism. The user stories never mentioned persistence :)
  • The ubiquitous language is starting to change. The user stories are starting to change. This is a Good Thing. One of the conversions I had at Agile 2007 with the Brian Button (an incredibly intelligent and most definitely non-red individual) was about developing bottom-up with EDD versus inside-out. Along with the concepts of BDD, I'm going to try out what he introduced me to. Brian - apologies if I'm not doing things quite right .. I'm learning as I go along here :)

I'd love to hear what people think about this .. have I completely gone off the deep end with regards to EDD here? Are the unit tests I currently have better written as acceptance tests (in Behave# for instance)? Any and all comments welcome.