We are going to look at the Provider model and Offloader/Worker model and how we use this approach for our Test Driven Development (TDD) and Unit Testing.  For this Demo we will be using the Northwind Database, and developing this in VB. To start off we need to set up 3 projects in the same solution:

  1. TCNorthwindClass:  This is were we will place our class files for the BLL, DAL, and Interface.
  2. TCNorthwindWeb:  For this demo we will be creating a webservice.
  3. TCNorthwindTest:  For our Unit Tests.

Now lets look at what the provider model is and how we use it with in our application.

Provider Model

We are going to define the provider model as a model in which the provider (access to the DAL) is passed in as an argument to the worker methods.  When developing your application using the provider model, you are going to create first your Interface with which all provider classes will implement.Diag1  

Looking at the diagram to the left you will see that we have our Interface along with 2 class files of which one we are defining as a mock.  We'll look more at this later.  What's important to see here is that in our provider class files we are defining what is to be done with the data.  Each class implements from the interface the function "GetInventoryCountByCategory" and has passed into the function a categoryID.

Reason for developing with this model is to provide a mechanism for changing data providers; for example moving from .NETTIERS to LLBLGEN or LINQ while maintaining control over what methods, properties are exposed from the DAL to the BLL.  Another reason for this model is it provides a means of creating a Mock DAL to Unit test our methods with out interacting with the Database.  This is a key factor when integrating your unit tests into the CI Builds in TFS (Team Foundation Server).  Now you might be thinking to yourself that the Data layer providers such as .NETTIERS, SubSonic, and LLBLGEN provide means of unit testing through mock objects or that you could use RhinoMock and other Mock Object frameworks to mock your objects and DAL with much more functionality.  Well you maybe right, but that also comes with larger overhead and a large amount of functionality that isn't needed. 

Now that we've had a look at the base of our provider model, lets look at some code and the differences between the two providers in diag1 (diagram above).

As you examine the code below you will notice that both providers implements the Function GetInventoryCountByCategory, however they do so in different ways.  The NorthwindProvider is our "custom" DAL which queries the Northwind DB to get the count of products based upon the categoryID and then returns that count.  While the Mock simply takes the categoryID and then returns an integer value based from the select case statement.  We'll look more into the how we set this up as a unit test in a moment.

IProvider.VB

   1: Public Interface IProvider
   2:     Function GetInventoryCountByCategory(ByVal CategoryID As Integer) As Integer
   3: End Interface

TCNorthwindProvider.vb

   1: Imports System
   2: Imports System.Data
   3: Imports System.Data.Sql
   4: Imports System.Data.SqlClient
   5:  
   6: Public Class TCNorthwindProvider
   7:     Implements IProvider
   8:  
   9:  
  10:     Public Function GetInventoryCountByCategory(ByVal CategoryID As Integer) As Integer Implements IProvider.GetInventoryCountByCategory
  11:         Dim count As Integer
  12:         Dim sqlText As String = "SELECT Count(productID) FROM Products WHERE CategoryID = @CategoryID"
  13:         Dim sqlCommand As New SqlCommand(sqlText, GetConnection)
  14:  
  15:         With sqlCommand
  16:             .CommandType = CommandType.Text
  17:             .Parameters.AddWithValue("@categoryID", CategoryID)
  18:             .CommandText = sqlText
  19:  
  20:             Dim dr As SqlDataReader = .ExecuteReader
  21:  
  22:             While dr.Read()
  23:                 count = dr(0)
  24:             End While
  25:             dr.Close()
  26:         End With
  27:  
  28:         Return count
  29:     End Function
  30:  
  31:     Private Function GetConnection() As SqlClient.SqlConnection
  32:         Dim oConn As New SqlClient.SqlConnection
  33:         oConn.ConnectionString = "Data Source=8X5SGF1;Initial Catalog=Northwind;Integrated Security=True"
  34:         oConn.Open()
  35:  
  36:         Return oConn
  37:     End Function
  38:  
  39:    
  40: End Class
