전략 패턴으로 리팩토링 된 함수를 단위 테스트하는 방법은 무엇입니까?


10

코드에 다음과 같은 함수가있는 경우 :

class Employee{

    public string calculateTax(string name, int salary)
    {
        switch (name)
        {
            case "Chris":
                doSomething($salary);
            case "David":
                doSomethingDifferent($salary);
            case "Scott":
               doOtherThing($salary);               
       }
}

일반적으로 팩토리 클래스와 전략 패턴을 사용하여 Ploymorphism을 사용하도록 이것을 리팩터링합니다.

public string calculateTax(string name)
{
    InameHandler nameHandler = NameHandlerFactory::getHandler(name);
    nameHandler->calculateTax($salary);
}

이제 TDD를 사용하는 경우 calculateTax()리팩토링 전에 원본에서 작동하는 몇 가지 테스트가 있습니다 .

전의:

calculateTax_givenChrisSalaryBelowThreshold_Expect111(){}    
calculateTax_givenChrisSalaryAboveThreshold_Expect111(){}

calculateTax_givenDavidSalaryBelowThreshold_Expect222(){}   
calculateTax_givenDavidSalaryAboveThreshold_Expect222(){} 

calculateTax_givenScottSalaryBelowThreshold_Expect333(){}
calculateTax_givenScottSalaryAboveThreshold_Expect333(){}

리팩토링 후 Factory 클래스 NameHandlerFactory와 적어도 3 개의 구현을 갖습니다 InameHandler.

테스트를 리팩토링하려면 어떻게해야합니까? 나는에 대한 단위 테스트를 삭제해야 claculateTax()로부터 EmployeeTests및 각 구현을위한 테스트 클래스를 생성 InameHandler?

Factory 클래스도 테스트해야합니까?

답변:


6

이전 테스트는 calculateTax여전히 정상적으로 작동 하는지 확인하는 데 적합합니다. 그러나 이것에 대해 많은 테스트 사례가 필요하지 않으며 3 개만 필요합니다 (또는 예기치 않은 값을 사용하여 오류 처리도 테스트하려는 경우 더 많을 수도 있습니다 name).

각 개별 사례 (현재 구현 된 시점 doSomething)에는 자체 구현 세트도 있어야합니다.이 테스트는 각 구현과 관련된 내부 세부 사항과 특수 사례를 테스트합니다. 새로운 설정에서 이러한 테스트는 해당 전략 클래스에서 직접 테스트로 변환 할 수 있습니다.

나는 그들이 실행하는 코드와 그것이 구현하는 기능이 완전히 사라진 경우에만 오래된 단위 테스트를 제거하는 것을 선호합니다. 그렇지 않으면 이러한 테스트로 인코딩 된 지식은 여전히 ​​관련이 있으며 테스트 만 리팩토링해야합니다.

최신 정보

의 시험 사이에 약간의 중복이있을 수 있습니다 calculateTax(의 그들을 호출 할 수 있도록 높은 수준의 테스트 ) 및 개별 계산 전략 (대한 테스트 낮은 레벨 테스트 ) - 당신의 구현에 따라 달라집니다.

테스트의 원래 구현은 특정 세금 계산의 결과를 주장하고 특정 계산 전략이 그것을 생성하는 데 사용되었음을 암시 적으로 추측합니다. 이 스키마를 유지하면 실제로 중복이 발생합니다. 그러나 @Kristof가 암시했듯이 mock을 사용하여 높은 수준의 테스트를 구현하여 올바른 종류의 (mock) 전략이 선택되고 호출되었는지 확인할 수 calculateTax있습니다. 이 경우 높은 수준과 낮은 수준의 테스트간에 중복이 없습니다.

따라서 영향을받는 테스트를 리팩토링하는 데 너무 많은 비용이 들지 않으면 후자의 방법을 선호합니다. 그러나 실제로는 대규모 리팩토링을 할 때 충분한 시간을 절약하면 소량의 테스트 코드 복제를 허용합니다. :-)

Factory 클래스도 테스트해야합니까?

다시 말하지만, 그것은 달려 있습니다. calculateTax공장 을 효과적으로 테스트 하는 테스트에 유의하십시오 . 따라서 팩토리 코드가 switch위의 코드와 같은 사소한 블록이면 이러한 테스트만으로 충분할 수 있습니다. 그러나 공장에서 좀 더 까다로운 작업을 수행하는 경우 특별히 테스트를 수행 할 수 있습니다. 문제의 코드가 실제로 작동한다는 것을 확신하는 데 필요한 테스트 양으로 요약됩니다. 코드를 읽거나 코드 적용 범위 데이터를 분석 할 때 테스트되지 않은 실행 경로가 표시되면이를 테스트하기 위해 몇 가지 테스트를 더하십시오. 그런 다음 코드에 완전히 확신 할 때까지이 과정을 반복하십시오.


