2016-08-23 99 views
0

在我的WPF應用程序,我做了兩個單獨的項目:在UI(用XAML和的ViewModels)和「核心」(它就是人們所說的「域對象」或「業務對象」 - 在我的用例中代表離散概念的對象)。我明白這是一個很好的做法。業務對象和多個數據源

但我的許多業務對象的多個數據源進行交互。例如,Document對象可能包含數據庫以及來自文件的數據。

然後,您可以用Document「執行」的一些事情 - 我在Document上執行的方法 - 涉及其他資源。例如,Document.SubmitForProcessing()調用Web服務。

所以我寫了這一點:

public class Document 
{ 
    public string Name { get; set; } 
    public string FilePath { get; set; } 
    public string FileData { get; set; } 

    private Document() { } 

    public static Document GetByID(int documentID, string databaseConnectionString, string baseFilePath) 
    { 
     // Using Dapper 
     Document newDoc = db.Query<Document>("SELECT Name, FilePath FROM Documents WHERE ID = @pID", new { pID = documentID }); 

     newDoc.FileData = File.ReadAllText(Path.Combine(basePath, newDoc.FilePath)); 

     return newDoc; 
    } 

    public void SubmitForProcessing(IWebService webService) 
    { 
     webService.ExecuteFoo(this.Name, this.FileData); 
    } 

    public void DoBusinessStuff() 
    { 
     this.FileData = this.FileData.Replace("foo", "bar"); 
    } 
} 

不用說,這讓超級討厭的真快,它的排序很難寫反對測試。

所以我也讀到了依賴注入和存儲庫模式。但我不確定如何在這種情況下正確地做到這一點。我是否對每個數據源都有單獨的存儲庫類,然後是某種DocumentFactory或訪問單獨存儲庫並將一個Document對象縫合在一起的東西?還是有更簡單的方法?

我主要關心的是讓代碼測試友好,這樣我就可以編寫一些單元測試而不嘲笑整個數據庫和文件系統,還可以停止向每個工廠方法傳遞整個參數的大雜燴(例如GetByID(int documentID, string databaseConnectionString, string baseFilePath) - 我的現實生活中有超過6個這樣的參數)。

在類似的問題,答案說好話狀固體,YAGNI,倉庫是CRUD,等我看重這些原則,但我遇到了麻煩,從它們的實用設計。例如,Web服務不是真正的CRUD-y。我是否有一個「存儲庫」,以便在單元測試期間將其切換出來?怎麼樣的文件系統?

TL; DR - 該代碼有什麼問題?

引導讚賞。謝謝!

回答

0

