eXtreme .NET: Introducing eXtreme Programming Techniques to .NET Developers [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

eXtreme .NET: Introducing eXtreme Programming Techniques to .NET Developers [Electronic resources] - نسخه متنی

Neil Roodyn

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید





A Strategy to Lower the Risk of Failure





Breaking everything you do into tiny steps and validating each step increases your chances of success. One of the aims of any software development methodology is to lower the risk of failure. Such a large number of software projects each year fail that it is still the majority case. Of course, many companies twist the story and change the boundaries so as to make the project seem like a success to the shareholders. In reality, very few projects can be considered a success based on the initial project guidelines.




In the following exercise, we work toward delivering a project to the customer. This exercise pulls together all the lessons covered thus far in this book. Because of the scope of even such a small project, this chapter contains only the first iteration of the project. You can download the rest of the iterations from my Web site at http://eXtreme.NET.Roodyn.com.





Step-by-Step Exercise to Demonstrate the Small-Step Approach





As we go through this project, I hope you appreciate the small steps that we are taking at each point and the value of this. I expect that more experienced developers will have the urge to skip through sections of this chapter. Unless you are already comfortable with how to develop in an XP manner, I implore you to stick with the exercises and try to learn from them. In my experience using these exercises in training courses, the experienced developers who do follow the exercises finish them more quickly and feel happier about the results.





The Customer Meeting






We will start off with the first meeting with our customer. In this exercise, our customer is a product manager who is looking for a new component to enhance an existing suite of Windows components. Quick, let''s get to the meeting before we are late.











Dr. Neil: You''ve got this application you want developed; tell me about it.




Product manager: It''s an alarm clock; I want an alarm clock for the desktop.




DN: Yep, sounds like a good idea, like Outlook Reminders?




PM: Yes, sort of. But when the alarm rings it should ring until I switch it off, not just once.




DN: Cool idea! What else?




PM: I want to have multiple alarms.




DN: Okay, nice. Do you want the alarms to be set by date as well as time?




PM: Yep, that''s right. Also, I want to have a label for the alarm




DN: Okay, like a description?




PM: Yes, and I want to be able to set sounds for an alarm; there should be a default "beep," but I want to choose a tune to play.




DN: Nice.




PM: Oh, and one last thing, I would love it to be able to use different skins so the user can choose how the clock looks.




DN: Okay.




PM: That''s it; how long will it take?




DN: Can we create some stories for all of the features you just asked for?




PM: Okay, show me what you''re talking about.





The Story Cards






Based on the previous conversation, we can write up the following stories on index cards:








Alarms rings continuously until I switch it off (when date time is reached or soonest time thereafter).




Add alarm for a date and time with a description.




Edit an alarm.




Set custom tune for the alarm.




Custom skin for alarm application.




Delete (remove) an alarm.




Visual feedback that an alarm is ringing.




Option to switch sound and visual feedback on or off (not allowed to have both off).




Options for feedback (visual and sound) per alarm.


















DN: Okay, great; these are the stories so far. If you come up with any more, please let us know. Right now I''d like you to sort the cards into three piles: EssentialMust Have, Adds Business Value, and Nice to Have.




PM: Sure.













EssentialMust Have







Adds Business Value







Nice to Have













Add alarm for a date and time







Visual feedback that alarm







Options for feedback is ringing (visual and sound) per alarm













Alarm rings continuously until I switch it off







Option to switch sound and visual feedback on or off







Custom skin for alarm application













Edit and alarm







Set custom tune for the alarm













Delete (remove) an alarm













DN: Great; now we are going to estimate in ideal programming hours how long it will take to complete these stories. Remember that in a day we may only get three or four ideal programming hours with the amount of disturbance that happens around this office in terms of meetings, e-mail, and other commitments.













Story







Development Cost













Alarms rings continuously until I switch it off (when date time is reached or soonest time thereafter)







2













Add alarm for a date and time with a description







1













Edit an alarm







2













Set custom tune for the alarm







3













Custom skin for alarm application







?













Delete (remove) an alarm







1













Visual feedback that an alarm is ringing







2













Option to switch sound and visual feedback on or off (not allowed to have both off)







2













Options for feedback (visual and sound) per alarm







1













DN: You will notice that the story "Custom skin for alarm application" has a question mark; this is because at this point we don''t know how long it will take. We have never developed an application with a skin before.




PM: Okay, well it''s not a high priority at the moment, so let''s leave that out for the moment.




DN: Fine, based on some previous experience and a bit of guesswork, I think we can deliver four units of development each iteration and our iterations will be a couple of days long. So could you please group the story cards into iterations that contain no more than four units of development each.




PM: So these iterations deliverables are like milestones that I can actually use?




DN: Yep, that''s right.




PM: Can I have an iteration with less than four units?




DN: Of course, and you will probably get that iteration delivered a bit earlier.




PM: Okay, sounds good to me; I''d like to see my iterations as follows.













Iteration 1













Add alarm for a date and time













Alarm rings continuously until I shut it off













Delete (remove) an alarm













Iteration 2













Edit an alarm













Iteration 3













Visual feedback that alarm is ringing













Option to switch sound and visual feedback on or off













Iteration 4













Set custom tune for the alarm













Options for feedback (visual and sound) per alarm













Iteration 5













Custom skin for alarm application













DN: Okay, good work; let''s get to it and start delivering something as soon as possible.














Iteration 0






Many XP teams start a project with a very short iteration to get everything prepared for the project. In iteration 0, we set up the environment that will enable us to build and ship this application as quickly as possible. This will include setting up the skeleton application, an installer, and an automatic build and test script.














1.







In Visual Studio.NET, create a new C# Windows application called MyAlarm.








Figure 9-1. Create a new MyAlarm project.






[View full size image]



















2.







In Solution Explorer, right-click the MyAlarm solution and from the pop-up menu select Add/New Project. Then select Setup and Deployment projects and Setup Wizard; we will call the project MyAlarm Setup.








Figure 9-2. Add a new Setup and Deployment project.






[View full size image]



















3.







Follow the wizard and create a setup for a Windows application see Figure 9-3) and primary output from MyAlarm see Figure 9-4), then click Finish.








Figure 9-3. Create a Windows application setup.
















Figure 9-4. Include the primary output from MyAlarm.





















4.







Chapter 7 about
automating the build process.





