programing

추상 클래스를 유닛화하는 방법: stub를 사용하여 확장?

bestcode 2022. 8. 17. 23:49
반응형

추상 클래스를 유닛화하는 방법: stub를 사용하여 확장?

추상수업과 추상수업을 확장한 수업을 어떻게 구분할지 궁금했어요.

추상 클래스를 확장하고 추상 메서드를 제거한 다음 모든 구체적인 메서드를 테스트해야 합니까?그런 다음 내가 재정의한 메서드만 테스트하고 단위 테스트에서 추상 클래스를 확장하는 개체에 대해 추상 메서드를 테스트하시겠습니까?

추상 클래스의 메서드를 테스트하는 데 사용할 수 있는 추상 테스트 케이스를 가지고 추상 클래스를 확장하는 오브젝트에 대해 테스트 케이스에서 이 클래스를 확장해야 합니까?

제 추상 수업은 몇 가지 구체적인 방법을 가지고 있습니다.

추상 기본 클래스를 사용하는 방법은 두 가지가 있습니다.

  1. 추상 객체를 특수화하고 있지만 모든 클라이언트는 기본 인터페이스를 통해 파생 클래스를 사용합니다.

  2. 추상 기본 클래스를 사용하여 설계 내의 객체 내 중복을 배제하고 클라이언트는 자체 인터페이스를 통해 구체적인 구현을 사용합니다.


솔루션 1 - 전략 패턴

옵션 1

첫 번째 상황일 경우 파생 클래스가 구현하고 있는 추상 클래스의 가상 메서드에 의해 정의된 인터페이스가 실제로 존재합니다.

이를 실제 인터페이스로 만들고 추상 클래스를 구체적으로 변경하여 해당 컨스트럭터에서 이 인터페이스의 인스턴스를 사용하는 것이 좋습니다.파생 클래스가 이 새로운 인터페이스의 실장이 됩니다.

리모트

즉, 새로운 인터페이스의 모의 인스턴스를 사용하여 이전의 추상 클래스를 테스트할 수 있고 퍼블릭인터페이스를 통해 새로운 실장을 테스트할 수 있습니다.모든 것이 간단하고 테스트 가능합니다.


솔루션: 2

두 번째 상황일 경우 추상 클래스는 도우미 클래스로 작동합니다.

Abstract Helper

여기에 포함된 기능을 살펴봅니다.이 중복을 최소화하기 위해 조작 중인 오브젝트에 푸시할 수 있는 것이 있는지 확인합니다.아직 남아 있는 것이 있으면 구체적인 구현이 컨스트럭터에서 사용하는 도우미 클래스로 만들고 기본 클래스를 제거합니다.

모터 헬퍼

이는 다시 단순하고 쉽게 테스트할 수 있는 구체적인 클래스로 이어집니다.


룰로서

복잡한 객체의 단순한 네트워크보다 단순한 객체의 복잡한 네트워크를 선호합니다.

확장 가능한 테스트 가능 코드의 핵심은 작은 구성 요소와 독립적인 배선입니다.


업데이트됨 : 두 가지 혼합물을 처리하는 방법

기본 클래스에서 이 두 가지 역할을 모두 수행할 수 있습니다.즉, 퍼블릭인터페이스와 보호 도우미 메서드가 있습니다.이 경우 도우미 메서드를 하나의 클래스(시나리오2)로 나누고 상속 트리를 전략 패턴으로 변환할 수 있습니다.

기본 클래스에서 직접 구현한 메서드와 가상 메서드가 있는 경우에도 상속 트리를 전략 패턴으로 변환할 수 있지만, 책임이 올바르게 정렬되지 않았으므로 리팩터링이 필요할 수 있습니다.


업데이트 2 : 발판으로서의 추상교실(2014/06/12)

얼마 전에 추상화를 사용한 적이 있기 때문에, 그 이유를 알고 싶습니다.

구성 파일에는 표준 형식이 있습니다.이 툴에는 3개의 컨피규레이션파일이 모두 같은 형식으로 포함되어 있습니다.각 설정 파일에 대해 강한 타입의 클래스를 원했기 때문에 의존성 주입을 통해 관심 있는 설정을 요구할 수 있었습니다.

