시험을 거친 수업에서 정탐하는 것은 나쁜 습관입니까?


14

클래스 내부 호출이 일반적인 프로젝트에서 작업하고 있지만 결과는 여러 번 간단한 값입니다. 예 ( 실제 코드 아님) :

public boolean findError(Set<Thing1> set1, Set<Thing2> set2) {
  if (!checkFirstCondition(set1, set2)) {
    return false;
  }
  if (!checkSecondCondition(set1, set2)) {
    return false;
  }
  return true;
}

이 유형의 코드에 대한 단위 테스트를 작성하는 것은 실제 조건의 구현이 아니라 조건 시스템을 테스트하고 싶기 때문에 실제로 어렵습니다. (나는 별도의 테스트 에서이 작업을 수행합니다.) 실제로 조건을 구현하는 함수를 전달하고 테스트에서 단순히 모의를 제공하는 것이 좋습니다. 이 접근 방식의 문제점은 노이즈입니다. 제네릭을 많이 사용 합니다.

작동하는 솔루션; 그러나 테스트 된 객체를 스파이 로 만들고 내부 함수에 대한 호출을 조롱하는 것입니다.

systemUnderTest = Mockito.spy(systemUnderTest);
doReturn(true).when(systemUnderTest).checkFirstCondition(....);

여기서 SUT의 구현이 효과적으로 변경되어 테스트를 구현과 동기화하는 것이 문제가 될 수 있습니다. 이것이 사실입니까? 이러한 내부 메소드 호출을 피하는 모범 사례가 있습니까?

우리는 알고리즘의 일부에 대해 이야기하고 있으므로 여러 클래스로 나누는 것이 바람직한 결정이 아닐 수도 있습니다.

답변:


15

단위 테스트는 테스트 한 클래스를 블랙 박스로 취급해야합니다. 중요한 것은 공개 방법이 예상대로 작동한다는 것입니다. 클래스가 내부 상태 및 개인 메소드를 통해이를 달성하는 방법은 중요하지 않습니다.

이런 식으로 의미있는 테스트를 작성하는 것이 불가능하다고 느끼면 수업이 너무 강력하고 과도하다는 것을 의미합니다. 기능 중 일부를 별도로 테스트 할 수있는 별도의 클래스로 이동하는 것을 고려해야합니다.


1
나는 오래 전에 단위 테스트의 아이디어를 파악하고 많은 것들을 성공적으로 작성했습니다. 종이에서 무언가가 단순 해 보이고 코드에서 더 나빠 보인다는 사실을 속이는 것입니다. 마지막으로 인터페이스가 간단하지만 입력의 주위에 세계의 절반을 조롱해야하는 무언가가 있습니다.
allprog

@allprog 많은 조롱을해야 할 때, 클래스 사이에 너무 많은 의존성이있는 것 같습니다. 그들 사이의 연결을 줄이려고 했습니까?
Philipp

@allprog 당신이 그 상황에 있다면, 클래스 디자인은 책임이 있습니다.
itsbruce

두통을 일으키는 것은 데이터 모델입니다. ORM 규칙 및 기타 여러 요구 사항을 준수해야합니다. 순수한 비즈니스 로직과 상태 비 저장 코드를 사용하면 단위 테스트를보다 쉽게 ​​얻을 수 있습니다.
allprog

3
단위 테스트는 반드시 SUT를 백 박스로 처리 할 필요는 없습니다. 이것이 단위 테스트라고하는 이유입니다. 종속성을 조롱하면 환경에 영향을 미치고 내가 조롱해야 할 것을 알기 위해 내부 요소도 알아야합니다. 그러나 이것이 SUT가 어떤 식 으로든 변경되어야한다는 것을 의미하지는 않습니다. 그러나 감시는 약간의 변화를 허용합니다.
allprog

4

두 경우 findError()checkFirstCondition()등 클래스의 공개 방법이 있습니다, 다음 findError()이미 같은 API에서 사용할 기능에 대한 외관 효율적이다. 아무 문제가 없지만 이미 존재하는 테스트와 매우 유사한 테스트를 작성해야 함을 의미합니다. 이 복제는 단순히 공용 인터페이스의 복제를 반영합니다. 그렇기 때문에이 방법을 다른 방법과 다르게 처리 할 필요는 없습니다.


