Toby and the complete stab in the dark
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.