TCNorthwindMock.vb
   1: Public Class TCNorthwindMock
   2:     Implements IProvider
   3:     Public Function GetInventoryCountByCategory(ByVal CategoryID As Integer) As Integer Implements IProvider.GetInventoryCountByCategory
   4:         Select Case CategoryID
   5:             Case 1
   6:                 Return 3
   7:             Case 2
   8:                 Return 5
   9:             Case 3
  10:                 Return 7
  11:             Case 4
  12:                 Return 6
  13:             Case 5
  14:                 Return 4
  15:             Case 6
  16:                 Return 2
  17:             Case 7
  18:                 Return 0
  19:             Case 8
  20:                 Return 1
  21:         End Select
  22:     End Function
  23:  
  24:     
  25: End Class

Now lets take a moment to look at the offloader/worker design pattern. 

Offloader/Worker

offloader-worker

 

 

 

 

 

 

 

What you'll notice when looking at this design pattern is that both the Offloader and Unittest (TCNorthwindMethodsTest) pass the work to the worker which in this case is (TCNorthwindMethods).  What this does for us, is allows us to write Unit tests for our worker methods directly, and gives us a single point of entry into the Business logic for the application.  This single point of entry will help with the trouble shooting and the maintenance aspects of the application.  Each method in the Worker class is dependent upon the provider being passed in.  Lets take a quick look at the worker class, Offloader class and unit test to see how they each work together.

 

Worker Class

You will notice in the worker class that we are passing in the provider and then calling the appropriate method in the provider and returning back to the offloader.  This is an example of a provider dependency injection.  There are other types of dependency injections which we will discuss some other time.  For now lets move down to the Offloader and Unit test to see how we work with the Worker class to achieve our results.

   1: Public Class TCNorthwindMethods
   2:     Public Function GetInventoryCountByCategory(ByVal categoryID As Integer, ByVal Provider As IProvider) As Integer
   3:         Return Provider.GetInventoryCountByCategory(categoryID)
   4:     End Function
   5:     Public Function GetProductsByCategoryID(ByVal categoryID As Integer, ByVal Provider As IProvider) As Products
   6:         Return Provider.GetProductsByCategoryID(categoryID)
   7:     End Function
   8:  
   9: End Class

 

Offloader Class

Here you will notice that we are defining 2 private members.

  • TCMethods: Our worker class
  • TCNorthWindProvider: Our Provider

Looking at Line #7 you will see that we are calling GetInventoryCountByCategory in our worker class and passing in which the provider we want to use.  Contrast this against how we are accessing the same method in our unit test.

   1: Public Class TCNorthwindOffLoader
   2:  
   3:     Private TCMethods As New TCNorthwindMethods
   4:     Private TCNorthWindProvider As New TCNorthwindProvider
   5:  
   6:     Public Function GetInventoryCountByCategory(ByVal CategoryID As Integer) As Integer
   7:         Dim iCount As Integer = TCMethods.GetInventoryCountByCategory(CategoryID, TCNorthwindProvider)
   8:         Return iCount
   9:     End Function
  10:     Public Function GetProductsByCategoryID(ByVal CategoryID As Integer) As Products
  11:         Dim products As Products = TCMethods.GetProductsByCategoryID(CategoryID, TCNorthwindProvider)
  12:         Return products
  13:     End Function
  14:  
  15: End Class

 

Unit Test