설정 파일 형식을 해석하는 방법을 아는 추상 기본 클래스와 이러한 메서드를 노출하는 파생 클래스를 가지고 있지만 설정 파일의 위치를 캡슐화했습니다.

3개의 클래스가 랩한 「Settings File Parser」를 작성해, 베이스 클래스에 위임해 데이터 액세스 방법을 공개할 수 있었습니다.다른 무엇보다 위임 코드가 많은 파생 클래스가 3개 생기기 때문에 아직 하지 않기로 했습니다.

하지만...이 코드가 진화하고 각 설정 클래스의 소비자가 명확해짐에 따라각 설정에서는, 몇개의 설정을 요구해, 몇개의 방법으로 변환합니다(설정은 텍스트이기 때문에, 숫자로 변환하는 오브젝트로 랩 할 수 있습니다).이 때 이 로직을 데이터 조작 방법으로 추출하여 강력한 유형의 설정 클래스로 되돌립니다.이렇게 하면 각 설정 세트에 대해 더 높은 수준의 인터페이스가 제공되며, 결국 '설정'을 더 이상 처리하지 못하게 됩니다.

이 시점에서 강력한 유형의 설정 클래스는 더 이상 기본 '설정' 구현을 노출하는 "getter" 메서드가 필요하지 않습니다.

이 시점에서는 퍼블릭인터페이스에 settings accessor 메서드가 포함되어 있지 않기 때문에 이 클래스에서 파생되는 것이 아니라 settings parser 클래스를 캡슐화하도록 이 클래스를 변경합니다.

따라서 Abstract 클래스는 현재 위임 코드를 피할 수 있는 방법이며 나중에 설계를 변경하도록 알려주는 코드 내의 마커입니다.영원히 못 갈 수도 있으니까 오래 갈 수도 있고...암호만이 알 수 있어요

어떤 규칙에서도 사실인 것 같아요예를 들어 "no static methods" 또는 "no private methods"입니다암호에서 냄새가 나는데...잘된 일이야당신이 놓쳤던 추상화를 계속 찾을 수 있게 해주죠그 사이에 고객에게 계속 가치를 제공할 수 있습니다.

이와 같은 규칙이 풍경을 정의하는 것으로 생각됩니다. 유지보수가 가능한 코드가 계곡에 존재합니다.새로운 행동을 더하면 코드에 비가 내리는 것과 같습니다.처음에는 어디에나 놓아야 합니다.그런 다음 좋은 디자인의 힘이 모든 것이 계곡으로 끝날 때까지 행동을 밀어낼 수 있도록 리팩터링합니다.

Mock 객체를 작성하여 테스트용으로만 사용합니다.그것들은 보통 아주 아주 아주 아주 미미하며(추상적인 클래스에서 상속된다) 그 이상은 아니다.그런 다음 단위 테스트에서 테스트할 추상 메서드를 호출할 수 있습니다.

다른 모든 클래스와 마찬가지로 논리를 포함하는 추상 클래스를 테스트해야 합니다.

추상 클래스 및 인터페이스에서 수행하는 작업은 다음과 같습니다.나는 그 물체를 그대로 사용하는 시험을 쓴다.그러나 유형 X(X는 추상 클래스)의 변수는 테스트에서 설정되지 않았습니다.이 테스트 클래스는 변수를 X의 구체적인 구현으로 설정하는 설정 메서드를 가진 테스트 스위트에는 추가되지 않지만 하위 클래스에 추가됩니다.그래야 테스트 코드를 복제하지 않을 수 있습니다.사용하지 않는 테스트의 서브클래스는 필요에 따라 테스트 방법을 추가할 수 있습니다.

특히 추상 클래스에서 단위 테스트를 만들려면 테스트 목적인 테스트 기준으로 추출해야 합니다.method() 결과 및 상속 시 의도된 동작.

메서드를 호출하여 테스트하므로 구현하여 추상 클래스를 테스트합니다.

귀하의 추상 클래스에 비즈니스 가치가 있는 구체적인 기능이 포함되어 있다면, 저는 보통 추상 데이터를 추출하는 테스트 더블을 만들거나 이를 위해 조롱 프레임워크를 사용하여 직접 테스트합니다.어떤 것을 선택할지는 추상적 방법의 테스트별 구현을 작성해야 하는지 여부에 따라 크게 달라집니다.

