测试驱动开发例子学习-Using TDD with ADO.NET
 

2009-10-28 作者:jasonw 来源:jasonw的BLOG

 
用C#做过项目,不过开发的不很爽 .很想美美的驱动和重构,一直没有机会.弄点代码来学习吧 .

1.连接字符串的测试驱动范例:

DataBase -SQL Server 2000:
1.Connecting to the Database
2.XML应用程序的配置文件:
 <?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <appSettings>
      <add key="Catalog.Connection"
        value="server=(local);database=catalog;Trusted_Connection=true" />
   </appSettings>
</configuration>

using System;

using System.Data;

using System.Data.SqlClient;

using NUnit.Framework;

 

[TestFixture]

public class SqlConnectionFixture

{

   [Test]

   public void ConnectionIsOpen()

   { 
    //使用配置文件定义连接字符串   
    //string connectionString =

    //     ConfigurationSettings.AppSettings.Get("Catalog.Connection");

    //  Assert.IsNotNull(connectionString);

 

      SqlConnection connection =

         new SqlConnection(

           @"server=(local);database=catalog;Trusted_Connection=true");

      connection.Open();

      Assert.AreEqual(ConnectionState.Open, connection.State);

      connection.Close();

   }

}

2.连接字符串的测试驱动优化范例:


以上代码可以替换为:

[TestFixture]

public class SqlConnectionFixture

{

   private string connectionString;

 

   [SetUp]

   public void RetrieveConnectionString()

   {

      connectionString =

         ConfigurationSettings.AppSettings.Get("Catalog.Connection");

   }

 

   [Test]

   public void CanRetrieveConnectionString()

   {

      Assert.IsNotNull(connectionString);

   }

 

   [Test]

   public void ConnectionIsOpen()

   {

      SqlConnection connection =

         new SqlConnection(connectionString);

      connection.Open();

      Assert.AreEqual(ConnectionState.Open, connection.State);

      connection.Close();

   }

}

3.ArtistGateway实现数据访问层的一个范例

对数据表artist的通用操作

using System;
using System.Data;
using System.Data.SqlClient;
namespace DataAccessLayer
{
   public class ArtistGateway
   {
      private SqlDataAdapter adapter; 
      private SqlConnection connection;
      private SqlCommand command;
      private 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 long Insert(
RecordingDataSet recordingDataSet, string artistName)
      {
         long artistId = 
GetNextId(recordingDataSet.Artists.TableName);
         
         RecordingDataSet.Artist artistRow = 
            recordingDataSet.Artists.NewArtist();
         artistRow.Id = artistId;
         artistRow.Name = artistName;
         recordingDataSet.Artists.AddArtist(artistRow);
         adapter.Update(recordingDataSet, 
         recordingDataSet.Artists.TableName);
         return artistId;
      }
      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]; 
      }
      public long GetNextId(string tableName)
      {
         SqlTransaction transaction = 
            connection.BeginTransaction(
            IsolationLevel.Serializable, "GenerateId");
         SqlCommand selectCommand = new SqlCommand(
            "select nextId from PKSequence where tableName = @tableName", 
                connection, transaction);
         selectCommand.Parameters.Add("@tableName", 
            SqlDbType.VarChar).Value=tableName;
         long nextId = (long)selectCommand.ExecuteScalar();
         SqlCommand updateCommand = new SqlCommand(
         "update PKSequence set nextId = @nextId where tableName=@tableName",
            connection, transaction);
         updateCommand.Parameters.Add("@tableName", 
            SqlDbType.VarChar).Value=tableName;
         updateCommand.Parameters.Add("@nextId", 
            SqlDbType.BigInt).Value=nextId+1;
         updateCommand.ExecuteNonQuery();
         transaction.Commit();
         return nextId;
      }

4.ArtistFixture.cs 数据访问层代理类的测试用例

using System;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using DataAccessLayer;
using NUnit.Framework;
[TestFixture]
public class ArtistFixture
{
   [Test]
   public void RetrieveArtistFromDatabase()
   {
      string artistName = "Artist";
      SqlConnection connection = 
         new SqlConnection(
            ConfigurationSettings.AppSettings.Get(
"Catalog.Connection"));
      connection.Open();
      ArtistGateway gateway = new ArtistGateway(connection);
      long artistId = 
         gateway.Insert(new RecordingDataSet(),artistName);
      RecordingDataSet loadedFromDB = new RecordingDataSet();
      RecordingDataSet.Artist loadedArtist = 
         gateway.FindById(artistId, loadedFromDB);
      Assert.AreEqual(artistId,loadedArtist.Id);
      Assert.AreEqual(artistName, loadedArtist.Name);  
      gateway.Delete(loadedFromDB, artistId); //删除功能的驱动
      connection.Close();
   }
}

