Search

Categories

Send mail to the author(s) E-mail

# Wednesday, October 21, 2009
Customer tests confirm how the feature is supposed to work as experienced by the end user.

Because customers generally don't want to use nUnit.. we're going to use Fitnesse, which is more user friendly:

The goal is to have the customer write their own tests.

To setup Fitnesse.  Download the .jar and run it like this:

c:\apps\fitnesse -jar fitness.jar -p 8080

Then go and get slim for .net.  I installed it to c:\apps\slim



Here is how it works for the Divider example:

!define TEST_SYSTEM {slim}
!define COMMAND_PATTERN {%m -r fitSharp.Slim.Service.Runner,c:\apps\slim\fitsharp.dll %p}
!define TEST_RUNNER {c:\apps\slim\Runner.exe}
 
!path C:\code\slimexample\slimexample\bin\Debug\slimexample.dll

|import      |
|slim_example|

!|Divider|
|Numerator|Denominator|Quotient?|
|10       |2          |5      |
|12.6     |3          |4.2      |
|22       |7          |~=3.14   |
|9        |3          |<5       |
|11       |2          |4<_<6    |
|100      |4          |33       |

Here is the C# code, I compiled to slimexample.dll
using System;

namespace slim_example
{
    public class Divider
    {
        public double  Numerator { get; set; }
        public double Denominator { get; set; }
        double _quotient;

        public void Execute()
        {
            _quotient = Numerator / Denominator;
        }

        public double Quotient()
        {
            return _quotient;
        }
    }
}
Run the tests.. and we'll see that when 2 numbers are sent... then then ? in the Fitnesse means that something should be checked.


Then we wire up to our code and test CatalogAdapter:

From this test we can see that I've a bug.. something wrong with the Duration.



Debugging

Have been working through why we are not getting the duration through.  The unit tests on the DAL are passing fine.. in CatalogFixture, have added in 2 tracks of different lengths.  They get pushed into the db, then pulled out using Catalog.. looks ok.



The next step is to look at the Service layer and see if the Duration is being written.  Interestingly, the webservice is passing back all the correct tracks, but nothing to do with reviewers etc..




Very interesting bug.. if I didn't specify this in my RecordingAssembler.WriteTotalRunTime:

dto.totalRunTimeSpecified = true;

then nothing would be passing back.  Same for WriteAverageRating.


Came across another issue with the webservice, and couldn't get it to autogen.. easiest way was to create it again.  Using source control so it if all went to custard, could go back.

Been getting bogged down in detail, so have decided to push to the end of the project, and get a front end up and running asap.



Comments [0] | | # 
# Tuesday, October 20, 2009
Task:  create a WebService that returns a recording and all its entities when specifying the id of the recording. Return type of XML (ie not a .net dataset).

The DAL.Catalog returns a RecordingDataSet.Recording.

We need to write a DTO that takes an input and transforms(munges) it into something else

Using a Service Interface Pattern.

Build a stub, map it into a DTO, and verify each field.
First thing I did was to create a stub, to hide how the RecordingDataSet is retrieved..so am not relying on the DAL at this point.

