2009-08-05 43 views
12

我在學習如何做單元測試和嘲弄。我理解TDD和基本測試的一些原則。但是,我正在考慮重構下面沒有測試編寫的代碼,並試圖瞭解它需要如何更改以使其可測試。我怎樣才能重構這個工廠類型的方法和數據庫調用是可測試的?

public class AgentRepository 
{ 

public Agent Select(int agentId) 
{ 
    Agent tmp = null; 
    using (IDataReader agentInformation = GetAgentFromDatabase(agentId)) 
    { 
     if (agentInformation.Read()) 
     { 
      tmp = new Agent(); 
      tmp.AgentId = int.Parse(agentInformation["AgentId"].ToString()); 
      tmp.FirstName = agentInformation["FirstName"].ToString(); 
      tmp.LastName = agentInformation["LastName"].ToString(); 
      tmp.Address1 = agentInformation["Address1"].ToString(); 
      tmp.Address2 = agentInformation["Address2"].ToString(); 
      tmp.City = agentInformation["City"].ToString(); 
      tmp.State = agentInformation["State"].ToString(); 
      tmp.PostalCode = agentInformation["PostalCode"].ToString(); 
      tmp.PhoneNumber = agentInformation["PhoneNumber"].ToString(); 
     } 
    } 

    return tmp; 
} 

private IDataReader GetAgentFromDatabase(int agentId) 
{ 
    SqlCommand cmd = new SqlCommand("SelectAgentById"); 
    cmd.CommandType = CommandType.StoredProcedure; 

    SqlDatabase sqlDb = new SqlDatabase("MyConnectionString"); 
    sqlDb.AddInParameter(cmd, "AgentId", DbType.Int32, agentId); 
    return sqlDb.ExecuteReader(cmd); 
} 

} 

這兩個方法在一個類中。 GetAgentFromDatabase中與數據庫相關的代碼與企業庫相關。

我該如何去做這個測試?我應該將GetAgentFromDatabase方法抽象爲不同的類嗎? GetAgentFromDatabase是否應該返回IDataReader以外的內容?任何建議或指向外部鏈接將不勝感激。

回答

9

對於將GetAgentFromDatabase()移動到單獨的類中是正確的。下面是如何重新定義AgentRepository

public class AgentRepository { 
    private IAgentDataProvider m_provider; 

    public AgentRepository(IAgentDataProvider provider) { 
     m_provider = provider; 
    } 

    public Agent GetAgent(int agentId) { 
     Agent agent = null; 
     using(IDataReader agentDataReader = m_provider.GetAgent(agentId)) { 
      if(agentDataReader.Read()) { 
       agent = new Agent(); 
       // set agent properties later 
      } 
     } 
     return agent; 
    } 
} 

,我所定義的IAgentDataProvider界面如下:

public interface IAgentDataProvider { 
    IDataReader GetAgent(int agentId); 
} 

所以,AgentRepository正在測試的類。我們將模擬IAgentDataProvider並注入依賴關係。 (我是用Moq做的,但是你可以很容易地用不同的隔離框架重做它)。

[TestFixture] 
public class AgentRepositoryTest { 
    private AgentRepository m_repo; 
    private Mock<IAgentDataProvider> m_mockProvider; 

    [SetUp] 
    public void CaseSetup() { 
     m_mockProvider = new Mock<IAgentDataProvider>(); 
     m_repo = new AgentRepository(m_mockProvider.Object); 
    } 

    [TearDown] 
    public void CaseTeardown() { 
     m_mockProvider.Verify(); 
    } 

    [Test] 
    public void AgentFactory_OnEmptyDataReader_ShouldReturnNull() { 
     m_mockProvider 
      .Setup(p => p.GetAgent(It.IsAny<int>())) 
      .Returns<int>(id => GetEmptyAgentDataReader()); 
     Agent agent = m_repo.GetAgent(1); 
     Assert.IsNull(agent); 
    } 

    [Test] 
    public void AgentFactory_OnNonemptyDataReader_ShouldReturnAgent_WithFieldsPopulated() { 
     m_mockProvider 
      .Setup(p => p.GetAgent(It.IsAny<int>())) 
      .Returns<int>(id => GetSampleNonEmptyAgentDataReader()); 
     Agent agent = m_repo.GetAgent(1); 
     Assert.IsNotNull(agent); 
        // verify more agent properties later 
    } 

    private IDataReader GetEmptyAgentDataReader() { 
     return new FakeAgentDataReader() { ... }; 
    } 

    private IDataReader GetSampleNonEmptyAgentDataReader() { 
     return new FakeAgentDataReader() { ... }; 
    } 
} 

(我離開了FakeAgentDataReader類,它實現IDataReader的,是微不足道的實現 - 你只需要實現閱讀()的Dispose()讓測試工作。)

AgentRepository這裏的目的就是要把IDataReader的對象,並把它們變成正確形成代理對象。您可以擴展上述測試夾具以測試更多有趣的案例。

單元測試AgentRepository脫離實際的數據庫後,您將需要一個具體的實施IAgentDataProvider的單元測試,不過這是另外一個問題的話題。 HTH