내부 메소드는 테스트 가능해야하기 때문에 공개되어 있으며 SUT를 서브 클래스 화하거나 SUT 클래스의 단위 테스트를 정적 내부 클래스로 포함하고 싶지 않습니다. 그러나 나는 당신의 요점을 얻는다. 그러나 이런 종류의 상황을 피하기 위해 좋은 가이드 라인을 찾을 수 없었습니다. 튜토리얼은 항상 실제 소프트웨어와 아무런 관련이없는 기본 수준을 고수했습니다. 그렇지 않으면, 감시의 이유는 정확하게 테스트 코드의 중복을 피하고 테스트 범위를 정하기위한 것입니다.
allprog

3
적절한 단위 테스트를 위해 도우미 메서드를 공개해야한다는 데 동의하지 않습니다. 방법의 계약서에 다양한 조건이 있는지 확인한 경우 동일한 "공개 방법"에 대해 하나씩 동일한 공용 방법에 대해 여러 테스트를 작성해도 아무런 문제가 없습니다. 단위 테스트의 요점은 모든 코드의 적용 범위를 달성하는 것인데, 1 : 1 방법 테스트 대응을 통해 공개 메소드의 피상적 범위를 달성하지는 않습니다.
Kilian Foth

테스트를 위해 공개 API 만 사용하는 것은 내부 조각을 하나씩 테스트하는 것보다 훨씬 더 복잡합니다. 나는이 접근법이 최선이 아니며 내 질문이 보여주는 그 후 시점을 가지고 있다고 주장하지 않는다. 가장 큰 문제는 Java에서 함수를 구성 할 수 없으며 해결 방법이 매우 간결하다는 것입니다. 그러나 실제 단위 테스트를위한 다른 솔루션은없는 것 같습니다.
allprog

4

단위 테스트는 계약을 테스트해야합니다. 그들에게 유일한 중요한 것입니다. 계약에 포함되지 않은 것을 테스트하는 것은 시간 낭비 일뿐만 아니라 잠재적 인 오류의 원인입니다. 개발자가 구현 세부 사항을 변경할 때 테스트가 변경되는 것을 볼 때마다 알람 벨이 울립니다. 그 개발자는 (의도적 으로든 아니든) 실수를 숨기고있을 수 있습니다. 의도적으로 구현 세부 사항을 테스트하면 이러한 나쁜 습관이 발생하여 오류가 가려 질 가능성이 높아집니다.

내부 호출은 구현 세부 사항이며 성능 측정에만 관심이 있어야합니다 . 일반적으로 단위 테스트 작업이 아닙니다.


잘 들린다. 그러나 실제로 입력하고 코드를 호출해야하는 "문자열"은 함수에 대해 거의 알지 못하는 언어입니다. 이론 상으로는 문제를 쉽게 설명하고 여기저기서 쉽게 대체 할 수 있습니다. 코드에서 나는 그것을 사용하는 것을 거절하는이 유연성을 달성하기 위해 많은 구문 소음을 추가해야합니다. a메소드 b에 동일한 클래스의 메소드 호출이 포함 된 경우 테스트 a에는의 테스트가 포함되어야합니다 b. 그리고 매개 변수로 b전달되지 않는 한이를 변경할 수있는 방법 a이 없지만 다른 해결책은 없습니다.
allprog

1
b공용 인터페이스의 일부 이면 어쨌든 테스트해야합니다. 그렇지 않은 경우 테스트 할 필요가 없습니다. 테스트하고 싶었 기 때문에 공개했다면 잘못한 것입니다.
itsbruce

@Philip의 답변에 대한 내 의견을 참조하십시오. 아직 언급하지 않았지만 데이터 모델은 악의 근원입니다. 순수한 상태 비 저장 코드는 케이크 한 조각입니다.
allprog

2

먼저, 작성한 예제 함수에 대해 테스트하기 어려운 것이 궁금합니다. 내가 볼 수있는 한, 단순히 다양한 입력을 전달하고 올바른 부울 값이 반환되는지 확인하십시오. 내가 무엇을 놓치고 있습니까?