Here is the test the Stub code:
[TestFixture]
  public class CatalogServiceStubFixture
     {
         RecordingDataSet.Recording recording;
         RecordingDataSet.Recording actual;
         CatalogServiceStub service;
 
         [SetUp]
         public void SetUp()
         {
             // create a test recording in memory
             recording = CreateRecording();
             service = new CatalogServiceStub(recording);
             actual = service.FindByRecordingId(recording.Id);
         }
 
         private RecordingDataSet.Recording CreateRecording() 
         {
             RecordingDataSet dataSet = new RecordingDataSet();
             RecordingDataSet.Recording recording = dataSet.Recordings.NewRecording();
             recording.Id = 1;
            // more code needed here to fill in the rest of the recording.
             return recording;
         }
 
         [Test]
         public void CheckId()
         {
             Assert.AreEqual(recording.Id, actual.Id);
         }

Next we want a dto to transform our dataset, into something else:



To specify in platform independant, we use XML.  This is still quite bulky, perhaps JSON would be lighter now.

<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:tns="http://nunit.org/webservices" elementFormDefault="qualified" targetNamespace="http://nunit.org/webservices" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="Recording" type="tns:RecordingDto" />
  <xs:complexType name="RecordingDto">
    <xs:sequence>
      <xs:element minOccurs="1" maxOccurs="1" name="id" type="xs:long" />
      <xs:element minOccurs="1" maxOccurs="1" name="title" type="xs:string" />
      <xs:element minOccurs="1" maxOccurs="1" name="artistName" type="xs:string" />
      <xs:element minOccurs="1" maxOccurs="1" name="releaseDate" type="xs:string" />
      <xs:element minOccurs="1" maxOccurs="1" name="labelName" type="xs:string" />
      <xs:element minOccurs="0" maxOccurs="unbounded" name="tracks" type="tns:TrackDto" />
      <xs:element minOccurs="0" maxOccurs="unbounded" name="reviews" type="tns:ReviewDto" />
      <xs:element minOccurs="0" maxOccurs="1" name="totalRunTime" type="xs:int" />
      <xs:element minOccurs="0" maxOccurs="1" name="averageRating" type="xs:int" />
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="TrackDto">
    <xs:sequence>
      <xs:element minOccurs="1" maxOccurs="1" name="id" type="xs:long" />
      <xs:element minOccurs="1" maxOccurs="1" name="title" type="xs:string" />
      <xs:element minOccurs="1" maxOccurs="1" name="artistName" type="xs:string" />
      <xs:element minOccurs="1" maxOccurs="1" name="duration" type="xs:string" />
      <xs:element minOccurs="1" maxOccurs="1" name="genreName" type="xs:string" />
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="ReviewDto">
    <xs:sequence>
      <xs:element minOccurs="1" maxOccurs="1" name="id" type="xs:long" />
      <xs:element minOccurs="1" maxOccurs="1" name="reviewerName" type="xs:string" />
      <xs:element minOccurs="1" maxOccurs="1" name="rating" type="xs:int" />
      <xs:element minOccurs="1" maxOccurs="1" name="reviewContent" type="xs:string" />
    </xs:sequence>
  </xs:complexType>
</xs:schema>


A benefit is that we can create C# from this:.. but really just getters and setters.  See below.. this led to an interesting bug.

http://www.codingday.com/xml-c-class-generator-for-c-using-xsd-for-deserialization/

xsd asdf.xml - create an xsd
xsd asdf.xsd /CLASSES  - creates a .cs

Then just creating an 'Assembler', which writes out the DTO...(from http://martinfowler.com/eaaCatalog/dataTransferObject.html)

The Assembler has business logic in it to flatten the structure (as in the similar example above)

The webservice is setup by adding it into IISAdmin.

As the default ASP.NET user does not have access to my local database
connection = new SqlConnection("Data Source=DAVEXPLAPTOP;Initial Catalog=catalog;Integrated Security=True");

The easiest way to get running on dev was to add the DAVEXPLAPTOP\ASPNET user into my SQL database.  This didn't show up under objects when adding, but I went ahead anyway and it worked.







So this is what we have so far:


Comments [0] | | # 
From the nbdn course, I had some interesting code I'd like to test.  Just the business logic really, no DAL nor UI.  As I wanted to make a full application, the first though was to make a DAL which talked to a text file, or just some dummy data.  How to do this well?  De-coupled.  Tried to use poor mans dependency injection, and got confused.  So I had a coffee, and bought this book.  Because it had an end to end example of a testable application.  It uses .net1.1 mostly (which is good as there are no language constructs to learn)



Some good introduction to refactorings, and unit testing.  The main meat of the book is an example called MediaLibrary (music recordings).  Chapter 5 started with testing the DAL.

Here are the first tests.  Am showing nunit test runner.. mostly I use testdriven.net, and have setup ctrl alt shft t to run tests.



for source control I'm using  git, and have coupled up with gvim as text editor.  And linked in beyond compare for a merge tool.

The book uses Strongly Typed Datasets, which were the 'star of the show' in the .net1.0 release.  By writing the sql, and having intellisense have autoproperties, this saves writing boiler plate code.

I learned about DataSets.. here is my edit (from wikipedia)..http://en.wikipedia.org/wiki/ADO.NET#DataSets

ADO.NET consists of two primary parts:

Data provider

These classes provide access to a data source, such as a Microsoft SQL Server.  Each data source has its own set of provider objects, but they each have a common set of utility classes:

  • Connection: Provides a connection used to communicate with the data source.
  • Command: Used to perform some action on the data source, such as reading, updating, or deleting relational data.
  • Parameter: Describes a single parameter to a command. A common example is a parameter to a stored procedure.
  • DataAdapter: A bridge used to transfer data between a Data source and a DataSet object (see below).
  • DataReader: Used to efficiently process a large list of results one record at a time. It allows records to be accessed in a read-only, forward-only mode, i.e., records have to be accessed in sequential order; they can neither be randomly accessed nor can a record which has been processed previously be accessed again.

DataSets

DataSet objects, a group of classes describing a simple in-memory relational database,

  • A DataSet object represents a schema (either an entire database or a subset of one). It can contain tables and relationships between those tables.
    • A DataTable object represents a single table in the database. It has a name, rows, and columns.
      • A DataView object overlays a DataTable and sorts the data (much like an SQL "order by" clause) and filters the records (much like an SQL "where" clause) if a filter is set. An in-memory index is used to facilitate these operations. All DataTables have a default filter, while any number of additional DataViews can be defined, reducing interaction with the underlying database and thus improving performance.
        • A DataColumn represents a column of the table, including its name and type.
        • A DataRow object represents a single row in the table; it allows reading and updating of values in that row, likewise retrieving any rows that are related to it through a primary-key foreign-key relationship.
VS has a nice editor:



Once I spiked up what datasets were (in simpleDatabaseTests.cs).. used this for reference: http://www.csharp-station.com/Tutorials/AdoDotNet/Lesson05.asp
 [Test]
        public void put_data_into_a_dataset()
        {
            DataSet dataset = new DataSet();
            SqlDataAdapter data_adapter = new SqlDataAdapter("select id, name from Artist", connection);
            SqlCommandBuilder sql_command_builder = new SqlCommandBuilder(data_adapter);
            data_adapter.Fill(dataset, "Artist");
            Assert.IsNotNull(dataset);

            DataTable data_table = dataset.Tables[0];
            for (int i = 0; i < data_table.Rows.Count; i++)
            {
                DataRow data_row = data_table.Rows[i];
                Console.Out.Write(data_row["id"] + " ");
                Console.Out.WriteLine(data_row["name"]);
            }
        }

The example I'm working through uses a Table Data Gateway Pattern for the DAL. 

http://martinfowler.com/eaaCatalog/tableDataGateway.html
"A Table Data Gateway holds all the SQL for accessing a single table or view: selects, inserts, updates, and deletes. Other code calls its methods for all interaction with the database."

Lets take an example of ArtistFixture.cs which tests ArtistGateway.cs

[TestFixture]
    public class ArtistFixture
    {
        static readonly string artistName = "Artist";
        SqlConnection connection;
        ArtistGateway gateway;
        RecordingDataSet recordingDataSet;
        long artistId;

        [SetUp]
        public void setup_and_open_connection_pass_to_gateway_setup_data_set()
        {
            connection = new SqlConnection(ConfigurationSettings.AppSettings.Get("Catalog.Connection"));
            connection.Open();

            recordingDataSet = new RecordingDataSet();
            gateway = new ArtistGateway(connection);
            // insert a new artist getting its ID from the database
            artistId = gateway.Insert(recordingDataSet, artistName);
        }

        [Test]
        public void RetrieveArtistFromDatabase()
        {
            // create new RDS, use same gateway.
            RecordingDataSet loadedFromDB = new RecordingDataSet();
            RecordingDataSet.Artist loadedArtist = gateway.FindById(artistId, loadedFromDB);

            Assert.AreEqual(artistId, loadedArtist.Id);
            Assert.AreEqual(artistName, loadedArtist.Name);
        }
Here is part of ArtistGateway.cs.. gateway is newed up with a connection passed in.. then only in Insert is it passed in a strongly typed RecordingDataSet, which has all the SQL in it which has been auto-genned.
public class ArtistGateway
{
SqlDataAdapter adapter;
SqlConnection connection;
SqlCommand command;
SqlCommandBuilder builder;

public ArtistGateway(SqlConnection connection)
{
this.connection = connection;

command = new SqlCommand("select id, name from artist where id = @id",connection);
command.Parameters.Add("@id", SqlDbType.BigInt);

adapter = new SqlDataAdapter(command);
builder = new SqlCommandBuilder(adapter);
}


public RecordingDataSet.Artist FindById(long artistId, RecordingDataSet recordingDataSet)
{
command.Parameters["@id"].Value = artistId;
adapter.Fill(recordingDataSet, recordingDataSet.Artists.TableName);
DataRow[] rows = recordingDataSet.Artists.Select(String.Format("id={0}", artistId));

if (rows.Length < 1) return null;
return (RecordingDataSet.Artist) rows[0];
}
Class Diagram done in VS2008 with the Class Designer Powertoy http://www.codeplex.com/modeling
To get the blue lines go to Class Diagram, Filter Lines, Show All Associations.

Sequence diagram done in Visio: (hmm - this is a bit complex... I prefer the class diagram above with notes)


Genre was next, then the rest.  Then relationships between objects eg ReviewReviewer.. just testing the test data we create comes back:  Have abstracted out some of the setup code into ConnectionFixture.
 [TestFixture]
 public class ReviewReviewerFixture : ConnectionFixture
 {
     [Test]
     public void ReviewerId()
     {
         RecordingDataSet recordingDataSet = new RecordingDataSet();

         ReviewGateway reviewGateway = new ReviewGateway(Connection);
         long reviewId = reviewGateway.Insert(recordingDataSet, 1, "Review Content");

         ReviewerGateway reviewerGateway = new ReviewerGateway(Connection);
         long reviewerId = reviewerGateway.Insert(recordingDataSet, "Reviewer Name");

         RecordingDataSet.Review review = reviewGateway.FindById(reviewId, recordingDataSet);

         review.ReviewerId = reviewerId;
         reviewGateway.Update(recordingDataSet);

         Assert.AreEqual(reviewerId, review.Reviewer.Id);

         reviewGateway.Delete(recordingDataSet, reviewId);
         reviewerGateway.Delete(recordingDataSet, reviewerId);
     }
 }
The hardest test is RecordingGateway, as it depends on all the other 'tables'.  To simplify, we abstract out the insert/del of test data to RecordingBuilder.cs.

The last class is Catalog, which is the only class that will be called from the Service layer.  In Catalog we have FindByRecordingId, which returns a RecordingDataSet.Recording (so all the data associated with this recordingId).




In summary we have done this of the overall structure of the APP:

Comments [0] | | # 
# Saturday, July 18, 2009
A simple MVC (Model View Controller) implementation..code by Jimmy Chandra
http://stackoverflow.com/questions/1107720/mvc-c-simplest-possible-implementation

Why use MVC?  Similar reasons to MVP - testability and seperation of concerns

Simple.MVC.zip (167.25 KB)



1. Program.cs runs, running Main.. as is standard in a Win Forms app.  Difference from MVP is that main here is instantiating and running a controller class first of all, instead of newing up the view, which calls the controller/presenter.
static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            
            TopEmployeeController controller = new TopEmployeeController();
            Application.Run(controller.View as Form);
        }
2. Controller is instantiated and constructor called:
        private ITopEmployeeView _view;
        private Employee _employee;

        private bool _monitoring;

        public TopEmployeeController()
        {
            _employee = new Employee();
            _view = new TopEmployeeForm(this, _employee);
        }
3. Controller news up an employee (dumb).. then a view of type TopEmployeeForm, passing its self (the controller) and employee to it.

4. View (the observer) subscribes to the model (the subject) so that when model.OnPropertyChange is called, view.UpdateView is called.  This is the observer pattern, made easier in C# using events.. which is using a delegate.
// view
public TopEmployeeForm(ITopEmployeeController controller, Employee model)
        {
            _controller = controller;
            _model = model;

            //Let the model know that this view is interested if the model change
            _model.OnPropertyChange += new Action(UpdateView);

            InitializeComponent();
}
6. When Button is pressed on the view.  The controller.GetTopEmplyee method is called.  This calls model.MontorChanges:
// controller
public void GetTopEmployee()
{
if (!_monitoring)
{
_monitoring = true;
_employee.MonitorChanges();
}
}



7. MonitorChanges does some waiting code, then every second calls FirePropertyChange(). Which calls model.OnPropertyChange.
Which is really a delegate to View.UpdateView
// model
public event Action OnPropertyChange;

private void FirePropertyChange()
        {
            var propChange = OnPropertyChange;
            if (propChange != null)
            {
                OnPropertyChange();
            }
        }

8. The view method which runs every second and displays the name on the form.
// view
        public void UpdateView()
        {
            // to do with threading
            if (this.InvokeRequired)
            {
                this.Invoke(new Action(UpdateView));
            }
            else
            {
                TopEmployeeName = _model.FullName;
            }
        }




Comments [1] | | # 
# Thursday, July 16, 2009
( MVP | Patterns | Testing )
Easy to understand, working example WinForms app using MVP (Model View Presenter) pattern.

Why use this pattern?  Testability and maintainability (ie loosely coupled so less likely to break!)

Download noddy.zip (106.62 KB) (VS2008)

Notes:
View is dumb and does render logic
View never talks to the model (or any business entity eg customer) directly
Presenter retries data and manipulates
Model is the data and business rules

usually MVP sits on top of an ORM or Database Abstraction / Business Logic layer:


How it works:

1) Application comes to Program.cs which calls Form1.cs.. no MVP code here.
static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// Standard code.. nothing MVP
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }

2) Form1 (which is the view in MVP) which implements IView interface (**why**).. instantiates a new MainPresenter calling it presenter.
public partial class Form1 : Form, IView
    {
        private MainPresenter presenter;

        public Form1()
        {
            InitializeComponent();
             presenter = new MainPresenter(this);
        }

        private void btnPostCustomer_Click(object sender, EventArgs e)
        {
            presenter.PostCustomer();
        }

        // IView members
        public string FirstName
        {
            get { return txtFirstName.Text; }
            set { txtFirstName.Text = value; }
        }

        public string LastName
        {
            get { return txtLastName.Text; }
            set { txtLastName.Text = value; }
        }

        public void DisplayResult(string result)
        {
            MessageBox.Show(result);
        }
    }