5.删除驱动所形成的实现代码:

  public void Delete(RecordingDataSet recordingDataSet, long artistId)

   {

      RecordingDataSet.Artist loadedArtist =

         FindById(artistId, recordingDataSet);

 

      loadedArtist.Delete();

              

      adapter.Update(recordingDataSet,

      recordingDataSet.Artists.TableName);

   }

6.有了完整的测试用例,以及实现的代码,可以进行重构:

//首先优化测试的代码:

[TestFixture]
public class ArtistFixture
{
   private static readonly string artistName = "Artist";
   private SqlConnection connection;
   private ArtistGateway gateway; 
   private RecordingDataSet recordingDataSet;
   private long artistId; 
   [SetUp]
   public void SetUp()
   {
      connection = new SqlConnection(
         ConfigurationSettings.AppSettings.Get(
         "Catalog.Connection"));
      connection.Open();
      recordingDataSet = new RecordingDataSet();
      gateway = new ArtistGateway(connection);
      artistId = gateway.Insert(recordingDataSet,artistName);
   }
   [TearDown]
   public void TearDown()
   {
      gateway.Delete(recordingDataSet, artistId);
      connection.Close();
   }
   [Test]
   public void RetrieveArtistFromDatabase()
   {
      RecordingDataSet loadedFromDB = new RecordingDataSet();
      RecordingDataSet.Artist loadedArtist = 
         gateway.FindById(artistId, loadedFromDB);
      Assert.AreEqual(artistId,loadedArtist.Id);
      Assert.AreEqual(artistName, loadedArtist.Name);   
   }
   [Test]
   public void DeleteArtistFromDatabase()
   {
      RecordingDataSet emptyDataSet = new RecordingDataSet();
      long deletedArtistId = gateway.Insert(emptyDataSet,"Deleted Artist");
      gateway.Delete(emptyDataSet,deletedArtistId);
      RecordingDataSet.Artist deleletedArtist = 
         gateway.FindById(deletedArtistId, emptyDataSet);
      Assert.IsNull(deleletedArtist);
   }
}

7.在重构的基础上加入更新方法:.

[Test]
public void UpdateArtistInDatabase()
{
   RecordingDataSet.Artist artist = recordingDataSet.Artists[0];
   artist.Name = "Modified Name";
   gateway.Update(recordingDataSet);
   RecordingDataSet updatedDataSet = new RecordingDataSet();
   RecordingDataSet.Artist updatedArtist = 
   gateway.FindById(artistId, updatedDataSet);
   Assert.AreEqual("Modified Name", updatedArtist.Name);
}

//The following is the implementation of Update:

   public void Update(RecordingDataSet recordingDataSet)
   {
      adapter.Update(recordingDataSet,
         recordingDataSet.Artists.TableName);
   }

8.Genre Gateway 另一个测试驱动的例子(类似于artists)

[TestFixture]
public class GenreFixture
{
   private static readonly string genreName = "Rock";
   private SqlConnection connection;
   private GenreGateway gateway; 
   private RecordingDataSet recordingDataSet;
   private long genreId; 
   [SetUp]
   public void SetUp()
   {
      connection = new SqlConnection(
         ConfigurationSettings.AppSettings.Get(
         "Catalog.Connection"));
      connection.Open();
      recordingDataSet = new RecordingDataSet();
      gateway = new GenreGateway(connection);
      genreId = gateway.Insert(recordingDataSet, genreName);
   }
   [TearDown]
   public void TearDown()
   {
      gateway.Delete(recordingDataSet, genreId);
      connection.Close();
   }
   [Test]
   public void RetrieveGenreFromDatabase()
   {
      RecordingDataSet loadedFromDB = new RecordingDataSet();
      RecordingDataSet.Genre loadedGenre = 
         gateway.FindById(genreId, loadedFromDB);
      Assert.AreEqual(genreId, loadedGenre.Id);
      Assert.AreEqual(genreName, loadedGenre.Name);   
   }
}

9.实现部分Genre Gateway:  

