DevPinoy.org
A Filipino Developers Community

>>> First two to make 3 wins! <<<

TDD Step by Step, Part 2: The Database

Welcome to part two of our series on building a Windows application using test-driven development (TDD). In the previous article we drove the design of our entity classes and data access layer by means of unit tests. The unit tests acted more as specifications for the system rather than tests, since we actually weren’t testing any output from our system. We simply used the tests to illustrate how we want to interact with our data access layer and what properties are exposed for each entity class.

Now we move on to the next step, which is creating the actual data access layer for the application. Keith graciously shared with us the MS Access database file containing the entire Bible (I wonder if there are any copyright issues with this?). If you would open the .mdb file you will see only three tables inside: Testaments, Books and Verses.

So what we are going to do now is to map the database to our classes by means of our IDataService implementation. Since I’m not very good when it comes to names, I’ll just call our implementation DataService. Since we are test-driven, we cannot go directly to coding without creating test code first. Strictly speaking, we will not actually be creating unit tests but integration tests because we are interacting with an external subsystem (the database). So let’s load the solution we’ve been working on, and add this first test to the BibleCodeTests project. I named the class DbIntegrationTests.cs:

1.  using Microsoft.VisualStudio.TestTools.UnitTesting;

2.  using BibleCode;

3.   

4.  namespace BibleCodeTests {

5.      [TestClass]

6.      public class DbIntegrationTests {

7.          private static string connString =

8.  @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Path\To\BibleDB.mdb;";

9.          [TestMethod]

10.         public void GetOldTestament() {

11.             IDataService srv = new DataService(connString);

12.             Testament oldT = srv.GetTestamentByName("Old Testament");

13.             Assert.AreEqual(1, oldT.Id);

14.             Assert.AreEqual("Old Testament", oldT.Name);

15.         }

16. }

Again, notice how we are specifying our intentions using this test. We expect to have a DataService class that implements the IDataService interface we developed (in Part 1), and we pass a connection string to initialize the DataService. For this test, we will be retrieving the object corresponding to the Old Testament. If you’d notice in the Testaments table from the actual database, the record for it has a TestamentID (primary key) value of 1.

So let’s code DataService.cs to pass this test:

1.  using System;

2.  using System.Collections.Generic;

3.  using System.Data.OleDb;

4.   

5.  namespace BibleCode {

6.      public class DataService : IDataService {

7.          private string connectionString;

8.   

9.          public DataService(string connString) {

10.             connectionString = connString;

11.         }

12.  

13.         public Testament GetTestamentByName(string input) {

14.             OleDbConnection conn = new OleDbConnection(connectionString);

15.             OleDbCommand comm = new OleDbCommand("SELECT [TestamentID],[Name] FROM [Testaments] WHERE [Name]=@name", conn);

16.             comm.Parameters.Add("@name", OleDbType.VarChar).Value = input.Trim();

17.             conn.Open();

18.             Testament output = null;

19.             OleDbDataReader reader = comm.ExecuteReader();

20.             while (reader.Read()) {

21.                 output = new Testament( Id = reader.GetInt32(0), Name = reader.GetString(1).Trim() );

22.             }

23.             reader.Close();

24.             conn.Close();

25.             return output;

26.         }

27. }

We use the OleDb* classes in ADO.NET to connect to the MS Access database. Compiling the app will fail because we have not yet implemented the other methods specified by the IDataService interface. For now just create stub methods for this that return null, or you can use a tool like ReSharper to generate stub methods that throw a NotImplementedException when called. Well, whatever…just make sure it compiles. When it finally compiles and you run the tests, all the tests (including our unit tests from Part 1) should pass.

So let’s proceed to the next method we want to develop in our DataService: retrieving all Testament objects. Here’s the test:

1.         [TestMethod]

2.          public void GetAllTestaments() {

3.              IDataService srv = new DataService(connString);

4.              Testament[] testaments = srv.GetAllTestaments();

5.              Assert.AreEqual(2, testaments.Length);

6.              Assert.AreEqual(1, testaments[0].Id);

7.              Assert.AreEqual("Old Testament", testaments[0].Name);

8.              Assert.AreEqual(2, testaments[1].Id);

9.              Assert.AreEqual("New Testament", testaments[1].Name);

10.         }

And the DataService method:

1.         public Testament[] GetAllTestaments() {

2.              OleDbConnection conn = new OleDbConnection(connectionString);

3.              OleDbCommand comm = new OleDbCommand("SELECT [TestamentID],[Name] FROM [Testaments] ORDER BY [TestamentID]", conn);

4.              List<Testament> testaments = new List<Testament>();

5.              conn.Open();

6.              OleDbDataReader reader = comm.ExecuteReader();

7.              while (reader.Read()) {

8.                  Testament t = new Testament();

9.                  t.Id = reader.GetInt32(0);

10.                 t.Name = reader.GetString(1).Trim();

11.                 testaments.Add(t);

12.             }

13.             reader.Close();

14.             conn.Close();

15.             return testaments.ToArray();

16.         }

At this point you may notice some code patterns repeating here. It is a good thing to eliminate duplication (DRY principle), so let’s exercise the “yellow” or refactoring stage in our red-green-refactor cycle. This means we improve our code while ensuring that all our tests still pass. We can start off by creating a private method that creates the OleDbConnection for us:

1.         private OleDbConnection CreateConnection() {

2.              return new OleDbConnection(connectionString);

3.          }

Hmm, come to think of it, we can do the same for the creation of the OleDbCommand as well:

1.         private OleDbCommand CreateCommand(string query) {

2.              return new OleDbCommand(query, CreateConnection());

3.          }

We can still spot duplication in the code – the connection is opened, the command is executed, then a reader is created, the reader is iterated, the reader is closed then finally the connection is closed. A way to eliminate that is to create a private method that will do the dirty work for us; we will just pass a delegate block to do what we want to do within the reader iteration. For that, we define a delegate type called “ReaderDelegate” and we create another private method that uses it:

1.         public delegate void ReaderDelegate(OleDbDataReader reader);

2.   

3.