3) MainPresenter constructor runs, and is passed in the view object of type IView. (**could we have just passed it in as a Form object?**)

public class MainPresenter
    {
        private IView view;
        private MainModel model = new MainModel();

        public MainPresenter(IView view)
        {
            this.view = view;    
        }

        public void PostCustomer()
        {
            Debug.Assert(view != null);

            try
            {
                Customer customer = new Customer(view.FirstName, view.LastName);
                model.PostCustomer(customer);
                view.DisplayResult(customer.ToString() + " posted");
            }
            catch(Exception ex)
            {
                view.DisplayResult(ex.Message);
            }
        }
    }
4) App is run, names are filled in, and Post button is submitted.



5) View code (form1.cs) runs presenter.PostCustomer
private void btnPostCustomer_Click(object sender, EventArgs e)
        {
            presenter.PostCustomer();
        }
6) presenter creates a new customer (which could be seen as being outside of MVP, and more in an entity later, as the model layer is usually light, and only relating the modelviews) and passes in the names.  Nothing special on the constructor of customer. 

public void PostCustomer()
        {
            Debug.Assert(view != null);

            try
            {
                Customer customer = new Customer(view.FirstName, view.LastName);
                model.PostCustomer(customer);
                view.DisplayResult(customer.FullName() + " posted");
            }
            catch(Exception ex)
            {
                view.DisplayResult(ex.Message);
            }
        }