0

我就開始把一些想法,並沿途更新:

  • SqlDatabase SQLDB =新SqlDatabase( 「MyConnectionString」); - 您應該避免新的運算符與邏輯混淆。你應該構建異或邏輯運算;避免它們同時發生。使用依賴注入來傳遞這個數據庫作爲參數,所以你可以嘲笑它。我的意思是,如果你想單元測試它(不去數據庫,這應該在稍後的某些情況下完成)
  • IDataReader agentInformation = GetAgentFromDatabase(agentId) - 也許你可以將Reader檢索分離到其他類,所以你可以在測試工廠代碼時模擬這個類。
0

國際海事組織你通常只應該擔心使你的公共財產/方法可測試。即只要選擇(int agentId)你通常不關心它是如何通過GetAgentFromDatabase(int agentId)

你有什麼似乎是合理的,因爲我想它可以通過類似下面的測試(假設你的類被稱爲AgentRepository)

AgentRepository aRepo = new AgentRepository(); 
int agentId = 1; 
Agent a = aRepo.Select(agentId); 
//Check a here 

至於建議的改進。我建議允許AgentRepository的連接字符串通過公開或內部訪問進行更改。

0

假設你想測試類的公共選擇方法[NONAME] ..

  1. 移動GetAgentFromDatabase()方法爲一個接口說IDB_Access。讓NoName具有可以設置爲ctor參數或屬性的接口成員。所以現在你有一個接縫,你可以在不修改方法中的代碼的情況下改變行爲。
  2. 我會改變上述方法的返回類型來返回更一般的東西 - 你似乎正在使用它像一個哈希表。讓IDB_Access的生產實現使用IDataReader在內部創建哈希表。它也使得它不依賴於技術;我可以使用MySql或一些非MS/.net環境實現此接口。 private Hashtable GetAgentFromDatabase(int agentId)
  3. 下一頁爲您的單元測試,你可以用存根工作(或使用一些更高級的像模擬框架)

public MockDB_Access : IDB_Access 
{ 
    public const string MY_NAME = "SomeName; 
    public Hashtable GetAgentFromDatabase(int agentId) 
    { var hash = new Hashtable(); 
    hash["FirstName"] = MY_NAME; // fill other properties as well 
    return hash; 
    } 
} 

// in the unit test 
var testSubject = new NoName(new MockDB_Access()); 
var agent = testSubject.Select(1); 
Assert.AreEqual(MockDB_Access.MY_NAME, agent.FirstName); // and so on... 
0

至於我看來GetAgentFromDatabase()方法不能被一個額外的測試testet,因爲它的代碼是完全由Select()方法的測試覆蓋。代碼沒有可以沿着的分支,所以在這裏創建一個額外的測試沒有意義。 如果從多個方法調用GetAgentFromDatabase()方法,您應該自行測試它。

1

這裏的問題是決定什麼是SUT和什麼是測試。以您的示例爲例,您正在嘗試測試Select()方法,因此希望將其與數據庫隔離。您有幾種選擇,

  1. 虛擬化的GetAgentFromDatabase(),這樣可以提供一個派生類的代碼返回正確的值,在這種情況下創建一個對象,它提供IDataReaderFunctionaity但不與DB即

    class MyDerivedExample : YourUnnamedClass 
    { 
        protected override IDataReader GetAgentFromDatabase() 
        { 
         return new MyDataReader({"AgentId", "1"}, {"FirstName", "Fred"}, 
          ...); 
        } 
    } 
    
  2. 作爲Gishu suggested而不是使用IsA關係(繼承)使用HasA(對象組合),你再次有一個類來處理創建模擬IDataReader,但這次沒有繼承。

    但是,這些都會導致大量代碼簡單地定義了我們在查詢時返回的一組結果。無可否認,我們可以將此代碼保留在測試代碼中,而不是我們的主代碼中,但它是一種努力。你真的在做的是爲特定查詢定義一個結果集,並且你知道這樣做的真正優勢......數據庫

  3. 我使用了LinqToSQL而後又發現DataContext對象有一些非常有用的方法包括DeleteDatabaseCreateDatabase

    public const string UnitTestConnection = "Data Source=.;Initial Catalog=MyAppUnitTest;Integrated Security=True"; 
    
    
    [FixtureSetUp()] 
    public void Setup() 
    { 
        OARsDataContext context = new MyAppDataContext(UnitTestConnection); 
    
        if (context.DatabaseExists()) 
        { 
        Console.WriteLine("Removing exisitng test database"); 
        context.DeleteDatabase(); 
        } 
        Console.WriteLine("Creating new test database"); 
        context.CreateDatabase(); 
    
        context.SubmitChanges(); 
    } 
    

考慮了一段時間。使用數據庫進行單元測試的問題是數據會發生變化。刪除數據庫並使用測試來演變可用於未來測試的數據。

有兩件事要注意 確保您的測試按正確的順序運行。 MbUnit的語法是[DependsOn("NameOfPreviousTest")]。 確保只有一組測試針對特定數據庫運行。

相關問題