靜態方法和I/O功能自然是難以測試,如果你不使用一些先進的單元測試工具(例如存根:https://msdn.microsoft.com/en-us/library/ff798446.aspx)。您面臨的問題是您有一個調用兩個I/O函數的靜態方法。目標是將它們分離。

我會做的第一件事就是重構你的GetById方法的工廠類。您可以通過將數據庫和文件系統I/O實現作爲接口來創建工廠類。 使用接口的優點是它允許你模擬I/O行爲。對於像下面這樣簡單的接口,我甚至可以簡單地在我的測試代碼中實現它們而不會產生任何嘲諷。通過這種方式,您可以將GetById方法的業務邏輯與Factory類隔離開來,並且您不會費心測試I/O本身,因爲它是由數據庫提供程序和win32 api完成的。這是你所需要的。

class Document 
{ 
    public string FileData { get; set; } 
    public string FileRelativePath { get; set; } 
} 

interface IDocumentRepository 
{ 
    Document Get(int id); 
} 


abstract class DocumentFactory 
{ 
    public abstract Document Create(int docId); 
} 

interface IFileStore 
{ 
    string Read(string fileName); 
} 

class ConcreteDocumentFactory : DocumentFactory 
{ 
    private IDocumentRepository _db; 
    private IFileStore _fileStore; 

    public ConcreteDocumentFactory(IDocumentRepository db, IFileStore fileStore) 
    { 
     _db = db; 
     _fileStore = fileStore; 
    } 

    public override Document Create(int docId) 
    { 

     Document newDoc = _db.Get(docId); 
     newDoc.FileData = _fileStore.Read(newDoc.FileRelativePath); 
     return newDoc; 
    } 
} 


/////// Test Code Below 

[TestFixture] 
class TestClass 
{ 

    class TestFriendlyFileStore : IFileStore 
    { 
     public string Read(string fileName) 
     { 

      if (fileName == "sample.txt") 
       return "Some File Content"; 
      throw new Exception("Not good file name."); 
     } 
    } 


    class TestFriendlyDocRepo : IDocumentRepository 
    { 
     public Document Get(int id) 
     { 
      if (id != 999) 
       return new Document() {FileRelativePath = "sample.txt"}; 
      throw new Exception("Not good id."); 

     } 
    } 

    [Test] 
    public void Test() 
    { 
     var concreteDocFactory = new ConcreteDocumentFactory(new TestFriendlyDocRepo(), new TestFriendlyFileStore()); 
     var doc = concreteDocFactory.Create(999); 
     Assert.AreEqual(doc.FileData == "Some File Content") 

    } 
} 
0

正如上回答說,是隻適合於測試你的業務類和趕上從infraestructure,數據庫或Web服務纔有望例外只在客戶端代碼調用,所以我會建議你到德興軟件, infraestructure爲了不可知的有之堅實,可測試:

public class Document : IAcceptDocumentVisitor 
{ 
    public int Id { get; private set; } 
    public string Name { get; private set; } 
    public string FilePath { get; private set; } 
    public string FileData { get; private set; } 

    public Document(int id, string name, string filePath, string fileData) 
    { 
     Id = id; 
     Name = name; 
     FilePath = filePath; 
     FileData = fileData; 
    } 

    /// <summary> 
    /// This method replace SubmitForProcessing 
    /// </summary> 
    /// <param name="visitor"></param> 
    public void Accept(IDocumentVisitor visitor) 
    { 
     if (visitor == null) throw new ArgumentNullException(nameof(visitor)); 
     visitor.Visit(Name, FileData); 
    } 

    public void ReplaceFileData(string fileData, Action onSuccess) 
    { 
     //Business valdation 
     var validate = true; 
     //Business valdation 
     if (!validate) return; 
     FileData = fileData; 
     onSuccess(); 
    } 
} 

public interface IAcceptDocumentVisitor 
{ 
    void Accept(IDocumentVisitor visitor); 
} 
public interface IDocumentVisitor 
{ 
    void Visit(string name, string fileData); 
} 

public class FakeWebServiceVisitor : IDocumentVisitor 
{ 
    public void Visit(string name, string fileData) 
    { 
     Name = name; 
     FileData = fileData; 
    } 

    public string FileData { get; set; } 

    public string Name { get; set; } 
} 

public class WebServiceVisitor : IDocumentVisitor 
{ 
    public void Visit(string name, string fileData) 
    { 
     //Call web service 
     //webService.ExecuteFoo(this.Name, this.FileData); 
    } 
} 

public interface IDocumentReader 
{ 
    Document GetById(int id); 
} 

public class DocumentDbReader : IDocumentReader 
{ 
    public Document GetById(int id) 
    { 
     //Get from database 
     //Document newDoc = db.Query<Document>("SELECT Name, FilePath FROM Documents WHERE ID = @pID", new { pID = documentID }); 
     return new Document(id, "Name", "Path", "Data"); 
    } 
} 

Usign一些OOP techniches和patters如倉庫,遊客,CQRS和固體,你也有一些好處,如可靠的代碼,你會開始做你的代碼也可測試:

[TestClass] 
public class DocumentSpecs 
{ 
    public string Name = "Name"; 
    public string FilePath = "Path"; 
    public string FileData = "Data"; 
    [TestMethod] 
    public void AcceptVisitorCorrectly() 
    { 
     //Arrange 
     var document = new DocumentDbReader().GetById(0); 
     var visitor = new FakeWebServiceVisitor(); 
     //Act 
     document.Accept(visitor); 
     //Assert 
     Assert.AreEqual(FileData, visitor.FileData); 
     Assert.AreEqual(Name, visitor.Name); 
    } 

    [TestMethod] 
    public void ReplaceFileDataCorrectly() 
    { 
     //Arrange 
     var successActionCalled = false; 
     var expectedFileData = Guid.NewGuid().ToString(); 
     var document = new DocumentDbReader().GetById(0); 
     //Act 
     var documentInitialData = document.FileData; 
     document.ReplaceFileData(expectedFileData,() => successActionCalled = true); 
     //Assert 
     Assert.IsTrue(successActionCalled); 
     Assert.AreEqual(expectedFileData, document.FileData); 
     Assert.IsFalse(documentInitialData == document.FileData); 
    }  
} 

下面是結果:

Test results

親切的問候!