@echo off
rem: set the source safe database
set SSDIR=C:\Work\SSDB\MyAlarm
rem: set the working directory for the source safe database root project
ss Workfold $/ C:\Work
rem:change to the directory where all the projects work is
c:
cd Work
rem: get the latest version of the source files from source safe
ss get $/ -R -I-
rem: check that it worked
if errorlevel 1 goto DisplayResult
rem: use the Visual studio cmd line to rebuild the entire solution
devenv /rebuild Debug "C:\Work\MyAlarm\MyAlarm.sln"
rem: check that it worked
if errorlevel 1 goto DisplayResult
rem: run the tests
nunit-console C:\Work\MyAlarm\bin\Debug\MyAlarm.exe
rem: check that they passed
if errorlevel 1 goto DisplayResult
rem: if all the tests passed then rebuild for release
devenv /rebuild Release "C:\Work\MyAlarm\MyAlarm.sln"
if errorlevel 1 goto DisplayResult
:DisplayResult
if errorlevel 1 goto failed
color A1
echo Success!! :-)
goto end
:failed
echo FAILURE :-(
color C1
:end
rem: for continuous builds the line below
rem: call C:\work\MyAlarm\Build.bat













6.







Open a command window and run the batch file; because we have no tests in the project yet, you should get the green screen of success.













We have now set up a build and test script along
with a simple installer; this will enable us to move faster in the iterations that are to come.





Iteration 1






The Task Breakdown




The stories we have for iteration 1 are as follows:














1.







Add alarm for a date and time.













2.







Delete (remove) an alarm.













3.







Alarm rings continuously until I shut it off.













Let''s work through a first attempt at breaking these stories down into tasks using the lessons we learned in
Chapter 3.












Story 1: Add Alarm













Task







Estimated Time













Create alarm collection







5 minutes













Check that a new alarm collection is empty







5 minutes













Validate that after adding one alarm to the collection the collection is not empty







10 minutes













Validate that after adding one alarm to the collection there is only one alarm







10 minutes













Validate that after adding three alarms to a new collection there are 3 alarms in the collection







10 minutes













Validate that an alarm added to the collection contains the same date, time, and description as the alarm that was added







10 minutes













Add UI for Adding alarm







15 minutes













Integrate and check in to source control







10 minutes






















Story 2: Delete (Remove) an Alarm













Task







Estimated Time













Check that trying to delete an alarm from an empty collection does not cause the system to fail







10 minutes













Check that after deleting an alarm from a collection that contains only that alarm the collection is empty







5 minutes













Check that deleting an alarm from a collection that contains 3 alarms leaves 2 alarms in the collection







10 minutes













Check that the alarm deleted from a collection is the correct alarm







10 minutes













Check that it is not possible to delete an alarm that doesn''t exist in a collection







10 minutes













Add UI for deleting alarm







10 minutes













Integrate and check in to source control







10 minutes




















Story 3: Alarm Rings Continuously Until I Shut It Off













Task







Estimated Time













Create an alarm ring event







5 minutes













Ensure the event gets called when an alarm should ring







15 minutes













Check that the event does not get called when no alarm should ring







10 minutes













Set up delegate method to play a sound continuously when an alarm rings







10 minutes













Add method to stop sound from playing







10 minutes













Validate that one alarm can ring after another alarm has rung and been switched off







10 minutes













Add UI to allow user to switch alarm sound off







10 minutes













Integrate and check in to source control







10 minutes













We may not have thought of everything, but we are in a good place to start developing this first iteration. So let''s get to it.





Story 1: Add Alarm






Task: Create Alarm Collection














1.







Add a new class to the MyAlarm project called AlarmsTests.













2.







Add another new class to the MyAlarm project called Alarms.













3.







Okay, we''re done! There is nothing to test, and we have an Alarms collection class. Check in to source control.













Task: Check That a New Alarm Collection Is Empty














1.







Add a reference to the NUnit.Framework.dll in our MyAlarms project.













2.







In the AlarmsTests class, add the using clause for NUnit.Framework and set the TestFixture attribute to the AlarmsTests class.













3.







Add a Test method to the AlarmsTests class called TestIsNewCollectionEmpty.





[Test]
public void TestIsNewCollectionEmpty()
{
Alarms alarms = new Alarms();
Assert.IsTrue(alarms.Empty,
"New alarms collection should empty");
}













4.







This will not compile you should make sure!), so we need to add the Empty property to the Alarms class.





public bool Empty
{
get{ return true;}
}













5.







Compile this and run the test; it should pass.













6.







Once again, we''re done for this task. About now you might be looking at the method we just wrote and saying it is pointless. I disagree; we have added the Boolean Empty property to the alarms class. To make our test pass, we did the simplest thing. This might look crazy, but the whole point is to do everything in very small steps. This enables us to focus on getting one thing right at a time. We know we will have to come back here and change it, but how is that different from any piece of code you write?













7.







Check in to source control.













Task: Validate That After Adding One Alarm to the Collection the Collection Is Not Empty














1.







Add a new test method to the AlarmsTests class called TestCollectionNotEmptyAfterAddingAlarm; sorry about the name, let me know if you can think of a better one.





[Test]
public void TestCollectionNotEmptyAfterAddingAlarm()
{
Alarms alarms = new Alarms();
Alarm testAlarm = new Alarm();
alarms.Add(testAlarm);
Assert.IsFalse(alarms.Empty,
"Alarms collection should not be empty after adding an alarm");
}













2.







This will not compile, so add the new Alarm class to the project and the Add method to the alarms class.





public void Add(Alarm alarm)
{
}













3.







