2012-03-11 1 views
8

나는 단위 테스트를 처음 접했고 TDD 접근법을 사용하기를 원하지만 현재는 모든 기존 클래스의 단위 테스트를 작성하여 모든 경우의 기능을 검증한다고합니다.테스트중인 클래스에서 메서드가 호출되었는지 테스트하려면 어떻게해야합니까?

많은 문제없이 NUnit 및 Rhino mock을 사용하여 대다수의 코드를 테스트 할 수있었습니다. 그러나, 나는 동일한 클래스 내에서 다른 많은 메소드를 호출하는 유닛 테스트 함수에 대해 궁금해하고 있습니다.

classUnderTest.AssertWasCalled(cut => cut.SomeMethod(someArgs)) 

과 같이 할 수 없습니다. 테스트 대상 클래스가 위조품이 아니기 때문입니다. 또한 테스트 할 메소드가 테스트중인 클래스의 다른 메소드를 호출하면 같은 클래스의 메소드를 호출하므로 "최상위 레벨"메소드를 테스트하기 위해 많은 양의 값을 가짜로 만들어야합니다. 또한이 "하위 메서드"를 모두 단위 테스트하기 때문에 "SomeMethod"는 단위 테스트를 통과하고 하위 메서드의 세부 정보를 걱정할 필요가 없다면 예상대로 작동한다고 가정 할 수 있어야합니다.

public DataSet ExportExcelDocToDataSet(bool headerRowProvided) 
    { 
     DataSet ds = new DataSet(); 

     for (int i = 0; i < currentWorkbook.NumberOfSheets; i++) 
     {    
      ISheet tmpSheet = currentWorkbook.GetSheetAt(i); 

      if (tmpSheet.PhysicalNumberOfRows == 0) { continue; } 
      DataTable dt = GetDataTableFromExcelSheet(headerRowProvided, ds, tmpSheet); 

      if (dt.Rows.Count > 0) 
      { 
       AddNonEmptyTableToDataSet(ds, dt); 
      } 
     } 

     return ds; 
    } 

    public DataTable GetDataTableFromExcelSheet(bool headerRowProvided, DataSet ds, ISheet tmpSheet) 
    { 
     DataTable dt = new DataTable(); 
     for (int sheetRowIndex = 0; sheetRowIndex <= tmpSheet.LastRowNum; sheetRowIndex++) 
     { 
      DataRow dataRow = GetDataRowFromExcelRow(dt, tmpSheet, headerRowProvided, sheetRowIndex); 
      if (dataRow != null && dataRow.ItemArray.Count<object>(obj => obj != DBNull.Value) > 0) 
      { 
       dt.Rows.Add(dataRow); 
      } 
     } 

     return dt; 
    } 

... 

당신은 그것을 볼 수 있습니다 여기에

내가 내 지점을 설명 돕는와 함께 일한지 몇 가지 예제 코드입니다 (나는 NPOI를 사용하여 Excel 파일의 가져 오기/내보내기를 관리하는 클래스를 작성했습니다) ExportExcelDocToDataSet (이 경우 내 "최고 수준"방법은)이 같은 클래스 내에서 정의 된 다른 방법 중 몇 가지를 호출 GetDataRowFromExcelRow를 호출 GetDataTableFromExcelSheet를 호출합니다.

그래서이 코드를 리팩토링하여 하위 메서드에서 호출 한 값을 스텁하지 않고도 단위 테스트를 쉽게 수행 할 수있는 전략은 무엇입니까? 테스트중인 클래스에서 메서드 호출을 위조하는 방법이 있습니까?

도움이나 조언을 미리 보내 주셔서 감사합니다.

답변

6

under test (SUT)을 수정하십시오. 단위 테스트가 어렵다면 디자인이 어색 할 수도 있습니다.

테스트중인 클래스 내에서 faking 메서드를 호출하면 지정된 테스트가 끝납니다. 결과는 매우 부서지기 쉬운 테스트입니다. 클래스를 수정하거나 리팩터링하는 즉시 단위 테스트를 수정해야 할 가능성이 큽니다. 이것은 단위 테스트의 유지 보수 비용을 너무 높입니다.

지정된 테스트를 피하려면 공용 방법에 집중하십시오. 이 메서드가 클래스 내의 다른 메서드를 호출하는 경우 이러한 호출을 테스트하지 마십시오. 반면에 : 다른 dependend on component (DOCs)에 대한 메소드 호출을 테스트해야합니다.

당신이 그것에 충실하고 시험에서 중요한 것을 놓친다는 느낌이 들면, 너무 많이하고있는 수업이나 방법을 나타내는 신호 일 수 있습니다. 수업의 경우 : Single Responsibility Principle (SRP)의 위반 사항을 찾으십시오. 클래스를 추출하고 별도로 테스트하십시오. 메소드의 경우 : 메소드를 여러 공용 메소드로 분할하고 각각을 개별적으로 테스트하십시오. 그래도 여전히 너무 어색하다면 SRP를 위반하는 수업을 반드시 치러야합니다. 특정 경우