실제 실제 코드에 가깝도록 코드를 약간 수정했습니다. 이제 salary함수 calculateTax()에 대한 두 번째 입력 이 추가되었습니다. 이렇게하면 원래 기능과 전략 클래스의 3 가지 구현에 대한 테스트 코드를 복제 할 것이라고 생각합니다.
Songo

@ Songo, 내 업데이트를 참조하십시오.
Péter Török

5

TDD 나 단위 테스트에 대해 전문가가 아니라고 말하면서 시작하겠습니다. 그러나 이것을 테스트하는 방법은 다음과 같습니다 (의사 코드를 사용합니다).

CalculateTaxDelegatesToNameHandler()
{
    INameHandlerFactory fakeNameHandlerFactory = Fake(INameHandlerFactory);
    INameHandler fakeNameHandler = Fake(INameHandler);

    A.Call.To(fakeNameHandlerFactory.getHandler("John")).Returns(fakeNameHandler);

    Employee employee = new Employee(fakeNameHandlerFactory);
    employee.CalculateTax("John");

    Assert.That.WasCalled(fakeNameHandler.calculateTax());
}

그래서 있는지 테스트 것 calculateTax()직원 클래스의 방법은 올바르게 요청 NameHandlerFactoryA에 대한 NameHandler다음 호출 calculateTax()반환의 방법을 NameHandler.


흠 그래서 당신은 대신 테스트를 행동 테스트로 만들고 (특정 함수가 호출되었는지 테스트) 위임 클래스에서 값 어설 션을 만들어야합니까?
Songo

그렇습니다. 실제로 NameHandlerFactory와 NameHandler에 대한 별도의 테스트를 작성하려고합니다. 그것들을 가지고 있다면 Employee.calculateTax()메소드 에서 기능을 다시 테스트 할 이유가 없습니다 . 이렇게하면 새로운 NameHandler를 도입 할 때 추가 직원 테스트를 추가 할 필요가 없습니다.
Kristof Claes

3

하나의 수업 (모든 것을 수행하는 직원)을 복용하고 공장, 직원 (전략 만 포함하는) 및 전략의 3 가지 그룹 그룹을 만듭니다.

따라서 세 그룹의 테스트를 만드십시오.

  1. 공장을 격리하여 테스트하십시오. 입력을 올바르게 처리합니까? 당신이 알 수없는을 통과하면 어떻게됩니까?
  2. 직원을 격리하여 테스트하십시오. 임의의 전략을 설정할 수 있으며 예상대로 작동합니까? 전략이나 공장 설정이 없으면 어떻게됩니까? (코드에서 가능하다면)
  3. 격리 된 전략을 테스트하십시오. 각각 당신이 기대하는 전략을 수행합니까? 홀수 경계 입력을 일관된 방식으로 처리합니까?

물론 전체 shebang에 대해 자동화 된 테스트를 수행 할 수 있지만 이제는 통합 테스트와 유사하므로 처리해야합니다.


2

코드를 작성하기 전에 팩토리 테스트를 시작합니다. 내가 필요한 것들을 조롱하면 구현과 유스 케이스에 대해 스스로 생각하게됩니다.

팩토리를 구현하고 각 구현에 대한 테스트를 계속하고 마지막으로 해당 테스트에 대한 구현 자체를 수행합니다.

마지막으로 이전 테스트를 제거합니다.


2

내 의견은 당신이 아무것도하지 않아야한다는 것입니다. 즉, 새로운 테스트를 추가해서는 안됩니다.

나는 이것이 의견이라고 강조하며, 실제로는 당신이 그 대상의 기대를 인식하는 방식에 달려 있습니다. 클래스 사용자가 세금 계산을위한 전략을 제공하고자한다고 생각하십니까? 그가 신경 쓰지 않는다면, 시험은 그것을 반영해야하고, 단위 시험에서 반영된 행동은 클래스가 세금을 계산하기 위해 전략 객체를 사용하기 시작한 것을 신경 쓰지 않아야한다는 것입니다.

실제로 TDD를 사용할 때이 문제가 여러 번 발생했습니다. 외부 리소스 (파일, DB, 원격 서비스 등)와 같은 구조적 경계 종속성과 달리 전략 개체는 자연스러운 종속성이 아니라고 생각합니다. 그것은 자연스러운 의존성이 아니기 때문에, 나는 보통이 전략에 대한 나의 수업의 행동에 근거하지 않습니다. 내 직감은 수업의 기대치가 변경된 경우에만 시험을 변경해야한다는 것입니다.

TDD를 사용할 때이 문제에 대해 정확하게 이야기하는 Bob 아저씨 의 훌륭한 게시물 이 있습니다.

개별 클래스를 테스트하는 경향이 TDD를 죽이는 것이라고 생각합니다. TDD의 전체 장점은 테스트를 사용하여 디자인 체계를 촉진하고 그 반대도 아니라는 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.