이 작업을 수행해야 하는 가장 일반적인 시나리오는 서드파티에 의해 사용되는 확장 가능한 프레임워크를 구축하는 경우 등 템플릿 메서드 패턴을 사용하는 경우입니다.이 경우 Abstract 클래스는 테스트하려는 알고리즘을 정의하는 것이기 때문에 특정 구현보다 Abstract Base를 테스트하는 것이 더 합리적입니다.

단, 이러한 테스트는 실제 비즈니스 로직의 구체적인 구현에만 초점을 맞추는 것이 중요하다고 생각합니다.추상 클래스의 테스트 구현 세부사항을 조합해서는 안 됩니다.결국 취약한 테스트가 되기 때문입니다.

한 가지 방법은 추상적인 테스트 케이스를 당신의 추상적인 클래스에 대응하고, 그리고 당신의 추상적인 테스트 케이스를 하위 분류하는 구체적인 테스트 케이스를 작성하는 것입니다.원래 추상 클래스의 각 구체적인 하위 클래스에 대해 이 작업을 수행합니다(즉, 테스트 케이스 계층이 클래스 계층을 반영합니다).junit recipients 북에서 인터페이스 테스트를 참조하십시오.http://safari.informit.com/9781932394238/ch02lev1sec6https://www.manning.com/books/junit-recipes 또는 safari 계정이 없는 경우 https://www.amazon.com/JUnit-Recipes-Practical-Methods-Programmer/dp/1932394230을 참조하십시오.

또, 「xUnit 패턴의 Testcase Superclass」도 참조해 주세요.http://xunitpatterns.com/Testcase%20Superclass.html

저는 "추상" 시험에 반대합니다.시험은 구체적인 생각이고 추상적인 개념이 없는 것 같아요.공통 요소가 있는 경우 모든 사용자가 사용할 수 있도록 도우미 메서드 또는 클래스에 배치합니다.

추상적인 시험 수업의 경우, 무엇을 시험하고 있는지 자문해 보세요.몇 가지 방법이 있습니다.또한 시나리오에서 어떤 방법이 기능하는지 알아내야 합니다.서브클래스에서 새로운 메서드를 테스트하려고 합니까?그런 다음 해당 방법만 사용하여 테스트하도록 하십시오.기본 클래스에서 메서드를 테스트하고 있습니까?그런 다음 해당 클래스에만 별도의 고정 장치를 두고 각 방법을 필요한 수만큼 검사하여 개별적으로 검사합니다.

추상 클래스를 테스트하기 위해 하니스를 설정할 때 주로 따르는 패턴은 다음과 같습니다.

public abstract class MyBase{
  /*...*/
  public abstract void VoidMethod(object param1);
  public abstract object MethodWithReturn(object param1);
  /*,,,*/
}

테스트 대상 버전:

public class MyBaseHarness : MyBase{
  /*...*/
  public Action<object> VoidMethodFunction;
  public override void VoidMethod(object param1){
    VoidMethodFunction(param1);
  }
  public Func<object, object> MethodWithReturnFunction;
  public override object MethodWithReturn(object param1){
    return MethodWihtReturnFunction(param1);
  }
  /*,,,*/
}

예상치 못한 상황에서 추상 메서드가 호출되면 테스트가 실패합니다.테스트를 준비할 때, 어설트 실행, 예외 던지기, 다른 값 반환 등을 수행하는 람다를 사용하여 추상적인 메서드를 쉽게 제거할 수 있습니다.

구체적인 방법이 효과가 없는 추상적인 방법을 호출하는 경우 각 자식 클래스의 동작을 개별적으로 테스트해야 합니다.그렇지 않으면, 당신이 설명한 대로 추상적인 메서드를 확장하고 스텁하는 것이 좋습니다.단, 추상적인 클래스의 구체적인 메서드가 자녀 클래스에서 분리되어 있으면 됩니다.

추상 클래스의 기본 기능을 테스트하는 것이 좋을 것 같습니다.그러나 어떤 메서드도 무시하지 않고 클래스를 확장하여 추상적인 메서드를 조롱하는 것이 가장 좋습니다.