스파이의 경우 스파이와 모의를 사용하는 소위 "화이트 박스"테스트는 작성해야 할 테스트 코드가 훨씬 많을뿐 아니라 구현할 때마다 인터페이스가 동일하게 유지되는 경우에도 테스트를 변경해야합니다. 또한 이러한 종류의 테스트는 블랙 박스 테스트보다 신뢰성이 떨어집니다. 왜냐하면 모든 추가 테스트 코드가 올바른지 확인해야하고 블랙 박스 단위 테스트가 인터페이스와 일치하지 않으면 실패 할 것이라는 것을 신뢰할 수 있기 때문입니다. , 때로는 테스트가 실제 코드 만 모의 테스트하지 않기 때문에 모의를 과도하게 사용하는 코드에 대해 믿을 수 없습니다. 모의가 잘못되면 테스트가 성공할 가능성이 있지만 코드가 여전히 손상되었을 수 있습니다.

화이트 박스 테스트 경험이있는 사람이라면 누구나 작성 및 유지 관리에 어려움을 겪을 수 있습니다. 신뢰성이 낮고 화이트 박스 테스트는 대부분의 경우에 훨씬 열등합니다.


메모 주셔서 감사합니다. 예제 함수는 복잡한 알고리즘으로 작성해야하는 것보다 훨씬 간단합니다. 실제로, 문제는 더 비슷하다는 것이 밝혀졌습니다. 여러 부분에서 스파이로 알고리즘을 테스트하는 것이 문제가됩니까? 이것은 상태 저장 코드가 아니며 모든 상태는 입력 인수로 분리됩니다. 문제는 하위 기능에 제정신 매개 변수를 제공하지 않고 예제에서 복잡한 기능을 테스트하려고한다는 사실입니다.
allprog 2016 년

Java 8에서 기능 프로그래밍이 시작되면서이 기능은 약간 더 우아해졌지만 여전히 단일 클래스에서 기능을 유지하는 것이 다른 경우 (단독으로 유용하지 않은) 부분을 "한 번만 사용"으로 추출하는 것보다 알고리즘의 경우 더 나은 선택이 될 수 있습니다. 테스트 가능성 때문에 클래스. 이와 관련하여 스파이는 모의와 동일하지만 일관된 코드를 시각적으로 날려 버릴 필요가 없습니다. 실제로, 모의와 동일한 설정 코드가 사용됩니다. 나는 극한을 피하고 싶습니다. 특정 장소에서 모든 유형의 테스트가 적절할 수 있습니다. 어쨌든 테스트는 어쨌든 훨씬 낫습니다. :)
allprog 2016 년

"복잡한 기능을 테스트하고 싶습니다. 하위 기능에 제정신 파라미터를 제공하지 않아도됩니다."-나는 당신이 의미하는 바를 얻지 못합니다. 어떤 하위 기능? '복잡한 기능'이 사용하는 내부 기능에 대해 이야기하고 있습니까?
BT

그것은 내 경우에 스파이가 유용한 것입니다. 내부 기능은 제어하기가 매우 복잡합니다. 코드 때문이 아니라 논리적으로 복잡한 것을 구현하기 때문입니다. 다른 클래스로 물건을 옮기는 것은 당연한 선택이지만 그 기능만으로는 전혀 유용하지 않습니다. 따라서 클래스를 함께 유지하고 스파이 기능으로 제어하는 ​​것이 더 나은 옵션으로 밝혀졌습니다. 거의 1 년 동안 완벽하게 작업했으며 모델 변경을 쉽게 견딜 수 있습니다. 나는 그 이후 로이 패턴을 사용하지 않았으며, 경우에 따라 실행 가능하다고 언급하는 것이 좋았습니다.
allprog

@allprog "논리적으로 복잡한"-복잡한 경우 복잡한 테스트가 필요합니다. 그 주위에 방법이 없습니다. 스파이는 당신을 위해 더 어렵고 복잡하게 만들 것입니다. 스파이를 사용하여 다른 함수 내에서 특수한 동작을 테스트하지 않고 스스로 테스트 할 수있는 이해할 수있는 하위 함수를 작성해야합니다.
BT
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.