public class GenreGateway
   {
      private SqlDataAdapter adapter; 
      private SqlConnection connection;
      private SqlCommand command;
      private SqlCommandBuilder builder;
      public GenreGateway(SqlConnection connection)
      {
         this.connection = connection;
         command = new SqlCommand(
            "select id, name from Genre where id = @id",
            connection);
         command.Parameters.Add("@id",SqlDbType.BigInt);
         adapter = new SqlDataAdapter(command);
         builder = new SqlCommandBuilder(adapter);
      }
      public long Insert(RecordingDataSet recordingDataSet, 
string genreName)
      {
         long genreId = GetNextId(recordingDataSet.Genres.TableName);
         
         RecordingDataSet.Genre genreRow = 
            recordingDataSet.Genres.NewGenre();
         genreRow.Id = genreId;
         genreRow.Name = genreName;
         recordingDataSet.Genres.AddGenre(genreRow);
         adapter.Update(recordingDataSet, 
recordingDataSet.Genres.TableName);
         return genreId;
      }
      public RecordingDataSet.Genre 
         FindById(long genreId, RecordingDataSet recordingDataSet)
      {
         command.Parameters["@id"].Value = genreId;
         adapter.Fill(recordingDataSet, 
recordingDataSet.Genres.TableName);
         DataRow[] rows = recordingDataSet.Genres.Select(
            String.Format("id={0}",genreId));
         if(rows.Length < 1) return null;
         return (RecordingDataSet.Genre)rows[0];
      }
      public void Delete(RecordingDataSet recordingDataSet, 
long genreId)
      {
         RecordingDataSet.Genre loadedGenre = 
            FindById(genreId, recordingDataSet);
         loadedGenre.Delete();
         adapter.Update(recordingDataSet, 
recordingDataSet.Genres.TableName);
      }
      public long GetNextId(string tableName)
      { /* same as in ArtistGateway */ }
   }

10.获取下一个ID

[TestFixture]
public class IdGeneratorFixture
{
   private SqlConnection connection;
   [SetUp]
   public void OpenConnection()
   {
      connection = new SqlConnection(
         ConfigurationSettings.AppSettings.Get(
         "Catalog.Connection"));
      connection.Open();
   }
   [Test]
   public void GetNextIdIncrement()
   {
      SqlCommand sqlCommand = new SqlCommand(
         "select nextId from PKSequence where tableName=@tableName",
         connection);
      sqlCommand.Parameters.Add(
      "@tableName",SqlDbType.VarChar).Value="Artist";
      long nextId = (long)sqlCommand.ExecuteScalar();
      long nextIdFromGenerator = 
      IdGenerator.GetNextId("Artist", connection);
      Assert.AreEqual(nextId, nextIdFromGenerator);
      nextId = (long)sqlCommand.ExecuteScalar();
      Assert.AreEqual(nextId, nextIdFromGenerator + 1);
   }
   [TearDown]
   public void CloseConnection()
   {
      connection.Close();
   }
}

//实现部分

public class IdGenerator
{
   public static long GetNextId(string tableName, 
SqlConnection connection)
   {
      SqlTransaction transaction = connection.BeginTransaction(
IsolationLevel.Serializable, "GenerateId");
      SqlCommand selectCommand = new SqlCommand(
         "select nextId from PKSequence where tableName = @tableName",
         connection, transaction);
      selectCommand.Parameters.Add("@tableName",
          SqlDbType.VarChar).Value=tableName;
      long nextId = (long)selectCommand.ExecuteScalar();
      SqlCommand updateCommand = new SqlCommand(
         "update PKSequence set nextId = @nextId where tableName=@tableName",
         connection, transaction);
      updateCommand.Parameters.Add("@tableName", SqlDbType.VarChar).Value=tableName;
      updateCommand.Parameters.Add("@nextId", SqlDbType.BigInt).Value=nextId+1;
      updateCommand.ExecuteNonQuery();
      transaction.Commit();
      return nextId;
   }
}

11.IdGeneratorFixture.cs优化过得实现:

using System;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using NUnit.Framework;
using DataAccessLayer;
[TestFixture]
public class IdGeneratorFixture : ConnectionFixture
{
   [Test]
   public void GetNextIdIncrement()
   {
      SqlCommand sqlCommand = 
         new SqlCommand(
            "select nextId from PKSequence where tableName=@tableName",
            Connection);
      sqlCommand.Parameters.Add("@tableName",SqlDbType.VarChar).Value="Artist";
      long nextId = (long)sqlCommand.ExecuteScalar();
      long nextIdFromGenerator = IdGenerator.GetNextId("Artist", Connection);
      Assert.AreEqual(nextId, nextIdFromGenerator);
      nextId = (long)sqlCommand.ExecuteScalar();
      Assert.AreEqual(nextId, nextIdFromGenerator + 1);
   }
}

火龙果软件/UML软件工程组织致力于提高您的软件工程实践能力,我们不断地吸取业界的宝贵经验,向您提供经过数百家企业验证的有效的工程技术实践经验,同时关注最新的理论进展,帮助您“领跑您所在行业的软件世界”。
资源网站: UML软件工程组织