추상 클래스를 사용하는 주요 동기 중 하나는 응용 프로그램 내에서 다형성을 활성화하는 것입니다. 즉, 런타임에 다른 버전을 대체할 수 있습니다.실제로 이는 인터페이스를 사용하는 것과 거의 비슷하지만 추상 클래스는 템플릿 패턴이라고 불리는 몇 가지 공통 배관을 제공합니다.

유닛 테스트의 관점에서 고려할 사항은 다음 두 가지가 있습니다.

  1. 추상 클래스와 관련 클래스의 상호 작용.모의고사 프레임워크를 사용하면 추상 클래스가 다른 클래스와 잘 어울린다는 것을 알 수 있으므로 이 시나리오에 이상적입니다.

  2. 파생 클래스의 기능.파생 클래스에 대해 작성한 사용자 지정 로직이 있는 경우 해당 클래스를 분리하여 테스트해야 합니다.

edit: Rhino Mocks는 클래스에서 동적으로 파생하여 런타임에 모의 객체를 생성할 수 있는 훌륭한 모의 테스트 프레임워크입니다.이 방법을 사용하면 수작업으로 코딩하는 수업을 수없이 절약할 수 있습니다.

먼저 추상 클래스가 구체적인 메서드를 포함하고 있다면 이 예를 고려하여 이 작업을 수행해야 한다고 생각합니다.

 public abstract class A 

 {

    public boolean method 1
    {
        // concrete method which we have to test.

    }


 }


 class B extends class A

 {

      @override
      public boolean method 1
      {
        // override same method as above.

      }


 } 


  class Test_A 

  {

    private static B b;  // reference object of the class B

    @Before
    public void init()

      {

      b = new B ();    

      }

     @Test
     public void Test_method 1

       {

       b.method 1; // use some assertion statements.

       }

   }

추상 클래스가 구현에 적합한 경우 위에서 제안한 대로 파생된 콘크리트 클래스를 테스트합니다.당신의 추측은 옳습니다.

향후 혼란을 피하기 위해 이 구체적인 시험 수업은 모의가 아니라 가짜임을 유의하십시오.

엄밀한 용어로 모크는 다음과 같은 특성에 의해 정의됩니다.

  • 테스트 대상 과목 클래스의 모든 종속성을 대신하여 모크가 사용됩니다.
  • 모크란 인터페이스의 의사 실장입니다(일반적으로 의존관계는 인터페이스로 선언해야 합니다.테스트성이 그 주된 이유 중 하나입니다).
  • 메서드나 속성에 관계없이 모의 인터페이스 구성원의 동작은 테스트 시 제공됩니다(또한 조롱 프레임워크 사용).이렇게 하면 테스트 중인 구현과 종속성의 구현(모두 고유한 개별 테스트가 있어야 함)의 결합을 피할 수 있습니다.

@patrick-desjardins 답변에 이어 추상화 및 구현 클래스를 구현했습니다.@Test다음과 같습니다.

추상 클래스 - ABC.java

import java.util.ArrayList;
import java.util.List;

public abstract class ABC {

    abstract String sayHello();

    public List<String> getList() {
        final List<String> defaultList = new ArrayList<>();
        defaultList.add("abstract class");
        return defaultList;
    }
}

Abstract 클래스는 인스턴스화할 없지만 하위 클래스가능하므로 구체적클래스 DEF.java는 다음과 같습니다.

public class DEF extends ABC {

    @Override
    public String sayHello() {
        return "Hello!";
    }
}

@추상 메서드와 비추상 메서드를 모두 테스트하는 테스트 클래스:

import org.junit.Before;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.contains;
import java.util.Collection;
import java.util.List;
import static org.hamcrest.Matchers.equalTo;

import org.junit.Test;

public class DEFTest {

    private DEF def;

    @Before
    public void setup() {
        def = new DEF();
    }

    @Test
    public void add(){
        String result = def.sayHello();
        assertThat(result, is(equalTo("Hello!")));
    }

    @Test
    public void getList(){
        List<String> result = def.getList();
        assertThat((Collection<String>) result, is(not(empty())));
        assertThat(result, contains("abstract class"));
    }
}

언급URL : https://stackoverflow.com/questions/243274/how-to-unit-test-abstract-classes-extend-with-stubs

반응형