Now the code will compile and our new test will fail. So our mission is to make the test pass. At this point we need to make a decision as to how to store the alarms in the collection class. We have a few options here:







    Derive the Alarms class from a .NET collection class.







    Have the Alarms class wrap (contain) a .NET collection class.







    Roll our own collection.





    The third option will involve the most work, and I don''t see it as the simplest thing we could do. The easiest thing to do would be to derive the Alarms class from a collection. At this point, it is probably also the simplest. If we change our minds, we can always refactor our code later.













    4.







    Add the System.Collections namespace to our using clauses at the top of the file and inherit our Alarms class from ArrayList (we can change this later as well if we need to), and then change the Empty property and remove the Add method. We don''t need it anymore because the base class supports an Add method.)





    public bool Empty
    {
    get
    {
    if (this.Count > 0)
    {
    return false;
    }
    else
    {
    return true;
    }
    }
    }













    5.







    Compile and run the tests; they should pass.













    6.







    Check in to source control.













    Task: Validate That After Adding One Alarm to the Collection There Is Only One Alarm














    1.







    Add a Test called TestAddingOneAlarm to the AlarmsTests class.





    [Test]
    public void TestAddingOneAlarm()
    {
    Alarms alarms = new Alarms();
    Alarm testAlarm = new Alarm();
    alarms.Add(testAlarm);
    Assert.AreEqual(1, alarms.Count,
    "Alarms collection should have 1 alarm after adding an alarm");
    }













    2.







    Compile and run the tests; they pass!




    We didn''t need to write any code for this test to pass. This happens sometimes, but doesn''t make the test any less useful; this test will validate that any future changes don''t change our assumptions about how the Alarms class works.













    3.







    Check in to source control.













    Task: Validate That After Adding Three Alarms to a New Collection There Are Three Alarms in the Collection














    1.







    To the AlarmsTests collection, add the Test method TestAddingThreeAlarms.





    [Test]
    public void TestAddingThreeAlarms()
    {
    Alarms alarms = new Alarms();
    Alarm testAlarm = new Alarm();
    Alarm testAlarm2 = new Alarm();
    Alarm testAlarm3 = new Alarm();
    alarms.Add(testAlarm);
    alarms.Add(testAlarm2);
    alarms.Add(testAlarm3);
    Assert.AreEqual(3, alarms.Count,
    "Alarms should have 3 alarm after adding 3 alarms");
    }













    2.







    Compile and run the tests. Again they pass; this is too easy!













    3.







    Check in to source control.













    Task: Validate That An Alarm Added to the Collection Contains the Same Date, Time, and Description As the Alarm That Was Added














    1.







    Add a new Test method called TestAlarmDataIsCorrect to the AlarmsTests class.





    [Test]
    public void TestAlarmDataIsCorrect()
    {
    Alarms alarms = new Alarms();
    DateTime alarmTime = DateTime.Now;
    string alarmDescription = "Test alarm";
    Alarm testAlarm = new Alarm(alarmTime, alarmDescription);
    alarms.Add(alarm);
    Assert.AreEqual(alarmTime, alarms[0].DateTime,
    "Alarm time not equal to that set");
    Assert.AreEqual(alarmDescription, alarms[0].Description,
    "Alarm description not equal to that set");
    }













    2.







    Try to compile this. Unlike the previous two tasks, this is going to require some work to get it to compile.













    3.







    In the Alarm class, add a new constructor and a couple of properties.





    public Alarm(DateTime dateTime, string description)
    {
    }
    public DateTime DateTime
    {
    get{ return DateTime.Now;}
    }
    public string Description
    {
    get{ return string.Empty;}
    }













    4.







    This still doesn''t compile because the indexer on the Alarms class returns an object and not an Alarm type, so we need to add an indexer method to the Alarms class.





    public Alarm this[int index]
    {
    get
    {
    return base[index] as Alarm;
    }
    }













    5.







    Now the project compiles, but that last test fails. In the alarm class, add the variables to store the description, date, and time. Then use them in the constructor and the property get methods.





    private DateTime dateTime;
    private string description;
    public Alarm(DateTime dateTime, string description)
    {
    this.dateTime = dateTime;
    this.description = description;
    }
    public DateTime DateTime
    {
    get{ return dateTime;}
    }
    public string Description
    {
    get{ return description;}
    }













    6.







    Compile and run the tests. Yes! Another task done.













    7.







    Check in to source control.













    Task: Add UI for Adding Alarm














    1.







    Add a new Windows Form class called AddAlarm to the MyAlarm project see Figure 9-5).








    Figure 9-5. Add a new Windows form.






    [View full size image]



















    2.







    Add a new class called AddAlarmFormTests.













    3.







    In the new AddAlarmFormTests class file, add a using reference for NUnit.Framework and add the TestFixture attribute to the class.













    4.







    The first test we are going to write will determine the controls we are going to use in our AddAlarm form.





    [Test]
    public void TestFormControls()
    {
    AddAlarm testForm;
    BindingFlags flags = BindingFlags.NonPublic|
    BindingFlags.Public|BindingFlags.Static|
    BindingFlags.Instance;
    Type tForm = typeof(AddAlarm);
    testForm = new AddAlarm();
    testForm.Show();
    FieldInfo datePickerInfo =
    tForm.GetField("date",flags);
    MonthCalendar datePicker =
    (MonthCalendar)datePickerInfo.GetValue(testForm);
    Assert.AreEqual(1, datePicker.MaxSelectionCount,
    "Calendar should only allow 1 date selected");
    Assert.AreEqual(DateTime.Now.Date,
    datePicker.SelectionStart.Date,
    "Calendar should initialize to todays date");
    FieldInfo timePickerInfo =
    tForm.GetField("time",flags);
    DateTimePicker timePicker =
    (DateTimePicker)timePickerInfo.GetValue(testForm);
    Assert.AreEqual(DateTimePickerFormat.Custom,
    timePicker.Format,
    "Time picker should be formated to show time only");
    Assert.AreEqual("HH:mm",
    timePicker.CustomFormat,
    "Time picker should be formated to show time only");
    Assert.AreEqual(true,
    timePicker.ShowUpDown,
    "Time picker should show up-down control");
    FieldInfo descriptionInfo =
    tForm.GetField("description",flags);
    TextBox description=
    (TextBox)descriptionInfo.GetValue(testForm);
    Assert.AreEqual(string.Empty,
    description.Text,
    "Initial description should be blank");
    FieldInfo AddBtnInfo =
    tForm.GetField("AddBtn",flags);
    Button AddBtn=
    (Button )AddBtnInfo.GetValue(testForm);
    testForm.Close();
    testForm.Dispose();
    }













    5.







    This test will fail because none of the controls are on the form. In the design view for the new form, add a Calendar control, a DateTime picker, a label, a text box, and a button, as shown in Figure 9-6. The values for the properties on the controls that need to be set can be determined from the test.








    Figure 9-6. Design the add Alarm dialog.





















    6.







    Chapter 8.





    [Test]
    public void TestAddBtnClick()
    {
    AddAlarm testForm;
    BindingFlags flags = BindingFlags.NonPublic|
    BindingFlags.Public|BindingFlags.Static|
    BindingFlags.Instance;
    Type tForm = typeof(AddAlarm);
    testForm = new AddAlarm();
    testForm.Show();
    FieldInfo datePickerInfo =
    tForm.GetField("date",flags);
    MonthCalendar datePicker =
    (MonthCalendar)datePickerInfo.GetValue(testForm);
    datePicker.SelectionStart = new DateTime(2004,12,25);
    FieldInfo timePickerInfo =
    tForm.GetField("time",flags);
    DateTimePicker timePicker =
    (DateTimePicker)timePickerInfo.GetValue(testForm);
    timePicker.Value = DateTime.Parse("08:00");
    FieldInfo descriptionInfo =
    tForm.GetField("description",flags);
    TextBox description=
    (TextBox)descriptionInfo.GetValue(testForm);
    description.Text = "Time to open presents";
    MethodInfo clickMethod =
    tForm.GetMethod("AddBtn_Click",flags);
    Object[] args = new Object[2];
    args[0] = this;
    args[1] = new EventArgs();
    clickMethod.Invoke(testForm, args);
    PropertyInfo alarmInfo =
    tForm.GetProperty("Alarm",flags);
    Alarm alarm=
    (Alarm)alarmInfo.GetValue(testForm,null);
    Assert.AreEqual(new DateTime(2004,12,25,08,00,00),
    alarm.DateTime,
    "Alarm datetime is not correct");
    Assert.AreEqual("Time to open presents",
    alarm.Description,
    "Alarm description is not correct");
    testForm.Close();
    testForm.Dispose();
    }













    8.







    The code won''t compile. To get it to compile, we need to add an event handler for the Add button and a public property on the form of type Alarm.





    private void AddBtn_Click(object sender, System.EventArgs e)
    {
    }
    public Alarm Alarm
    {
    get
    { return null; }
    }













    9.







    Compile and run the tests; they fail. We have defined the AddBtn and Alarm property and now need to fill them in.





    private Alarm alarm;
    private void AddBtn_Click(object sender, System.EventArgs e)
    {
    alarm = new Alarm(date.SelectionStart +
    time.Value.TimeOfDay,
    description.Text);
    }
    public Alarm Alarm
    {
    get
    {
    return alarm;
    }
    }













    10.







    Right now I can think of another test we should write. We should check that before the Add button has been clicked, the Alarm property returns null.





    [Test]
    public void TestAlarmBeforeButtonClick()
    {
    AddAlarm testForm;
    testForm = new AddAlarm();
    testForm.Show();
    Assert.IsNull(testForm.Alarm,
    "Alarm should be null if add button not clicked");
    testForm.Close();
    testForm.Dispose();
    }













    11.







    Compile and run the tests. They all pass. This last test doesn''t require any work but prevents any future changes from setting the Alarm to a valid value if the button has not been clicked.













    12.







    There is some refactoring that we can do now in the AddAlarmFormTests class. Let''s start by creating a setup and teardown that creates the form and closes and disposes the form.





    private AddAlarm testForm;
    private BindingFlags flags = BindingFlags.NonPublic|
    BindingFlags.Public|BindingFlags.Static|
    BindingFlags.Instance;
    private Type tForm = typeof(AddAlarm);
    [SetUp]
    public void SetUp()
    {
    testForm = new AddAlarm();
    testForm.Show();
    }
    [TearDown]
    public void TearDown()
    {
    testForm.Close();
    testForm.Dispose();
    }




    I will leave it to you to extract the duplicate code from each of the test methods that now exists in the SetUp and TearDown methods.













    13.







    There is one final thing to do, add a UI to the MyAlarm form to enable the user to add an alarm. We will, of course, do this the test-driven way, so add a new class to the project called MyAlarmFormTests. Add the Nunit. Framework namespace to the using clauses at the top of the file and attribute the class as a TestFixture.




    At this point I am not sure what the best approach is for calling the Add Alarm dialog, so I ask the customer.

























    DN: How should the user add an alarm?




    PM: From a menu. Is that possible?




    DN: Sure.






















    A quick chat with the customer is the best way to get the answer, and now we know they are expecting a menu to add the alarm. So we can write to test to check that there is a menu item on form to do this.





    [Test]
    public void TestAddAlarmMenuItemExists()
    {
    MyAlarmForm testForm;
    BindingFlags flags = BindingFlags.NonPublic|
    BindingFlags.Public|BindingFlags.Static|
    BindingFlags.Instance;
    Type tForm = typeof(MyAlarmForm);
    testForm = new MyAlarmForm();
    testForm.Show();
    FieldInfo menuInfo =
    tForm.GetField("mainMenu",flags);
    MainMenu mainMenu =
    (MainMenu)menuInfo.GetValue(testForm);
    Assert.AreEqual("Alarms",
    mainMenu.MenuItems[0].Text,
    "Alarms menu is not on the main menu");
    Assert.AreEqual("Add",
    mainMenu.MenuItems[0].MenuItems[0].Text,
    "Add menu is not on the Alarms menu");
    testForm.Close();
    testForm.Dispose();
    }













    14.







    Compile the code and run the tests. They fail. In the design view, we need to add a MainMenu control to our MyAlarm form. Then add an Alarms menu item and an Add menu item to the Alarms menu. Rerun the tests; they should pass.













    15.







    At this point we know the AddAlarm form behaves as expected because we have tested it, and we know that the Alarms collection class passes all the tests, so we should feel comfortable plugging them together in the menu click event code.





    private Alarms alarms;
    public MyAlarmForm()
    {
    InitializeComponent();
    alarms = new Alarms();
    }
    private void AlarmsAddMenu_Click(object sender,
    System.EventArgs e)
    {
    AddAlarm alarmFrm = new AddAlarm();
    alarmFrm.ShowDialog();
    if (alarmFrm.Alarm != null)
    {
    alarms.Add(alarmFrm.Alarm);
    }
    }













    16.







    Compile and run the tests again just to be sure we haven''t broken anything. We must be nearly done now. There are a couple of things left to do. The alarm we add doesn''t get used at the moment, we need to display a list on the form, and there is a small bug in one of our forms.













    17.







    To the MyAlarmFormTests class, add a new test method called TestAddAlarm. Time to ask the customer another question:

























    DN: How do you want to see the alarms?




    PM: Like in Outlook, in a list with description first, then time and date.






















    Let''s code a test for this.





    [Test]
    public void TestAddAlarm()
    {
    MyAlarmForm testForm;
    BindingFlags flags = BindingFlags.NonPublic|
    BindingFlags.Public|BindingFlags.Static|
    BindingFlags.Instance;
    Type tForm = typeof(MyAlarmForm);
    testForm = new MyAlarmForm();
    testForm.Show();
    FieldInfo alarmListInfo =
    tForm.GetField("alarmsList",flags);
    ListView alarmList =
    (ListView)alarmListInfo.GetValue(testForm);
    Assert.AreEqual(0, alarmList.Items.Count,
    "There should be no alarms on a new form");
    FieldInfo alarmsInfo=
    tForm.GetField("alarms",flags);
    Alarms alarms =
    (Alarms)alarmsInfo.GetValue(testForm);
    DateTime testDateTime =
    new DateTime(2005, 12, 25, 08, 00, 00);
    alarms.Add(new Alarm(testDateTime, "Christmas"));
    MethodInfo refreshMethod =
    tForm.GetMethod("RefreshAlarmList",flags);
    refreshMethod.Invoke(testForm, null);
    Assert.AreEqual(1, alarmList.Items.Count,
    "There should be 1 alarm after adding it");
    Assert.AreEqual("Christmas",
    alarmList.Items[0].Text,
    "The new alarm description is not correct");
    testForm.Close();
    testForm.Dispose();
    }













    18.







    Run the test we just wrote. As we get more tests into the system, we don''t want to have to run all the tests after
    we have written
    a new one, especially if we expect our new test to fail. We should run all the tests when we have got this
    test to pass. To get this test to pass, we need to add a ListView called alarmsList to our form and a method called RefreshAlarmList that updates the list view.





    protected void RefreshAlarmList()
    {
    alarmsList.Items.Clear();
    foreach(Alarm alarm in alarms)
    {
    alarmsList.Items.Add(
    new ListViewItem(
    new string[] {alarm.Description,
    alarm.DateTime.ToShortDateString(),
    alarm.DateTime.ToShortTimeString()})
    );
    }
    this.Refresh();
    }













    19.







    Rerun the test; it should pass, and we can check that all the tests pass, which they should because we haven''t broken anything.













    20.







    Finally, we can plug this new method into the menu click event.





    private void AlarmsAddMenu_Click(object sender,
    System.EventArgs e)
    {
    AddAlarm alarmFrm = new AddAlarm();
    alarmFrm.ShowDialog();
    if (alarmFrm.Alarm != null)
    {
    alarms.Add(alarmFrm.Alarm);
    RefreshAlarmList();
    }
    }













    21.







    We should now run all the tests and make sure they pass. Then we should run the application we have written; we should just do a quick run through and make sure it behaves as expected. There is one main thing we should notice. The Add button doesn''t close the AddAlarm dialog form. That is not how we would expect the program to work. So we will write a test to highlight this bug and then fix it.





    [Test]
    public void TestAddBtnClosesForm()
    {
    MethodInfo clickMethod =
    tForm.GetMethod("AddBtn_Click",flags);
    Object[] args = new Object[2];
    args[0] = this;
    args[1] = new EventArgs();
    clickMethod.Invoke(testForm, args);
    Assert.AreEqual(null, Form.ActiveForm,
    "There should be no active form after add btn clicked");
    Assert.IsFalse(testForm.Visible,
    "After add click form should not be visible");
    }













    22.







    The test should fail to fix this one line of code, but don''t forget the value in the test is to make sure that correction remains encoded in the system.





    private void AddBtn_Click(object sender,
    System.EventArgs e)
    {
    alarm = new Alarm(date.SelectionStart +
    time.Value.TimeOfDay,
    description.Text);
    this.Close();
    }













    23.







    Run all the tests and check that nothing else has been broken. Then run the application again. There is one other small thing that we need to think about. The ListView by default shows the items using the LargeIcon view, but the customer wanted to see them "like in Outlook," as a list with the description first. Let''s code a test to enforce the ListView uses the Details View mode. While we are here, we can check that there are three columns, for the description, date, and time.





    [Test]
    public void TestAlarmsListView()
    {
    MyAlarmForm testForm;
    BindingFlags flags = BindingFlags.NonPublic|
    BindingFlags.Public|BindingFlags.Static|
    BindingFlags.Instance;
    Type tForm = typeof(MyAlarmForm);
    testForm = new MyAlarmForm();
    testForm.Show();
    FieldInfo alarmListInfo =
    tForm.GetField("alarmsList",flags);
    ListView alarmList =
    (ListView)alarmListInfo.GetValue(testForm);
    Assert.AreEqual(View.Details, alarmList.View,
    "Alarms list should display Details");
    Assert.AreEqual(3, alarmList.Columns.Count,
    "there should be 3 columns");
    testForm.Close();
    testForm.Dispose();
    }













    24.







    Run this test; it should fail until we set up the ListView to display a Details view and have three columns which we can do from the Properties window on the design view of the form).













    25.







    There is more refactoring that we can do now in the MyAlarmFormTests class. In the same way as we did with the AddAlarmFormTests, we should extract the repeated form initialization and destruction code into SetUp and TearDown methods.













    26.







    Check in to source control.













    Task: Integrate and Check In to Source Control














    1.







    The first thing we should do is check that everything is still good with our automated batch file to build and test the project. Run the batch file; we should get the green screen of joy. We are nearly done.













    2.







    Check in the files to source control. If we were working on a project where other people were accessing the same source control database, I would suggest running the batch file again now to ensure that nothing has changed and that the version of the code in the source control compiles and all the tests run.













    That is the first story complete, yippee, high fives all around and a general feeling of "we''ve done it" and "we''re making progress." Let''s take a small break, and then get started with the next story.





    Story 2: Delete (Remove) an Alarm






    Task: Check That Trying to Delete an Alarm from an Empty Collection Does Not Cause the System to Fail














    1.







    By now I''m sure you know we need to write the test first. We will do this in the AlarmsTests class; we have introduced the ExpectedException attribute here to enable us to get the test to pass only when the exception is thrown.





    [Test,
    ExpectedException(typeof(ArgumentOutOfRangeException))]
    public void TestRemoveFromEmptyList()
    {
    Alarms alarms = new Alarms();
    alarms.RemoveAt(0);
    }













    2.







    Compile and run the test. It passes. Because we have inherited the alarms class from the ArrayList, it supports this functionality "out of the box." Time to move to the next task.













    3.







    Check in to source control.













    Task: Check That After Deleting an Alarm from a Collection That Contains Only That Alarm the Collection Is Empty














    1.







    Test first again.





    [Test]
    public void TestRemove()
    {
    Alarms alarms = new Alarms();
    alarms.Add(new Alarm());
    alarms.RemoveAt(0);
    Assert.IsTrue(alarms.Empty,
    "After removing an item the list should empty");
    }













    2.







    Compile and run the test. It works. Good. Check in to source control and let''s move on.













    Task: Check That Deleting an Alarm from a Collection That Contains Three Alarms Leaves Two Alarms in the Collection














    1.







    The test.





    [Test]
    public void TestListContainsItemsNotRemoved()
    {
    Alarms alarms = new Alarms();
    alarms.Add(new Alarm(DateTime.Now, "One"));
    alarms.Add(new Alarm(DateTime.Now, "Two"));
    alarms.Add(new Alarm(DateTime.Now, "Three"));
    alarms.RemoveAt(0);
    Assert.AreEqual(2, alarms.Count,
    "After removing an item the list should not be empty");
    }













    2.







    Test passes. Okay. Check in to source control. Next task. Are you getting the feeling that we are starting to move faster now?













    Task: Check That the Alarm Deleted from a Collection Is the Correct Alarm














    1.







    The test.





    [Test]
    public void TestRemoveTakesCorrectAlarm()
    {
    Alarms alarms = new Alarms();
    alarms.Add(new Alarm(DateTime.Now, "One"));
    alarms.Add(new Alarm(DateTime.Now, "Two"));
    alarms.Add(new Alarm(DateTime.Now, "Three"));
    alarms.RemoveAt(1);
    Assert.AreEqual("One", alarms[0].Description);
    Assert.AreEqual("Three", alarms[1].Description);
    }













    2.







    Compile and run the tests. All still good and green. Check in to source control. Next
    task, please.













    Task: Check That It Is Not Possible to Delete an Alarm That Doesn''t Exist in a Collection














    1.







    This is cool; we are nearing the end of this story, and it has been so easy to validate the functionality. Once again, the test comes first.





    [Test,
    ExpectedException(typeof(ArgumentOutOfRangeException))]
    public void TestRemoveNonExistantAlarm()
    {
    Alarms alarms = new Alarms();
    alarms.Add(new Alarm(DateTime.Now, "One"));
    alarms.Add(new Alarm(DateTime.Now, "Two"));
    alarms.Add(new Alarm(DateTime.Now, "Three"));
    alarms.RemoveAt(4);
    }













    2.







    Then we compile and run the tests. Yes, all good; check in to source control. Let''s get this story done!













    Task: Add UI for Deleting Alarm




    Before we start this, I hope you have refactored out the SetUp and TearDown methods in the MyAlarmFormTests because we need them now.














    1.







    In the MyAlarmFormTestsClass, we''ll start with a test to validate the control we want for deleting an alarm is on the form.





    [Test]
    public void TestDeleteMenuExists()
    {
    FieldInfo menuInfo =
    tForm.GetField("mainMenu",flags);
    MainMenu mainMenu =
    (MainMenu)menuInfo.GetValue(testForm);
    Assert.AreEqual("Delete",
    mainMenu.MenuItems[0].MenuItems[1].Text,
    "Delete menu is not on the Alarms menu");
    }













    2.







    Compile and run the test. It looks like we have to do some work here. We need to add a Delete menu item to the Alarms menu in the design view.













    3.







    The next test we write will check that if no alarm is selected in the list then the delete menu should be disabled. This test does require that we have an understanding of how menus work. We can set up a popup event delegate to get called just before a menu gets displayed. We want to validate that one exists for the alarms menu and that after it has been fired it sets the Enabled property of our Delete menu to false when there are no selected items in the ListView.





    [Test]
    public void TestDeleteDisabled()
    {
    FieldInfo menuInfo =
    tForm.GetField("mainMenu",flags);
    MainMenu mainMenu =
    (MainMenu)menuInfo.GetValue(testForm);
    MethodInfo menuPopupMethod =
    tForm.GetMethod("AlarmsMenu_Popup",flags);
    Object[] args = new object[2];
    args[0] = testForm;
    args[1] = null;
    menuPopupMethod.Invoke(testForm, args);
    Assert.IsFalse(
    mainMenu.MenuItems[0].MenuItems[1].Enabled,
    "Delete menu should be disabled by default");
    }













    4.







    Compile and run this test. It will fail because there is no AlarmMenu_Popup method. We should add this to the code in the MyAlarmForm. You can get an event handler skeleton generated for you by selecting the events from the Properties window of the Alarms menu item and double-clicking the Popup event.





    private void AlarmsMenu_Popup(object sender, System.EventArgs e)
    {
    if (0 <= alarmsList.SelectedItems.Count)
    {
    mainMenu.MenuItems[0].MenuItems[1].Enabled = false;
    }
    else
    {
    mainMenu.MenuItems[0].MenuItems[1].Enabled = true;
    }
    }













    5.







    Compile and run the test again. We should be seeing green for go.













    6.







    We should also write a test to validate that the menu is enabled when an alarm is selected.





    [Test]
    public void TestDeleteEnabled()
    {
    FieldInfo alarmListInfo =
    tForm.GetField("alarmsList",flags);
    ListView alarmList =
    (ListView)alarmListInfo.GetValue(testForm);
    FieldInfo alarmsInfo=
    tForm.GetField("alarms",flags);
    Alarms alarms =
    (Alarms)alarmsInfo.GetValue(testForm);
    DateTime testDateTime =
    new DateTime(2005, 12, 25, 08, 00, 00);
    alarms.Add(new Alarm(testDateTime, "Christmas"));
    MethodInfo refreshMethod =
    tForm.GetMethod("RefreshAlarmList",flags);
    refreshMethod.Invoke(testForm, null);
    alarmList.Items[0].Selected = true;
    alarmList.Select();
    FieldInfo menuInfo =
    tForm.GetField("mainMenu",flags);
    MainMenu mainMenu =
    (MainMenu)menuInfo.GetValue(testForm);
    MethodInfo menuPopupMethod =
    tForm.GetMethod("AlarmsMenu_Popup",flags);
    Object[] args = new object[2];
    args[0] = testForm;
    args[1] = null;
    menuPopupMethod.Invoke(testForm, args);
    Assert.IsTrue(
    mainMenu.MenuItems[0].MenuItems[1].Enabled,
    "Delete menu should be enabled");
    }













    7.







    Compile and run the test. It fails. Hold on a minute; shouldn''t it pass? Didn''t we just write the code in the Popup event method to ensure this test would pass? Let''s look at that method again. Notice anything? We''ve put a less than where we should have a greater than sign! Let''s change that and run the tests again. This is a good example of how tests can help us when we are going too fast.













    8.







    Now let''s write a test to check that we can delete a selected alarm from the list.





    [Test]
    public void TestDelete()
    {
    FieldInfo alarmListInfo =
    tForm.GetField("alarmsList",flags);
    ListView alarmList =
    (ListView)alarmListInfo.GetValue(testForm);
    FieldInfo alarmsInfo=
    tForm.GetField("alarms",flags);
    Alarms alarms =
    (Alarms)alarmsInfo.GetValue(testForm);
    DateTime testDateTime =
    new DateTime(2005, 12, 25, 08, 00, 00);
    alarms.Add(new Alarm(testDateTime, "Christmas"));
    MethodInfo refreshMethod =
    tForm.GetMethod("RefreshAlarmList",flags);
    refreshMethod.Invoke(testForm, null);
    alarmList.Items[0].Selected = true;
    alarmList.Select();
    Assert.AreEqual(1,
    alarmList.Items.Count,
    "Alarm list should contain 1 alarm");
    FieldInfo menuInfo =
    tForm.GetField("mainMenu",flags);
    MainMenu mainMenu =
    (MainMenu)menuInfo.GetValue(testForm);
    mainMenu.MenuItems[0].MenuItems[1].PerformClick();
    Assert.AreEqual(0,
    alarmList.Items.Count,
    "Alarm list should be empty");
    }













    9.







    Compile and run the test. It fails. We need to add some code in a click event for the Delete menu.





    private void AlarmsDeleteMenu_Click(object sender, System.EventArgs e)
    {
    foreach(int index in alarmsList.SelectedIndices)
    {
    alarms.RemoveAt(index);
    }
    RefreshAlarmList();
    }













    10.







    Compile and run the run the tests. All 21 tests should pass. That''s correct, we have written 21 tests already. Pretty good going, that''s 21 ways that we have protected the system from failing. Check in to source control.













    Task: Integrate and Check In to Source Control














    1.







    Run the batch file; we should get the green screen.













    2.







    Check in the files to source control.













    3.







    Take a break.











    Story 3: Alarm Rings Continuously Until I Shut It Off






    Task: Create an Alarm Ring Event














    1.







    Test first.





    [Test]
    public void TestAlarmEventExists()
    {
    Alarms alarms = new Alarms();
    alarms.AlarmRing +=
    new AlarmRingEventHandler(AlarmRing);
    }
    private void AlarmRing(Object obj,
    AlarmRingEventArgs args)
    { }













    2.







    The code doesn''t compile, so we need to add the delegate and event declarations to the Alarms class.





    public class AlarmRingEventArgs: EventArgs {}
    public delegate void AlarmRingEventHandler(Object obj,
    AlarmRingEventArgs args);
    public class Alarms:ArrayList
    {
    public event AlarmRingEventHandler AlarmRing;
    .
    .
    .













    3.







    Compile the code and run the tests. Check in to source control.













    Task: Ensure the Event Gets Called When an Alarm Should Ring














    1.







    This task is the hardest one we have seen to date, and that is not surprising because it really specifies the core functionality of the software we are writing. We will code the test first, of course. We will use an AutoResetEvent from the Threading namespace to indicate when an alarm has gone off.





    using System.Threading;
    .
    .
    .
    private void AlarmRing(Object obj,
    AlarmRingEventArgs args)
    {
    alarmRung.Set();
    }
    private AutoResetEvent alarmRung;
    [Test]
    public void TestAlarmRings()
    {
    Alarms alarms = new Alarms();
    alarmRung = new ManualResetEvent(false);
    alarms.AlarmRing +=
    new AlarmRingEventHandler(AlarmRing);
    alarms.Add(new Alarm(DateTime.Now, "Test Alarm") );
    Assert.IsTrue(alarmRung.WaitOne(1500, false),
    "Alarm didn''t ring");
    }













    2.







    Compile and run the test. It will fail. From here there are a few ways we can go about making this test pass. We will use a worker thread to run in the background and monitor the alarms and then raise the event when an alarm should ring. I have used a common design for threads with a monitor to ensure the thread ends when the object gets disposed.





    using System.Threading;
    public class Alarms:ArrayList, IDisposable
    {
    ManualResetEvent monitorAlarms;
    Thread monitorThread;
    public void Dispose()
    {
    monitorAlarms.Reset();
    }
    public Alarms()
    {
    monitorAlarms = new ManualResetEvent(true);
    monitorThread = new Thread(new
    ThreadStart(this.AlarmMonitorThread));
    monitorThread.Start();
    }
    protected void AlarmMonitorThread()
    {
    while (monitorAlarms.WaitOne())
    {
    foreach(Alarm alarm in this)
    {
    if (alarm.DateTime <= DateTime.Now)
    {
    AlarmRing(this, new AlarmRingEventArgs());
    }
    }
    Thread.Sleep(500);
    }
    }
    .
    .
    .













    3.







    We should write a test to ensure we have coded our thread termination correctly. This is an example where we have jumped ahead of ourselves. We could have written a simpler version of the thread function with a while true) loop. From experience, we should know that this will cause us trouble and do it the correct way, but we should also protect our code from being changed in some way that would prevent the thread from exiting when the object is disposed.





    [Test]
    public void TestAlarmThreadTerminates()
    {
    Alarms alarms = new Alarms();
    alarmRung = new ManualResetEvent(false);
    alarms.AlarmRing +=
    new AlarmRingEventHandler(AlarmRing);
    alarms.Dispose();
    alarms.Add(new Alarm(DateTime.Now, "Test Alarm") );
    Assert.IsFalse(alarmRung.WaitOne(1000, false),
    "Alarm rings after dispose");
    }













    4.







    Compile and run this test; it should pass because we jumped ahead and wrote the code for this for the previous test to pass. Now make sure you run all the tests. You should see that this test fails when the other tests run. We have introduced a dependency in the tests. This is not good. The issue is that Dispose isn''t getting called. The best way to fix this would be to Refactor the AlarmsTests class to contain a SetUp and TearDown method.





    public class AlarmsTests
    {
    Alarms alarms;
    [SetUp]
    public void SetUp()
    {
    alarms = new Alarms();
    }
    [TearDown]
    public void TearDown()
    {
    alarms.Dispose();
    }
    .
    .
    .













    5.







    Compile and run the tests; again, they all pass. Check in to source control. Let''s move on with our next task.













    Task: Check That the Event Does Not Get Called When No Alarm Should Ring














    1.







    The test for this will be similar to the previous test.





    [Test]
    public void TestAlarmDoesntRing()
    {
    alarmRung = new ManualResetEvent(false);
    alarms.AlarmRing +=
    new AlarmRingEventHandler(AlarmRing);
    alarms.Add(new Alarm(DateTime.Now +
    new TimeSpan(1,0,0), "Test Alarm") );
    Assert.IsFalse(alarmRung.WaitOne(1000, false),
    "Alarm rings when it shouldn''t");
    }













    2.







    Compile and run the tests; they all pass, so the code we wrote for the alarm ringing seems to be good. Check in to source control.













    Task: Set Up Delegate Method to Play a Sound Continuously When an Alarm Rings














    1.







    The .NET Framework does not currently support playing sound files. So for playing sounds, we are going to use a third-party library that I have made available for download from my Web site http://eXtreme.NET.Roodyn.com/BookExercises.aspx). This library provides access to the MultiMedia Windows function to play a sound via a static method in a class. To test this, we are just going to write a test that makes sure the method is there and it doesn''t crash. This will protect us from any changes to library that might occur in the future. Because it is a user-interface feature, we will write the test in the MyAlarmFormTests class.





    [Test]
    public void TestPlaySound()
    {
    SoundPlay.Player.PlaySound(@"Ringin.wav");
    }













    2.







    To get this to compile, we need to add a reference to the DLL to our MyAlarm project and place the Ringin.wav file somewhere that it can be accessed. We should add the Ringin.wav file to our source control system also.













    3.







    We can then hook together the parts we need to get the alarm sound to play in the user interface.





    public MyAlarmForm()
    {
    InitializeComponent();
    alarms = new Alarms();
    alarms.AlarmRing +=
    new AlarmRingEventHandler(alarms_AlarmRing);
    }
    private void alarms_AlarmRing(Object obj,
    AlarmRingEventArgs args)
    {
    SoundPlay.Player.PlaySound(@"Ringin.wav");
    }













    4.







    Make sure the tests pass. Check in to source control.













    Task: Add Method to Stop Sound from Playing














    1.







    If we run the MyAlarms application, we can set an alarm and it will ring continuously as per the code and tests we have written. There is no way to stop the alarm. Until we put a call to the Dispose method of the Alarms class in the MyAlarmForm Dispose method, even trying to close the application will not stop the alarm from ringing!





    protected override void Dispose( bool disposing )
    {
    if( disposing )
    {
    if (components != null)
    {
    components.Dispose();
    }
    alarms.Dispose();
    }
    base.Dispose( disposing );
    }













    2.







    We need a test to ensure that the alarm can stop ringing.





    [Test]
    public void TestSwitchAlarmOff()
    {
    alarmRung = new ManualResetEvent(false);
    alarms.AlarmRing +=
    new AlarmRingEventHandler(AlarmRing);
    alarms.Add(new Alarm(DateTime.Now, "Test Alarm") );
    alarms[0].Off = true;
    alarmRung.Reset();
    Assert.IsFalse(alarmRung.WaitOne(1000, false),
    "Alarm rings when it has been switched off");
    }













    3.







    This doesn''t compile; we need to put an Off property on the Alarm class.





    public bool Off
    {
    get{return true;}
    set{ }
    }













    4.







    Compile and run the test; it fails. We need to put code into the Off property and use it to indicate whether the alarm should ring.





    public class Alarm
    {
    .
    .
    .
    private bool off;
    .
    .
    .
    public bool Off
    {
    get{return off;}
    set{off = value;}
    }
    }
    public class Alarms:ArrayList, IDisposable
    {
    protected void AlarmMonitorThread()
    {
    while (monitorAlarms.WaitOne())
    {
    foreach(Alarm alarm in this)
    {
    if (!alarm.Off &&
    alarm.DateTime <= DateTime.Now)
    {
    AlarmRing(this, new AlarmRingEventArgs());
    }
    }
    Thread.Sleep(500);
    }
    }
    .
    .
    .













    5.







    Compile and run the test. It passes, so we must check all the tests in case we have broken anything else. Yes, looks good. Check in to source control.













    Task: Add UI to Enable User to Switch Alarm Sound Off




    We must now enable users to switch an alarm off from the UI. We are not sure as to the best way to do this, and the product manager is not available. We manage to find a potential user elsewhere in the company; her name is Kris.

























    DN: Kris, we have been working on this Alarm program for our product manager, I think you know about it?




    Kris: Yes, I have discussed this with the PM.




    DN: Will you help us, please? We need to know how an alarm should be switched off when it is ringing. We can demo the application so far.




    K: Sure, let''s see.




    <demo application to user>




    K: I think it should show a box with the description of the alarm and a button to switch it off.




    DN: Okay; can the box be dismissed without switching the alarm off?




    K: No, it shouldn''t be possible.




    DN: What if more than one alarm is ringing?




    K: There has to be a separate box for each alarm.




    DN: Okay, thanks




    K: You''re welcome.
































    1.







    This sounds like it might be a larger job than we initially thought. We can start by creating a Test class for our new form, AlarmNotificationFormTests, and adding a test to assert that a button on the form switches an alarm off.





    using System;
    using System.Windows.Forms;
    using NUnit.Framework;
    using System.Reflection;
    namespace MyAlarm
    {
    [TestFixture]
    public class AlarmNotificationFormTests
    {
    [Test]
    public void TestSwitchOffAlarm()
    {
    Alarm alarm = new Alarm();
    alarm.Off = false;
    AlarmNotificationForm testForm =
    new AlarmNotificationForm(alarm);
    testForm.Show();
    BindingFlags flags = BindingFlags.NonPublic|
    BindingFlags.Public|BindingFlags.Static|
    BindingFlags.Instance;
    Type tForm = typeof(AlarmNotificationForm);
    FieldInfo btnInfo =
    tForm.GetField("OffBtn",flags);
    Button offBtn =
    (Button)btnInfo.GetValue(testForm);
    offBtn.PerformClick();
    Assert.IsTrue(alarm.Off,
    "Alarm should be switched off");
    }
    }
    }













    2.







    This doesn''t compile because we haven''t created the Form class yet, so let''s do that. Add a button called OffBtn to the form in the design view, and then in the code we need to change the constructor so that it takes a parameter of type Alarm.





    public AlarmNotificationForm(Alarm alarm)
    {
    .
    .
    .













    3.







    The code will now compile, and we can run the test, which fails. We need to put some code behind the OffBtn click event that switches the alarm off. To do this, we need to keep a reference to the alarm in the class.





    Alarm alarm;
    public AlarmNotificationForm(Alarm alarm)
    {
    InitializeComponent();
    this.alarm = alarm;
    }
    private void OffBtn_Click(object sender, System.EventArgs e)
    {
    alarm.Off = true;
    this.Close();
    }













    4.







    Compile and run the test; it passes, so we must run all the tests. They all pass. Now we need to call the dialog the first time an alarm rings. We can start with a method that displays the dialog. Because the dialog is related to one alarm, we should put that method in the Alarm class. I am not convinced this is a great idea, but it is simple and we can refactor it later if the need arises.





    internal void ShowNotificationForm()
    {
    AlarmNotificationForm frm =
    new AlarmNotificationForm(this);
    frm.ShowDialog();
    }














    Note





    We have given this method the internal access modifier; this allows only files within our assembly to see the method as public. The very fact we have used the internal modifier is a good indicator that we should look at it later for refactoring.






















    5.







    For this method to be called, we need to know which alarm is to ring when we raise the AlarmRing event. We can write a test for this in the AlarmsTests class.





    private void AlarmRingCheckAlarm(Object obj,
    AlarmRingEventArgs args)
    {
    checkAlarm = obj as Alarm;
    alarmRung.Set();
    }
    private Alarm checkAlarm;
    [Test]
    public void TestCorrectAlarmRings()
    {
    alarmRung = new ManualResetEvent(false);
    alarms.AlarmRing +=
    new AlarmRingEventHandler(AlarmRingCheckAlarm);
    Alarm testAlarm =
    new Alarm(DateTime.Now, "Test Alarm");
    alarms.Add(testAlarm );
    Assert.IsTrue(alarmRung.WaitOne(1000, false),
    "Alarm should ring");
    Assert.AreSame(testAlarm, checkAlarm,
    "Alarms should be the same");
    }













    6.







    Compile and run the test. It fails. We need to set the alarm as the first parameter of the delegate method that gets called by the event. This is in the Alarms class.





    protected void AlarmMonitorThread()
    {
    while (monitorAlarms.WaitOne())
    {
    foreach(Alarm alarm in this)
    {
    if (!alarm.Off &&
    alarm.DateTime <= DateTime.Now)
    {
    AlarmRing(alarm, new AlarmRingEventArgs());
    }
    }
    Thread.Sleep(500);
    }
    }













    7.







    Compile and run the tests; they should all pass.













    8.







    We now need to use this alarm in the delegate that gets called on an alarm ringing in the MyAlarmForm class. We only want the form to display once for each alarm, and the event gets called repeatedly until the alarm is switched off. We will use a flag to indicate whether this is the first time the alarm has started ringing. We will put this as a property on the alarm.





    public class Alarm
    {
    .
    .
    .
    private bool started =false;
    public bool StartedRinging
    {
    get{return started;}
    }
    internal void ShowNotificationForm()
    {
    started = true;
    AlarmNotificationForm frm =
    new AlarmNotificationForm(this);
    frm.ShowDialog();
    }
    }
    public class MyAlarmForm : System.Windows.Forms.Form
    {
    .
    .
    .
    private void alarms_AlarmRing(Object obj,
    AlarmRingEventArgs args)
    {
    Alarm alarm = obj as Alarm;
    if (!alarm.StartedRinging)
    {
    Thread thread = new Thread(
    new ThreadStart(alarm.ShowNotificationForm));
    thread.Start();
    }
    SoundPlay.Player.PlaySound(@"Ringin.wav");
    }
    }













    9.







    Compile and run the tests to check we haven''t broken anything, and then run the program and "user test" it. The alarm should ring, and we can switch it off by clicking the button in the notification form that displays. Check in to source control.













    Task: Validate That One Alarm Can Ring After Another Alarm Has Rung and Been Switched Off














    1.







    We can do this with a test in the AlarmsTests class.





    [Test]
    public void TestOneAlarmAfterAnother()
    {
    alarmRung = new ManualResetEvent(false);
    alarms.AlarmRing +=
    new AlarmRingEventHandler(AlarmRingCheckAlarm);
    Alarm testAlarm =
    new Alarm(DateTime.Now, "Test Alarm");
    Alarm testAlarm2 =
    new Alarm(DateTime.Now, "Test Alarm2");
    alarms.Add(testAlarm);
    alarms.Add(testAlarm2);
    Assert.IsTrue(alarmRung.WaitOne(1000, false),
    "Alarm should ring");
    testAlarm.Off = true;
    alarmRung.Reset();
    Assert.IsTrue(alarmRung.WaitOne(1000, false),
    "Alarm2 should ring");
    Assert.AreSame(testAlarm2, checkAlarm,
    "Alarms should be the same");
    }













    2.







    Compile and run the test; it should pass.













    3.







    There is one other thing we should test here: that two alarms can ring at the same time.





    private void AlarmsRinging(Object obj,
    AlarmRingEventArgs args)
    {
    if (!ringingAlarms.Contains(obj))
    {
    ringingAlarms.Add(obj);
    }
    if (ringingAlarms.Count == 2)
    {
    alarmRung.Set();
    }
    }
    private ArrayList ringingAlarms;
    [Test]
    public void TestTwoAlarmsRinging()
    {
    ringingAlarms = new ArrayList();
    alarmRung = new ManualResetEvent(false);
    alarms.AlarmRing +=
    new AlarmRingEventHandler(AlarmsRinging);
    Alarm testAlarm =
    new Alarm(DateTime.Now, "Test Alarm");
    Alarm testAlarm2 =
    new Alarm(DateTime.Now, "Test Alarm2");
    alarms.Add(testAlarm);
    alarms.Add(testAlarm2);
    Assert.IsTrue(alarmRung.WaitOne(1000, false),
    "Both alarms should ring");
    Assert.IsTrue(ringingAlarms.Contains(testAlarm),
    "Alarm1 should ring");
    Assert.IsTrue(ringingAlarms.Contains(testAlarm2),
    "Alarm2 should ring");
    }













    4.







    Compile and run the test. It works! All 31 tests that we have written should now run and pass. We have nearly completed our first iteration. Check in to source control.













    Task: Integrate and Check In to Source Control














    1.







    We need to make sure the WAV file we added to our project gets shipped, so we should include that in the setup project. This is a good example of making sure we are always ready to ship.













    2.







    Run the batch file; we should get the green screen.













    3.







    Check in the files to source control.













    4.







    Send a copy of the alarms setup to our product manager.











    Customer Meeting






    The day after we sent a copy of the setup file, we have a meeting with our customer, the product manager. I''m keen to find out what they have to say.

























    Dr. Neil: Did you get the setup program? Have you had time to look at it?




    Product manager: Yes, it looks okay. Not bad at all.




    DN: Are all the stories from the first milestone complete as far as you''re concerned?




    PM: Yes, sort of, but I want the application to start automatically.




    DN: Okay, that''s not too hard; we''ll put that down as a story. There is something else we realized that we haven''t included.




    PM: What''s that?




    DN: It doesn''t save and load the alarms.




    PM: That''s kind of important!




    DN: Yeah, we just didn''t think about it when we were sitting down the first time, doh! I''ll create three new story cards, Save, Load, and AutoStart




    PM: Okay, great.




    DN: Let me put some costs for delivery on those.













    Story







    Development Cost













    Save alarms







    1













    Load alarms







    1













    Autostart application







    1/2













    PM: Great.




    DN: You had a short iteration 2 scheduled next, so we could put some of that in there.




    PM: I want it all in; then we have something that starts to be marketable.




    DN: Does it make sense to have save and load as separate stories?




    PM: Not really




    DN: Okay, let''s rip those cards up and replace them with one that reads "Load and Save Alarms" with a development cost of two.




    PM: Fine, but I want those in this next iteration''s deliverable.




    DN: Okay, that will be a slightly longer-than-normal iteration, and seeing as it is only an extra half point on the development cost, I will let it pass. But remember, this milestone will be delivered later than normal.




    PM: Sure.




    DN: Great, we''re done and ready to go.






















    At the end of this meeting, the stories for the next iteration are as follows:














    1.







    Edit an alarm.













    2.







    Save and Load an alarm.













    3.







    Autostart the application.











    Following Iterations






    In order to finish this project, you can download the rest of the iterations from the Web site







/ 117