2016-08-19 3 views
2

와 결합 테스트 가능한 코드를 설계합니다. 그러나 클래스 자체 내에는 TextFieldParser가 검사 할 줄을 반환하는 것에 의존하는 논리가 있습니다.어떻게 같이 상상 클래스를 비 검증 기능

인터페이스가없는 Microsoft의 외부 클래스이기 때문에 논리를 테스트하기 위해 TextFieldParser를 모의 객체로 "인터페이스 아웃"할 수 없습니다.

내가 밀어 수있는이 같은 별도의 기능에 문 경우 :

public bool HasSingleQuote(string lineToCheck) 
{ 
    return lineToCheck.Contains("'"); 
} 

그러나 이러한 클래스 외부에서 접근 할 필요가 없습니다. 그들은 다른 곳에서 실제로 부름을받을 필요가 없으므로 도우미 클래스에 속하지 않습니다. 따라서 좋은 설계 원칙에 따라 공개되지 않은 비공개가 될 수 있으므로 공공 접근을 통해 테스트해야합니다. 이 경우 테스트 할 수없는 TextFieldParser에 의존합니다.

내 자신의 클래스에 TextFieldParser를 래핑하고 스틱 및 인터페이스 할 수 있지만 과도한 코드 및 불필요한 코드 복제가 발생합니다.

정말 감사할만한 간단한 예제이지만이 샘플을 함께 사용하여 문제를 설명했습니다. 논리를 테스트 할 수 있도록이 코드를 리팩터링하는 최선의 방법은 무엇입니까?

+0

나는 당신이 소유하고있는 것을 테스트한다고 말할 것입니다. 'TextFieldParser'는 구현 세부 사항입니다. MS는 릴리스 용 기능을 광범위하게 테스트했을 것입니다. 관심사가 조건부 검사를 수행하는 구현 내부의 논리에 관한 것이라면 IFileParser 구현이 너무 많은 작업을 수행하고 있다고 주장 할 수 있습니다. SRP를 생각 나게하고 변경해야 할 한 가지 이유 만 있습니다. – Nkosi

+1

부수적으로, 레거시 코드로 효과적으로 작업하는 것은 환상적인 책입니다. http://www.informit.com/store/working-effectively-with-legacy-code-9780131177055 14 장과 15 장은 확실히 여기에 적용될 것입니다. –

답변

2

나는 당신이 소유하고있는 것을 테스트한다고 말할 것입니다. TextFieldParser은 구현 세부 사항입니다. MS는 릴리스 용 기능을 광범위하게 테스트했을 것입니다. 관심사가 조건부 검사를 수행하는 구현 내부의 논리에 관한 것이라면 IFileParser 구현이 너무 많은 작업을 수행 할 수 있다고 주장 할 수 있습니다. SRP를 생각 나게하고 변경해야 할 한 가지 이유 만 있습니다. FileParser 구현이 다음에 리팩토링 될

public class DefaultDelimiterLogic : IDelimiterLogic { 
    public string Invoke(string line) { 
     if (line.Contains("'")) { 
      return "'"; 
     } 

     if (line.Contains("\"")) { 
      return "\""; 
     } 

     return ""; 
    } 
} 

같은 구현

public interface IDelimiterLogic { 
    string Invoke(string line); 
} 

... 그래서 지금 당신은 당신의 구분 로직 테스트중인 시스템을 테스트하려는 경우

public class FileParser : IFileParser { 
    IDelimiterLogic delimiterLogic; 
    public FileParser(IDelimiterLogic delimiterLogic) { 
     this.delimiterLogic = delimiterLogic; 
    } 

    public string ParseFirstRowForDelimiters(string path) { 
     using (TextFieldParser parser = new TextFieldParser(path)) { 
      string line = parser.ReadLine(); 
      return delimiterLogic.Invoke(line); 
     } 
    } 
} 

구현은 IDelimiterLogic입니다.

UPDATE : 제 3 자 종속성을 추상화를위한뿐만 아니라 @JAllen에

신용.

public interface ITextFieldParser : IDisposable { 
    bool EndOfData { get; } 
    string ReadLine();  
} 

public interface ITextFieldParserFactory { 
    ITextFieldParser Create(string path); 
} 

public class TextFieldParserFactory : ITextFieldParserFactory { 
    public ITextFieldParser Create(string path) { 
     return new TextFieldParserWrapper(path); 
    } 
} 

public class TextFieldParserWrapper : ITextFieldParser { 
    TextFieldParser parser; 
    internal TextFieldParserWrapper(string path) { 
     parser = new TextFieldParser(path); 
    } 
    public bool EndOfData { get{ return parser.EndOfData; } } 
    public string ReadLine() { return parser.ReadLine(); } 
    public void Dispose() { parser.Dispose(); } 
} 

새로운 테스팅 문제가 TextFieldParser가 올바른 제 3 자 의존성이라는 사실에 기초

public class FileParser : IFileParser { 
    IDelimiterLogic delimiterLogic; 
    ITextFieldParserFactory parserFactory; 

    public FileParser(IDelimiterLogic delimiterLogic, ITextFieldParserFactory parserFactory) { 
     this.delimiterLogic = delimiterLogic; 
     this.parserFactory = parserFactory; 
    } 

    public string ParseFirstRowForDelimiters(string path) { 
     using (ITextFieldParser parser = parserFactory.Create(path)) { 
      string line = parser.ReadLine(); 
      return delimiterLogic.Invoke(line); 
     } 
    } 
} 
+0

감사합니다. 이것은 다른 클래스로 푸시하는 제안 된 솔루션에 매우 가깝습니다 (구현이 더 좋음에도 불구하고). 그러나 이것이 작은 수업의 확산을 초래할 가능성은없는가? –

+0

하나의 일을 잘 수행하는 많은 작은 클래스를 유지하는 것이 유지 관리 및 테스트가 어려운 곳에서 모든 것을 결합하는 것보다 낫습니다. 그것은 일들을 단단하게 유지합니다. – Nkosi

2

구현 IFileParser 리팩토링? 사용할 수있는 전략 중 하나는 해당 타사 종속성을 서비스 인터페이스에 래핑하여 FileParser에 전달하는 것입니다.

public interface ITextFieldParserService 
{ 
    string ReadLine(); 
} 

public class DefaultTextFieldParserService : ITextFieldParserService 
{ 
    private TextFieldParser parser; 
    public ITextFieldParserService Setup(string path) 
    { 
     parser = new TextFieldParser(path); 
    } 
    //you'd want some teardown method to dispose of TextFieldParser, or make 
    //the service IDisposable probably 
} 

public class FileParser : IFileParser 
{ 
    public FileParser(ITextFieldParserService textParserService) 
    { 
    } 
    ... 
    public string ParseFirstRowForDelimiters(string path) 
    { 
     var parser = textParserService.Setup(path)   
     string line = parser.ReadLine(); 

     if(lineContains("'")) 
     { 
      return "'"; 
     } 

     if(lineContains("\"") 
     { 
      return "\""; 
     } 

     return "";   
    } 

당신은 실제로 제 3 자 TextFieldParser를 사용하는 서비스의 기본 구현을 가질 수 있습니다,하지만 당신은 단지 미리 정의 된 데이터 세트를 반환하는 테스트 구현을 작성할 수 있습니다.