답변:
Mock 객체를 작성하고 테스트 용도로만 사용하십시오. 그것들은 일반적으로 매우 매우 적으며 (추상 클래스에서 상 속됨) 더 이상은 아닙니다. 그러면 단위 테스트에서 테스트하려는 추상 메소드를 호출 할 수 있습니다.
가지고있는 다른 모든 클래스와 같이 일부 논리가 포함 된 추상 클래스를 테스트해야합니다.
추상 기본 클래스가 사용되는 두 가지 방법이 있습니다.
추상 객체를 전문화하고 있지만 모든 클라이언트는 기본 인터페이스를 통해 파생 클래스를 사용합니다.
디자인의 객체 내에서 중복을 제거하기 위해 추상 기본 클래스를 사용하고 있으며 클라이언트는 자체 인터페이스를 통해 구체적인 구현을 사용합니다.!
1 전략 패턴에 대한 솔루션
첫 번째 상황 인 경우 실제로 파생 클래스가 구현하는 추상 클래스의 가상 메소드로 정의 된 인터페이스가 있습니다.
이것을 실제 인터페이스로 만들고 추상 클래스를 구체적으로 변경하고 생성자에서이 인터페이스의 인스턴스를 가져와야합니다. 파생 클래스는이 새로운 인터페이스의 구현이됩니다.
즉, 이제 새로운 인터페이스의 모의 인스턴스를 사용하여 이전 추상 클래스를 테스트 할 수 있으며 이제는 공용 인터페이스를 통해 각각의 새로운 구현을 테스트 할 수 있습니다. 모든 것이 간단하고 테스트 가능합니다.
2를위한 해결책
두 번째 상황이있는 경우 추상 클래스는 도우미 클래스로 작동합니다.
포함 된 기능을 살펴보십시오. 이 복제를 최소화하기 위해 조작중인 물체에 밀어 넣을 수 있는지 확인하십시오. 여전히 남아있는 것이 있다면 구체적인 구현이 생성자를 가져와 기본 클래스를 제거하는 도우미 클래스로 만드는 것을보십시오.
이것은 간단하고 쉽게 테스트 할 수있는 구체적인 수업으로 이어집니다.
원칙적으로
복잡한 복잡한 네트워크를 통해 단순한 단순한 네트워크를 선호합니다.
확장 가능한 테스트 가능 코드의 핵심은 작은 빌딩 블록과 독립적 인 배선입니다.
업데이트 : 두 가지 혼합물을 처리하는 방법?
이 두 역할을 모두 수행하는 기본 클래스를 가질 수 있습니다. 즉, 공용 인터페이스가 있으며 도우미 메소드를 보호합니다. 이 경우 도우미 메소드를 하나의 클래스 (시나리오 2)로 인수 화하고 상속 트리를 전략 패턴으로 변환 할 수 있습니다.
기본 클래스가 직접 구현하고 다른 메소드가 가상 인 일부 메소드가있는 경우에도 상속 트리를 전략 패턴으로 변환 할 수 있지만 책임이 올바르게 정렬되지 않았 음을 나타내는 좋은 지표로 사용할 수도 있습니다. 리팩토링이 필요합니다.
업데이트 2 : 디딤돌로서의 추상 클래스 (2014/06/12)
요 전에는 초록을 사용한 상황이 있었으므로 그 이유를 살펴보고 싶습니다.
구성 파일에 대한 표준 형식이 있습니다. 이 특정 도구에는 해당 형식으로 된 3 개의 구성 파일이 있습니다. 종속성 설정을 통해 클래스가 관심있는 설정을 요청할 수 있도록 각 설정 파일에 대해 강력한 유형의 클래스를 원했습니다.
설정 파일 형식을 구문 분석하는 방법을 알고있는 추상 기본 클래스와 동일한 방법을 노출하지만 설정 파일의 위치를 캡슐화 한 파생 클래스를 사용하여이를 구현했습니다.
3 개의 클래스가 래핑 된 "SettingsFileParser"를 작성한 다음 기본 클래스에 위임하여 데이터 액세스 방법을 노출 할 수있었습니다. 나는 다른 어떤 것보다 더 많은 위임 코드를 가진 3 개의 파생 클래스로 이어질 수 있기 때문에 아직 이 작업을 수행하지 않기로 선택했습니다 .
그러나 ...이 코드가 발전하고 이러한 각 설정 클래스의 소비자가 더 명확 해집니다. 각 설정 사용자는 몇 가지 설정을 요구하고 어떤 방식 으로든 설정을 변경합니다 (설정은 텍스트이므로 설정을 숫자 등으로 변환하는 개체로 래핑 할 수 있습니다). 이런 일이 발생하면이 논리를 데이터 조작 방법으로 추출하고 강력한 형식의 설정 클래스로 다시 밀어 넣습니다. 이는 각 설정 세트에 대해 더 높은 수준의 인터페이스로 연결되어 결국 '설정'을 다루는 것을 더 이상 인식하지 못합니다.
이 시점에서 강력한 형식의 설정 클래스는 더 이상 기본 '설정'구현을 노출하는 "게터"메소드가 필요하지 않습니다.
그 시점에서 나는 더 이상 공개 인터페이스에 설정 접근 자 메소드를 포함시키지 않기를 원합니다. 따라서이 클래스를 변경하여 설정 파서 클래스를 캡슐화하지 않고 변경합니다.
따라서 Abstract 클래스는 현재 위임 코드를 피할 수있는 방법이며 코드의 마커는 나중에 디자인을 변경하도록 상기시킵니다. 나는 그것을 얻지 못할 수도 있으므로 좋은 시간 동안 살 수 있습니다 ... 코드 만 말할 수 있습니다.
"정적 메소드 없음"또는 "비공개 메소드 없음"과 같은 규칙에서이 사실을 발견했습니다. 그들은 코드에서 냄새를 나타냅니다 ... 그리고 좋습니다. 그것은 당신이 놓친 추상화를 계속 찾고 ... 그 동안 고객에게 가치를 계속 제공합니다.
계곡에 유지 가능한 코드가있는 풍경을 정의하는 것과 같은 규칙을 상상합니다. 새로운 행동을 추가하면 코드에 비가 오는 것과 같습니다. 처음에는 착륙 할 때마다 그것을 배치합니다. 그런 다음 리팩터링하여 좋은 디자인의 힘이 모든 동작이 계곡에서 끝날 때까지 행동을 옮길 수 있도록합니다.
내가 추상 클래스와 인터페이스를 위해하는 일은 다음과 같다 : 나는 테스트를 작성하고, 객체를 구체적으로 사용한다. 그러나 X 유형의 변수 (X는 추상 클래스 임)는 테스트에서 설정되지 않습니다. 이 테스트 클래스는 테스트 스위트에 추가되지 않지만 서브 클래스에는 변수를 X의 구체적인 구현으로 설정하는 setup-method가 있습니다. 그렇게하면 테스트 코드를 복제하지 않습니다. 사용되지 않은 테스트의 서브 클래스는 필요한 경우 더 많은 테스트 방법을 추가 할 수 있습니다.
구체적으로 추상 클래스에서 단위 테스트를 수행하려면 테스트 목적, base.method () 결과 테스트 및 상속시 의도 된 동작을 테스트해야합니다.
메소드를 호출하여 테스트하므로 추상 클래스를 구현하여 테스트하십시오.
귀하의 추상 클래스에 비즈니스 가치가있는 구체적인 기능이 포함되어 있으면 일반적으로 추상 데이터를 스텁하는 테스트 더블을 작성하거나 모의 프레임 워크를 사용하여 직접 테스트하여 직접 테스트합니다. 내가 선택하는 것은 추상 메소드의 테스트 특정 구현을 작성해야하는지 여부에 달려 있습니다.
가장 일반적인 시나리오는 템플릿 메소드 패턴을 사용하는 경우 (예 : 타사에서 사용할 일종의 확장 가능한 프레임 워크를 빌드하는 경우)입니다. 이 경우 추상 클래스는 테스트하려는 알고리즘을 정의하는 것이므로 특정 구현보다 추상 기반을 테스트하는 것이 더 합리적입니다.
그러나 이러한 테스트는 실제 비즈니스 로직 의 구체적인 구현 에만 중점을 두어야 합니다 . 취성 테스트로 끝날 것이기 때문에 추상 클래스의 구현 세부 사항 을 단위 테스트하지 않아야 합니다.
한 가지 방법은 추상 클래스에 해당하는 추상 테스트 사례를 작성한 다음 추상 테스트 사례를 서브 클래스하는 구체적인 테스트 사례를 작성하는 것입니다. 원래 추상 클래스의 각 구체적인 서브 클래스에 대해이를 수행하십시오 (예 : 테스트 케이스 계층 구조가 클래스 계층 구조를 반영 함). junit 레시피 서적에서 인터페이스 테스트 : http://safari.informit.com/9781932394238/ch02lev1sec6을 참조하십시오 .
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);
}
/*,,,*/
}
예상치 못한 추상 메서드가 호출되면 테스트가 실패합니다. 테스트를 정렬 할 때 어설 션을 수행하고 예외를 throw하며 다른 값을 반환하는 람다로 추상 메서드를 쉽게 스텁 할 수 있습니다.
추상 클래스를 사용하는 주요 동기 중 하나는 응용 프로그램 내에서 다형성을 활성화하는 것입니다. 즉, 런타임에 다른 버전을 대체 할 수 있습니다. 실제로 이것은 추상 클래스가 종종 템플릿 패턴 이라고 불리는 일반적인 배관을 제공한다는 점을 제외하고는 인터페이스를 사용하는 것과 거의 동일합니다 .
단위 테스트 관점에서 고려해야 할 두 가지가 있습니다.
추상 클래스와 관련 클래스의 상호 작용 . 모의 테스트 프레임 워크를 사용하는 것은 추상 클래스가 다른 클래스와 잘 작동 함을 보여주기 때문에이 시나리오에 이상적입니다.
파생 클래스의 기능 . 파생 클래스에 대해 작성한 사용자 지정 논리가있는 경우 해당 클래스를 별도로 테스트해야합니다.
편집 : RhinoMocks는 클래스에서 동적으로 파생시켜 런타임에 모의 객체를 생성 할 수있는 멋진 모의 테스트 프레임 워크입니다. 이 접근 방식을 사용하면 수많은 코딩 작업 시간을 절약 할 수 있습니다.
먼저 추상 클래스에 구체적인 방법이 포함되어 있다면이 예제를 고려해야한다고 생각합니다.
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 답변에 따라 abstract를 구현 @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;
}
}
으로 추상 클래스를 인스턴스화 할 수 있지만,이 서브 클래스화할 수있는 구체적인 클래스 DEF.java , 같은 다음입니다 :
public class DEF extends ABC {
@Override
public String sayHello() {
return "Hello!";
}
}
초록과 비 추상적 방법을 모두 테스트하는 @Test 클래스 :
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"));
}
}