Looking at our unit test, you will see in our first test method on line #20 we are setting a private member to our provider model.  In this case its our mock model we created earlier.  Now look at line #36 where we are setting a local member to our worker class and then at line 43 where we pass the provider into our worker class.  In this unit test we are hard coding in the value of our categoryID and what our expected return value is.  We will then compare the expected return value against the actual return value to determine if the test passed.  This type of Unit testing is used to determine if our logic / design is going to give us the results we need for the application and wether or not we need to redesign the application before we've accumulated to much technical debt.

   1: 'The following code was generated by Microsoft Visual Studio 2005.
   2: 'The test owner should check each test for validity.
   3: Imports Microsoft.VisualStudio.TestTools.UnitTesting
   4: Imports System
   5: Imports System.Text
   6: Imports System.Collections.Generic
   7: Imports TCNorthwindClass
   8: Imports TCNorthwindClass.TCNorthwindOffLoader
   9:  
  10:  
  11: '''
  12: '''This is a test class for TCNorthwindClass.TCNorthwindMethods and is intended
  13: '''to contain all TCNorthwindClass.TCNorthwindMethods Unit Tests
  14: '''
  15:  _
  16: Public Class TCNorthwindMethodsTest
  17:  
  18:  
  19:     Private testContextInstance As TestContext
  20:     Private tcNorthWindMock As New TCNorthwindMock
  21:  
  22:     Public Property TestContext() As TestContext
  23:         Get
  24:             Return testContextInstance
  25:         End Get
  26:         Set(ByVal value As TestContext)
  27:             testContextInstance = value
  28:         End Set
  29:     End Property
  30:  
  31:     '''
  32:     '''A test for GetInventoryCountByCategory(ByVal Integer)
  33:     '''
  34:      _
  35:     Public Sub GetInventoryCountByCategoryTest()
  36:         Dim target As TCNorthwindMethods = New TCNorthwindMethods
  37:  
  38:         Dim categoryID As Integer = 3 'TODO: Initialize to an appropriate value
  39:  
  40:         Dim expected As Integer = 7
  41:         Dim actual As Integer
  42:  
  43:         actual = target.GetInventoryCountByCategory(categoryID, tcNorthWindMock)
  44:  
  45:         Assert.AreEqual(expected, actual, "TCNorthwindClass.TCNorthwindMethods.GetInventoryCountByCategory did not return th" & _
  46:                 "e expected value.")
  47:     End Sub
  48:  
  49:  
  50:  
  51:     '''
  52:     '''A test for GetProductsByCategoryID(ByVal Integer, ByVal TCNorthwindClass.IProvider)
  53:     '''
  54:      _
  55:     Public Sub GetProductsByCategoryIDTest()
  56:         Dim target As TCNorthwindMethods = New TCNorthwindMethods
  57:  
  58:         Dim categoryID As Integer = 3 'TODO: Initialize to an appropriate value
  59:  
  60:  
  61:  
  62:         Dim expected As New Products
  63:         For i As Integer = 0 To categoryID
  64:             Dim tProduct As New Product
  65:             With tProduct
  66:                 .ProductID = i
  67:                 .ProductName = String.Format("Item{0}", i)
  68:                 .QuantityPerUnit = i.ToString
  69:                 .ReorderLevel = i
  70:                 .Discontinued = False
  71:             End With
  72:             expected.product.Add(tProduct)
  73:         Next
  74:  
  75:  
  76:  
  77:         Dim actual As Products
  78:  
  79:         actual = target.GetProductsByCategoryID(categoryID, tcNorthWindMock)
  80:  
  81:         Assert.AreEqual(expected.product.Count, actual.product.Count, "TCNorthwindClass.TCNorthwindMethods.GetProductsByCategoryID did not return the ex" & _
  82:                 "pected value.")
  83:        
  84:  
  85:         
  86:     End Sub
  87: End Class

 

 The final bit of code I want to look at is a webservice I created to use the offloader class to utilize with in the application.

Here you will see that we have created a private member to our offloader and are passing in the categoryID to the offloader which then returns the results.

   1: Imports System.Web.Services
   2: Imports System.Web.Services.Protocols
   3: Imports System.ComponentModel
   4: Imports TCNorthwindClass
   5:  
   6: Namespace:="http://tempuri.org/")> _
   7: 
   8: False)> _
   9: Public Class TCNorthwindService
  10:     Inherits System.Web.Services.WebService
  11:     Private tcWorker As New TCNorthwindOffLoader
  12:      _
  13:     Public Function GetInventoryCountByCategory(ByVal CategoryID As Integer) As String
  14:  
  15:         Return tcWorker.GetInventoryCountByCategory(CategoryID)
  16:     End Function
  17:      _
  18:         Public Function GetProductsByCategoryID(ByVal CategoryID As Integer) As Products
  19:         Return tcWorker.GetProductsByCategoryID(CategoryID)
  20:     End Function
  21:  
  22:  
  23: End Class

 For further information and to demonstrate the actual creation and running of a unit test, please refer to the 3 screen casts linked below.

Thank You

David Yancey 

Part 1
http://www.screencast.com/t/PRz3QnkoP

Part 2
http://www.screencast.com/t/3SGkZ9Os

Part 3
http://www.screencast.com/t/3iC1qSDXXXu