는 다음을 수행 할 수 있습니다 (어쩌면 그들 ExcelToDataSetExporterExcelSheetToDataTableExporter 전화) 두 개의 서로 다른 클래스로 방법을 ExportExcelDocToDataSetGetDataTableFromExcelSheet의 압축을 풉니 다. 두 메서드가 모두 포함 된 원본 클래스는 두 클래스를 모두 참조하고 이전에 추출한 메서드를 호출해야합니다. 이제 세 클래스를 모두 독립적으로 테스트 할 수 있습니다. 원본 클래스 수정을 위해 Extract Class refactoring (book)을 적용하십시오.

개조 테스트는 항상 작성하고 유지 보수하기가 쉽지 않습니다. 그 이유는 단위 테스트없이 작성된 SUT가 어색한 디자인을 가지기 쉽기 때문에 테스트하기가 더 어렵 기 때문입니다. 즉, 단위 테스트 문제는 SUT를 수정하여 해결해야하며 단위 테스트 포밍으로 해결할 수 없습니다.

+0

AssertWasCalled 확장을 사용하여 단위 테스트를 할 수 있도록 별도의 클래스로 언급 한 메서드를 추출 할 생각을 했었습니다. 그러나 이것이 매우 얕은 클래스 톤으로 이어지는 것을 볼 수 있었기 때문에 이것이 훌륭한 아이디어인지 확실하지 않았습니다. 최대 하나 또는 두 가지 방법. 나는 당신이 추천 한 책과 SRP에 대해 더 자세히 알아볼 것입니다. 이 모든 것에 대한 가장 좋은 점은 프로젝트의 유일한 사람이고 모든 코드가 나에 의해 작성되었으므로 작업을 수행하기 위해 필요한 모든 작업을 수행 할 수 있습니다. –

+1

@Gage Trader : 메소드의 수를 기준으로 클래스를 측정하지 마십시오. 예를 들어 커맨드 패턴은 하나의 메서드 만 노출하고 여전히 widley 사용 기법입니다. 단위 테스트와 좋은 OO 설계 원칙을 적용하기 전에 더 많은 수의 클래스에 만족하지 못했습니다. 나는 더 많은 수의 수업이 더 많은 일을 의미하지 않는다는 것을 깨닫기 위해 어느 정도 시간이 걸렸다. 예, 디자인 및 테스트에 더 많은 시간이 필요하지만 더 높은 품질과 낮은 버그 수를 통해이를 보완 할 수 있습니다. –

1

나는 당신이 (ExportExcelDocToDataSet 작품이 예상대로 사실을 넘어) GetDataTableFromExcelSheet의 동작을 검증 할 필요가 없습니다 ExportExcelDocToDataSet의 시험에 대한 있도록 별도로 공공 방법을 GetDataTableFromExcelSheet 테스트하는 것 같아요.

일반적인 방법은 공용 메서드를 테스트하는 것입니다. 공용 메서드를 지원하는 모든 개인 메서드는 공용 메서드가 예상대로 작동하는 경우 기본적으로 테스트됩니다.

이렇게하면 클래스의 동작 만 테스트 할 수 있습니다. 메서드를 단위로 사용하지 않고 테스트 할 수 있습니다. 이렇게하면 테스트가 부서지기 쉬워집니다. 즉, 클래스의 내부를 변경하면 테스트 중 일부가 중단되는 경향이 있습니다.

물론 모든 코드가 잘 테스트되기를 원하지만 너무 빡빡한 방법을 사용하면 취성이 발생할 수 있습니다. 클래스 행동 테스트 (최상위 레벨에서해야하는 일을하는지)는 또한 하위 레벨을 테스트합니다.

테스트에서 메서드를 가짜로 만들려면 위조하려는 메서드에 대한 인터페이스를 사용하도록 코드를 리팩터링 할 수 있습니다. command pattern을 참조하십시오.

명백한 변경은 ExportExcelDocToDataSet이 통합 문서를 인수로 사용하기위한 것이지만. 테스트에서 가짜 워크 북을 보낼 수 있습니다. inversion of control을 참조하십시오.

+0

IWorkbook 개체를 가져 와서 통합 문서 개체를 조롱하는 생성자가 있습니다.이 예제에서는 생성자를 보여주지 않았으므로이 부분은 처리됩니다. GetDataTableFromExcelSheet는 개인적 테스트 일 뿐이므로 나에게 적합하지 않습니다. 공개 메서드 만 테스트하는 방법에 대해 알고있는 것을 볼 수 있습니다. 상위 수준의 메서드 중 일부가 복잡하기 때문에 테스트를 위해 이전에 개인 메서드를 많이 사용하고있었습니다. 내가 가지고있는 모든 방법에 대해 테스트를 작성하는 데 많은 시간을 소비했기 때문에 조언을 많이 좋아합니다. 아마도 올바른 방법이 아닙니다. –

