2015年6月20日 星期六

測試專案使用 LocalDB - 使用 Entity Framework 的情境

測試要與外部資源做隔離,這包含了專案所使用的資料庫,那要如何測試呢?其實這邊可以使用 LocalDB 在測試專案裡取代專案使用的資料庫,不過在使用上並不是那麼單純,因為 LocalDB 並不是在每種專案類型都可以加入使用的,像「測試專案」無法直接加入使用,不過可以改用別的方式作處理,這邊就說明測試專案如何使用 LocalDB。


2016-11-08 更新

我目前已經不再使用這篇文章裡的做法,而是改用其他的方式,連結如下:

Repository 測試使用 LocalDB - Part.1
Repository 測試使用 LocalDB - Part.2
Repository 測試使用 LocalDB - Part.3
Repository 測試使用 LocalDB - Part.4

 

這邊的做法是,我打算把 LocalDB 放在 Solution 當中,但是一開始有說到在測試專案裡是沒有辦法使用 LocalDB,你可以加入項目,但就是無法以我們所熟悉的方式去使用。

在測試專案裡依然可以加入 LocalDB 服務架構資料庫的項目

image

但是加了之後,當你想要點兩下開啟並且使用 Visual Studio 做管理時,就會看到這樣的內容

image

其實可以使用別種方式來處理,既然在測試專案裡不能正常使用 LocalDB 的話,那就轉換一下,另外建立一個類別庫專案,然後在這一個專案裡加入 LocalDB,最後讓測試專案去加入類別庫專案的參考使用,如此一來就可以使用到 LocalDB,話是這樣簡單的說,不過做起來還是有些地方要特別留意。

 

NorthwindLibrary

建立了一個簡單的類別庫專案,這裡面使用了 Entity Framework,範例資料庫是使用 Northwind,目前只有一個對 Product 進行資料讀取的類別 ProductDataAccess,

image

 

NorthwindLibraryTests

接著建立測試專案,主要是對 ProductAccess 進行測試,

image

 

NorthwindLibraryTests.Resources

因為一開始有講過,無法在測試專案裡加入 LocalDB 後使用,所以這邊就另外建立了一個類別庫專案,而這一個類別庫專案就只有用來放測試用的 LocalDB 而已,因為只有放 LocalDB 以及是給測試專案使用的,所以專案名稱就用 NorthwindibraryTests.Resources,

SNAGHTML7ac2682

然後在 NorthwindLibraryTests.Resources 這個類別庫專案裡加入 LocalDB,而且這個 LocalDB 是可以開啟並且做管理,所以就建立同樣的 Northwind 內容,
image

image

 

測試專案加入參考與修改

再來就是 NorthwindLibraryTests 測試專案加入 NorthwindLibraryTests.Resources 的參考,

image

在 NorthwindLibraryTests 測試專案會使用 Nuget 加入使用以下幾個 Packages:

  • NSubstitute
  • FluentAssertions
  • ExpectedObjects
  • EntityFramework

image

在 NorthwindLibraryTests 測試專案加入 App.Config(應用程式組態檔),並且補上 ConnectionString,

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <configSections>
        <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
        <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
    </configSections>
    <connectionStrings>
        <add name="NorthwindEntities" connectionString="metadata=res://*/Northwind.csdl|res://*/Northwind.ssdl|res://*/Northwind.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=(LocalDB)\v11.0;attachdbfilename=|DataDirectory|\NorthwindTest.mdf;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework&quot;" providerName="System.Data.EntityClient" />
    </connectionStrings>
    <entityFramework>
        <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
        <providers>
            <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
        </providers>
    </entityFramework>
</configuration>

以下是 ProductDataAccessTests.cs 的內容,先對 GetAll 方法進行測試,

using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NorthwindLibrary;
using NSubstitute;
using System;
using System.Configuration;
using System.Data.Entity;
 
namespace NorthwindLibraryTests
{
    [TestClass()]
    public class ProductDataAccessTests
    {
        private string connectionString;
 
