2016-08-23 7 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オブジェクトをつなぎ合わせるものはありますか?それとも簡単な方法がありますか?

私の主な関心事は、テスト容易なコードを作成して、データベースとファイルシステム全体を模擬しないで単体テストを書くことができるだけでなく、私が持っているすべてのファクトリメソッドにパラメータのsmorgasbordを渡すのを止めることです(例えば、GetByID(int documentID, string databaseConnectionString, string baseFilePath) - 私の実際の人生は、このような半ダース以上のパラメータを持っています)。

SOLID、YAGNI、リポジトリはCRUDなどの回答がありますが、私はこれらの原則を大切にしていますが、実用的なデザインを得られません。たとえば、Webサービスは実際にはCRUD-yではありません。私は単体テスト中に切り替えることができるように "リポジトリ"を持っていますか?ファイルシステムはどうですか?

TL; DR - このコードで何が問題になっていますか?

ガイダンスが高く評価されました。ありがとうございました!

答えて

0

いくつかの高度なユニットテストツール(スタブ:https://msdn.microsoft.com/en-us/library/ff798446.aspxなど)を使用しないと、静的メソッドと入出力関数はテストするのが難しいです。あなたが直面している問題は、2つのI/O関数を呼び出す静的メソッドがあることです。目標はそれらを切り離すことです。

最初に行うことは、GetByIdメソッドをFactoryクラスにリファクタリングすることです。ファクトリクラスを作成するには、データベースとファイルシステムのI/O実装をインターフェイスとして渡します。 インターフェイスを使用する利点は、I/O動作をモックすることができることです。私が下で行ったような単純なインターフェースについては、テストコードで単純に実装することさえできます。このようにして、GetByIdメソッドのビジネスロジックをFactoryクラスに分離することができ、データベースプロバイダとwin32 APIによって行われるように、I/O自体のテストは煩わされることはありません。これがあなたの必要です。前回の回答で述べたように

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サービスからのみ予想される例外をキャッチするのみに適しているので、私はあなたがあるソフトウェアを最新デザインし提案しますそれはSOLIDおよびテスト可能有するためにとらわれない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なリポジトリ、訪問者、CQRS、および固体など、いくつかのOOPのtechnichesとpatters、あなたは、SOLIDコードなどの利点を持つことができますし、あなたを作るために開始しますテスト可能なコード:

ここでは
[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

種類よろしく!

関連する問題