7) Presenter calls the main model to PostCustomer which writes out to disk the Customer which has just been entered.
public void PostCustomer(Customer customer)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(Customer));

            if (File.Exists("Customer.xml"))
            {
                using (FileStream stream = new FileStream("Customer.xml", FileMode.Append, FileAccess.Write))
                {
                    serializer.Serialize(stream, customer);
                }
            }
            else
            {
                using (FileStream stream = new FileStream("Customer.xml", FileMode.Create, FileAccess.Write))
                {
                    serializer.Serialize(stream, customer);
                }
            }
        }



8) Presenter calls the view to DisplayResult, passing in the customer data as a string.
public void DisplayResult(string result)
        {
            MessageBox.Show(result);
        }


Included in the download are simple tests on the ViewModel and Domain model.  Also Form2 which has a Form2Presenter.  Uses same ViewModel (hmm probably should have a seperate one), and the same domain model (customer.cs).  Next step for me is to get tests working on the Presenters.




Comments [0] | | # 
# Friday, January 16, 2009
( Testing )



Girl and man looking towards Mt Cook, NZ.

To get nUnit working on VS 2008 Express, firstly download and install nUnit.

Make a new console application.  Add the 3 nunit references you see below



Open up the .csproj file.. in my case in: C:\code\stuff\HowNUnitInspiredConsole\HowNUnitInspiredConsole\HowNUnitInspiredConsole.csproj