        private IDbContextFactory DbContextFactory { get; set; }
 
        private DbContext dbContext { get; set; }
 
        public ProductDataAccessTests()
        {
            this.connectionString =
                ConfigurationManager.ConnectionStrings["NorthwindTest"].ToString();
 
            this.dbContext = this.GetDbContext(this.connectionString);
 
            this.DbContextFactory = Substitute.For<IDbContextFactory>();
            this.DbContextFactory.GetDbContext().Returns(this.dbContext);
        }
 
        public DbContext GetDbContext(string connectionString)
        {
            Type t = typeof(DbContext);
            var dbContext = (DbContext)Activator.CreateInstance(t, connectionString);
            return dbContext;
        }
 
        [TestMethod()]
        public void GetAllTest()
        {
            // arrange
            var sut = new ProductDataAccess(this.DbContextFactory);
 
            // act
            var actual = sut.GetAll();
 
            // assert
            actual.Should().NotBeNull();
        }
 
        [TestMethod()]
        [Ignore()]
        public void GetByCategoryIDTest()
        {
            Assert.Fail();
        }
 
        [TestMethod()]
        [Ignore()]
        public void GetOneTest()
        {
            Assert.Fail();
        }
    }
}

上面的測試絕對是無法通過的,因為我們在 Connection String 裡所指定的資料並不存在,我們所要使用的是在 NorthwindLibraryTests.Resources 專案裡的 NorthwindTest.mdf 這個 LocalDB,之前我們已經是加入了 NorthwindLibraryTests.Resources 參考,所以希望能夠在 NorthwindLibraryTests 專案裡的 DataDirectory 去讀取測試用的 LocalDB。

先確認 NorthwindLibraryTests.Resources 裡的 NorthwindTest.mdf 屬性,是否將「建置動作」設定為「內容」,而「複製到輸出目錄」要設定為「永遠複製」

image

 

有些人到了上面的步驟完成之後再去重新執行測試就可以讓測試通過,但是有些人的環境仍然會出現問題,例如會看到以下的錯誤訊息內容:

image

如果遇到了這樣的情況,就必須要在測試專案的屬性裡,在建置事件的「建置前事件命令列」加入三個對於 LocalDB 的指令,

sqllocaldb.exe stop v11.0
sqllocaldb.exe delete v11.0
sqllocaldb.exe start v11.0

image

MSDN - 如何:指定建置事件 (C#)

MSDN - SqlLocalDB 公用程式

 

重新執行單元測試,並且得到測試通過的執行結果

image

image

補完其他的測試

image

 

同樣的專案,在其他的開發環境下不需要做什麼樣的處理與設定,只要取得開發的程式原始碼,在重新建置過後,執行所有的測試都是可以通過的。

SNAGHTMLaec77df

 


WorksOnMyMachine

這篇文章的使用情境與調整是當專案使用 Entity Framework 的情況,但如果專案的資料存取並不是使用 EntityFramework 而是使用一般的 ADO.NET 或是 Dapper,那麼在一些設定的地方會有點不同,這些日後再寫文章做說明。

 

參考連結

Using SQL Server 2012 LocalDb in VS11 and VS2010 for testing - Roel van Lisdonk

MSDN - 如何:指定建置事件 (C#)

MSDN - SqlLocalDB 公用程式

 

以上

4 則留言:

  1. 推! 期待一般的 ADO.NET 或是 Dapper 的測試方式。
    請問如果是 Oracle 的資料庫,也有這種 LocalDB 能做測試嗎?

    回覆刪除
    回覆
    1. Oralce 的話,我就不清楚啦

      不過如果是使用 MS SQL Server 的情境,我已經不推薦這篇文章的做法囉
      而是會推薦使用「Repository 測試使用 LocalDB」這系列文章的方式

      刪除
    2. Dockerfile of Oracle Database Express Edition 11g Release 2
      https://github.com/wnameless/docker-oracle-xe-11g

      刪除

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。