2009-06-04 11 views
2

타사 공급 업체의 SQLite 데이터 테이블에서 모든 행을 가져 와서 해당 레코드에서 비즈니스 개체를 만들고 새 비즈니스 개체를 다른 클래스로 보냅니다.테스트 가능한 "데이터베이스에서 데이터 가져 오기"클래스 작성하기

의사 코드 : 나는 모든 작업이있어 ​​

var databasePath = "%user profile%\application data\some3rdPartyVendor\vendor.sqlite" 
var connection = OpenSqliteConnection(databasePath); 
var allGizmoRecords = connection.Query(...); 
var businessObjects = TransformIntoBizObjs(allGizmoRecords); 
someOtherClass.HandleNewBizObjs(businessObjects); 

.

내 질문 : 단위 테스트 할 수 있도록이 클래스를 작성하려면 어떻게해야합니까?

내가해야 :

  • 실제로 유닛 테스트

또는 더 좋은 아이디어 더미 SQLite 데이터베이스를 제공하는 데이터 액세스

  • 을 조롱하는 저장소 패턴을 사용? 저는 C#을 사용하고 있습니다. 그러나이 질문은 오히려 언어에 구애받지 않는 것처럼 보입니다.

  • 답변

    2

    은 테스트 전용 Sqlite 데이터베이스를 아주 쉽게 삽입 할 수 있으며 아래 코드와 같이 리팩터링합니다. 그러나 당신은 어떻게 결과를 주장합니까? 비즈니스 개체는 someOtherClass에게 전달됩니다. ISomeOtherClass을 삽입하는 경우 해당 클래스의 작업을 볼 수 있어야합니다.그것은 약간의 고통처럼 보입니다. 당신은 단지 테스트를위한 IRepository 구현, 또는 가짜를 조롱 할 수 있도록이 클래스에서 코드의 일부를 제거하는 것 인 IRepository를 사용

    public class KillerApp 
    { 
        private String databasePath; 
        private ISomeOtherClass someOtherClass; 
    
        public KillerApp(String databasePath, ISomeOtherClass someOtherClass) 
        { 
         this.databasePath = databasePath; 
         this.someOtherClass = someOtherClass; 
        } 
    
        public void DoThatThing() 
        { 
         var connection = OpenSqliteConnection(databasePath); 
         var allGizmoRecords = connection.Query(...); 
         var businessObjects = TransformIntoBizObjs(allGizmoRecords); 
         someOtherClass.HandleNewBizObjs(businessObjects); 
        } 
    } 
    
    [TestClass] 
    public class When_Doing_That_Thing 
    { 
        private const String DatabasePath = /* test path */; 
        private ISomeOtherClass someOtherClass = new SomeOtherClass(); 
        private KillerApp app; 
    
        [TestInitialize] 
        public void TestInitialize() 
        { 
         app = new KillerApp(DatabasePath, someOtherClass); 
        } 
    
        [TestMethod] 
        public void Should_convert_all_gizmo_records_to_busn_objects() 
        { 
         app.DoThatThing(); 
         Assert.AreEqual(someOtherClass.Results, /* however you're confirming */); 
        } 
    } 
    

    .

    public class KillerApp 
    { 
        private IRepository<BusinessObject> repository; 
        private ISomeOtherClass someOtherClass; 
    
        public KillerApp(IRepository<BusinessObject> repository, ISomeOtherClass someOtherClass) 
        { 
         this.repository = repository; 
         this.someOtherClass = someOtherClass; 
        } 
    
        public void DoThatThing() 
        { 
         BusinessObject[] entities = repository.FindAll(); 
         someOtherClass.HandleNewBizObjs(entities); 
        } 
    } 
    
    [TestClass] 
    public class When_Doing_That_Thing 
    { 
        private const String DatabasePath = /* test path */; 
        private IRepository<BusinessObject> repository; 
        private ISomeOtherClass someOtherClass = new SomeOtherClass(); 
        private KillerApp app; 
    
        [TestInitialize] 
        public void TestInitialize() 
        { 
         repository = new BusinessObjectRepository(DatabasePath); 
         app = new KillerApp(repository, someOtherClass); 
        } 
    
        [TestMethod] 
        public void Should_convert_all_gizmo_records_to_busn_objects() 
        { 
         app.DoThatThing(); 
         Assert.AreEqual(someOtherClass.Results, /* however you're confirming */); 
        } 
    } 
    

    그러나 이것은 여전히 ​​꽤 부담 스럽습니다. 두 가지 이유가 있습니다. 1) Repository 패턴은 getting somebad press입니다. 최근에는 Repository에 대해 알고있는 사람이 Ayende입니다. 그리고 2) 뭐하는거야 자신의 데이터 액세스를 쓰고!? 및 ActiveRecord을 사용하십시오!

    [ActiveRecord] /* You define your database schema on the object using attributes */ 
    public BusinessObject 
    { 
        [PrimaryKey] 
        public Int32 Id { get; set; } 
    
        [Property] 
        public String Data { get; set; } 
    
        /* more properties */ 
    } 
    
    public class KillerApp 
    { 
        private ISomeOtherClass someOtherClass; 
    
        public KillerApp(ISomeOtherClass someOtherClass) 
        { 
         this.someOtherClass = someOtherClass; 
        } 
    
        public void DoThatThing() 
        { 
         BusinessObject[] entities = BusinessObject.FindAll() /* built-in ActiveRecord call! */ 
         someOtherClass.HandleNewBizObjs(entities); 
        } 
    } 
    
    [TestClass] 
    public class When_Doing_That_Thing : ActiveRecordTest /* setup active record for testing */ 
    { 
        private ISomeOtherClass someOtherClass = new SomeOtherClass(); 
        private KillerApp app; 
    
        [TestInitialize] 
        public void TestInitialize() 
        { 
         app = new KillerApp(someOtherClass); 
        } 
    
        [TestMethod] 
        public void Should_convert_all_gizmo_records_to_busn_objects() 
        { 
         app.DoThatThing(); 
         Assert.AreEqual(someOtherClass.Results, /* however you're confirming */); 
        } 
    } 
    

    결과는 훨씬 더 작은 클래스와 더 쉽게 변경할 수있는 비즈니스 개체 및 데이터 계층입니다. 데이터베이스 호출을 조롱 할 필요조차 없으며 테스트 데이터베이스 (메모리 내, 심지어는)를 사용하도록 ActiveRecord를 구성하고 초기화 할 수 있습니다.

    +0

    Ayende는 IRepository를 사용하여 신중한 것을 알고 있습니다. 나는 여기에 질문을하기 전에 그 글을 참고했다. 나는 NHibernate 나 LINQ-to-Sqlite를 사용할 수 있다고 생각했지만 잔인 함을 느꼈습니다. 이것은 타사 데이터베이스와 정확히 한 번 대화하는 일회용 클래스입니다 (예 : 파일 -> 가져 오기 ...). 이런 간단한 일을하기 위해 ORM을 설정하는 것은 과도한 행동으로 보입니다. –

    +0

    어쨌든,이 답변을 주셔서 감사합니다. 나는 그것을 투표했다. 그것은 내 문제를 해결하는데 도움이되고 AR이나 NHibernate 또는 다른 ORM과 함께하지는 않을지라도 몇 가지 아이디어를 제공한다. –

    +0

    어떻게하면 모든 일을 할 수 있습니다. –

    1

    글쎄, 실제로 여기에서 테스트해야 할 유일한 것은 TransformIntoBizObjs입니다. 연결 코드가 다른 곳에서 쓰고 테스트 되었어야했기 때문입니다. Transform에 표시 될 수있는 것들을 전달하고 옳은 일이 튀어 나오는지 확인하는 것만으로도 당신이해야 할 일이 될 것입니다.

    Transform의 모든 유즈 케이스를 테스트하십시오. 아마도 함수 호출로 끝나지 않아야 할 가능성이있는 이상한 항목 일지라도 테스트 할 수 있습니다. 사람들이 데이터베이스에 어떤 내용을 집어 넣었는지 절대 알지 못합니다.

    +0

    오른쪽, TransformIntoBizObjs는 테스트해야합니다. 코드가 SQLite db 연결을 열어야한다는 것을 어떻게 알 수 있습니까? –

    +0

    데이터베이스 연결이 필요합니까? 그것은 정말로해서는 안됩니다. 당신의 단계는 1) SQLite에서 레코드를 메모리로 가져 오기 2) 레코드를 새로운 엔터티로 변환 3) 엔티티와 재미있게 플레이하십시오. 이 경우에는 메모리 내 예제 레코드를 만들어 transfomation 메소드에 넣을 수 있습니다. –

    +0

    정확히, 우리에게 보여준 코드를 테스트 할 필요가 없습니다. 테스트를 위해 별도의 메소드를 작성합니다. 단순히 Transform을 호출하면됩니다. 방법/방법을 만드는 방법은 어떤 종류의 테스트 철학을 따르는가에 달려 있습니다. – Kurisu

    -1

    데이터베이스가 복잡하기 때문에 쿼리 코드를 테스트해야하고 실제 sqlite 인스턴스에 대해 테스트해야합니다. 그렇지 않으면 어떤 희귀 한 sqlite 버크 또는 버그를 치지 않았는지 확신 할 수 없습니다.

    그리고 쿼리를 테스트하는 유일한 방법은 실제 sqlite 파일에서 실행하는 것이므로 테스트에 이러한 파일을 포함시키는 것은 쉽습니다. 다른 레이어를 추가하여 "더"더 테스트 가능하게 만들지 않아도됩니다 또는 "순수한"단위 테스트를하는 것입니다.

    생각할 수있는 모든 이상한 케이스를 샘플 파일에 추가하십시오.

    0

    IoC (Inversion of Control) 및 DI (Dependency Injection)는 코드를 더 쉽게 테스트 할 수있는 방향으로 나아갈 것입니다. 거기에 당신을 도울 수있는 많은 프레임 워크가 있지만, 당신의 목적을 위해 반드시 모든 노력을 기울일 필요는 없습니다.

    시작과 같이 보일 수있는 인터페이스 추출과 :

    Interface ISqlLiteConnection 
    { 
        public IList<GizmoRecord> Query(...); 
    
    } 
    

    당신은 당신이 아니라 구체적인 구현보다 ISqlLiteConnection의 인스턴스를 반환하는 OpenSqlLiteConnection() 메소드를 리팩토링해야한다는 완료하면을 . 테스트하려면 인터페이스를 구현하는 클래스를 만들어야합니다.이 클래스는 실제 DB 쿼리와 연결을 정교한 결과로 조롱합니다.

    +0

    나는 그것에 대해 생각했다. 그러나 꽤 현명한 개발자들은 "System.Data. *!"을 조롱하지 말라고 말했다. http://ayende.com/Blog/archive/2006/07/02/DoNOTMockSystemData.aspx –

    관련 문제