Where you see... add in the two Startxxx lines.

 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

    <StartAction>Program</StartAction>
    <StartProgram>C:\Program Files\NUnit 2.4.8\bin\nunit.exe</StartProgram>
   
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>

Here is my first unit test for a web app:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using System.Web.Script.Serialization;
using System.Web.Util;
using System.Net;
using System.Web;
using System.IO;
using System.Diagnostics;

namespace HowNUnitInspiredConsole
{
    class NUnitConsoleRunner
    {
        [STAThread]
        static void Main(string[] args)
        {
            NUnit.ConsoleRunner.Runner.Main(args);
        }
    }

    [TestFixture]
    public class FormTest
    {
        // 1 is dev / local
        // 2 is test
        int debugbit = 2;

        string targetUri = "";
        string targetUriNoHTTP = "";

        [SetUp]
        public void Init()
        {
            if (debugbit == 1)
            {
                targetUri = "http://192.168.139.128/drink/";
                targetUriNoHTTP = "192.168.139.128/drink/";
            }

            if (debugbit == 2)
            {
                targetUri = "http://www.davemateer.com/drink/";
                targetUriNoHTTP = "www.davemateer.com/drink/";
            }
        }

        [Test]
        public void helloWorld()
        {
            WebClient client = new WebClient();
            StreamReader reader = new StreamReader(client.OpenRead(targetUri + "test/helloWorld.php"));
            string responseFromServer = reader.ReadToEnd();

            Assert.AreEqual("Hello World", responseFromServer);
        }
    }
}



When the nUnit gui popped up, make sure it gets the correct .exe file.  I had to do a project add assembly, while the application was running.

Run your application which should pop up with something like this:


Comments [0] | | #