2

테스트 된 메소드가 실제로 호출되는 것은 중요하지 않습니다. 구현 세부 사항이며 단위 테스트는 이 아니어야합니다.을 알고 있어야합니다. 일반적으로 (잘, 단위 테스트를 할 때 대부분) 단일 단위을 테스트하고 싶습니다.

클래스의 각 공용 메소드 또는 외부에서 테스트 한 클래스의 리팩터링 기능에 대해 별도의 분리 된 테스트를 작성할 수 있습니다. 두 접근법은 똑같은 것에 초점을 맞 춥니 다 - 각 단위에 대한 격리 된 테스트를 가지고.

  • 당신의 테스트 클래스의 이름은 무엇입니까 : 이제

    , 당신에게 몇 가지 힌트를주는? 노출되는 메소드를 기초로 ExcelExporterAndToDataSetConverter ... 또는 ExcelManager 라인을 따라 무엇인가? 이 수업은 too many things at once 일 수 있습니다. 이것은 약간의 리팩토링을 요구합니다. DataSet으로 데이터를 내보내는 작업은 Excel 데이터를 DataSets/DataRows로 쉽게 변환 할 수 있습니다.

  • GetDataTableFromExcelSheet 방법이 변경되면 어떻게됩니까? 다른 클래스로 이동하거나 제 3 자 코드로 대체됩니까? 수출 시험을 중단해야합니까? 그렇게해서는 안됩니다. 이것은 수출 테스트가 호출되었는지 여부를 확인해서는 안되는 이유 중 하나입니다.

클래스를 분리하기 위해 DataSet/DataRow 변환 메서드로 이동하는 것이 좋습니다. 단위 테스트를 쉽게 작성하고 내보내기 테스트가 취약하지 않습니다.

+0

클래스 이름에 대한 좋은 추측 : "ExcelHelper"라고하며 Excel과 관련된 모든 것을 처리하기 위해 나머지 응용 프로그램에서 호출하는 단일 클래스가되도록하는 것이 좋습니다. 아마도 적어도 ExcelImporter 및 ExcelExporter 클래스를 만들어야하며 이러한 주석을 읽은 후에 코드를 더 분할 할 수있는 몇 가지 아이디어가 있습니다. 필자는 항상 리팩토링을 특정 클래스를 사용하여 특정 용도로 작성하는 대신 동일한 클래스의 작고 사적인 메소드로 코드를 분할하는 방식으로 생각했습니다. –

+1

@GageTrader : * helpers *, * managers * 및 모호하게 이름 지어진 클래스는 일반적으로 SRP 위반을 나타냅니다. ExcelImporter는 역할/목적을 잘 전달합니다. 'ExcelHelper'는 무엇을 의사 소통합니까? 뭘 도와 드릴까요? 클래스 이름 지정의 어려움은 SRP 위반의 징후 중 하나입니다. 클래스에 맞는 이름을 찾는 것이 더 어렵습니다. 분명히 코드의 일부를 리팩터링해야합니다. 즉, 설계가보다 명확하고 이해하기 쉽고 테스트하기 쉽습니다. –

0

확실한 것은 TDD 올바른 방법입니다. :) 위 코드에서 ExportExcelDocToDataSet 메서드를 테스트하기 전에 GetDataTableFromExcelSheet 메서드를 조롱해야합니다.

그러나 할 수있는 한 가지 방법은 다른 매개 변수를 추가하여 ExportExcelDocToDataSet 메서드를 호출 한 코드에서 GetDataTableFromExcelSheet에서 반환 된 데이터 테이블을 대신 전달하는 것입니다.

DataTable을 dtExcelData = GetData의 같은

뭔가 ...; 및

공공 데이터 집합 ExportExcelDocToDataSet (부울 headerRowProvided, DataTable을 dtExcelData)

당신이 ExportExcelDocToDataSet 방법을 테스트 할 때 ExportExcelDocToDataSet 방법의 내부 GetDataTableFromExcelSheet을 조롱 할 필요가 없습니다이 방법은 다음과 같은 방법을 수정합니다.

+0

반환 값을 가져 오는 대신 DataTable을 전달하는 것이 좋습니다. 가장 좋은 방법을 플립 플롭 (flop-flop)하는 경향이 있습니다. 인스턴스화 된 객체를 전달하거나 호출하는 메소드로 반환하는 것입니다. 여전히 호출 스택 (아래에 표시되지 않음)에 더 많은 종속성이 있습니다. 따라서 저수준 메서드 중 일부를 테스트 할 수 없다는 점에서 원래의 문제가 남아 있습니다. –

관련 문제