TDD에서 재 설계 한 후 해당 메소드가 비공개가되면 메소드 테스트는 어떻게됩니까?


29

다른 캐릭터와 그 종류의 물건을 공격하는 캐릭터로 롤 게임을 시작한다고 가정 해 봅시다.

TDD를 적용하여 Character.receiveAttack(Int)메서드 내부의 논리를 테스트하기 위해 테스트 사례를 만듭니다 . 이 같은:

@Test
fun healthIsReducedWhenCharacterIsAttacked() {
    val c = Character(100) //arg is the health
    c.receiveAttack(50) //arg is the suffered attack damage
    assertThat(c.health, is(50));
}

10 가지 방법의 테스트 receiveAttack방법 이 있다고 가정 해 봅시다 . 이제 메소드 Character.attack(Character)를 호출 하는 메소드를 추가하고 receiveAttack일부 TDD 주기로 메소드를 테스트 한 후 다음과 같이 결정 Character.receiveAttack(Int)합니다 private.

이전 10 개의 테스트 사례는 어떻게됩니까? 삭제해야합니까? 나는 방법을 유지해야합니까 public(그렇지 않다)?

이 질문은 개인 메소드를 테스트하는 방법이 아니라 TDD를 적용 할 때 재 설계 후 처리하는 방법에 관한 것입니다.



10
비공개 인 경우 테스트하지 않으면 쉽습니다. 리 팩터 댄스를
kayess

6
나는 아마 여기 곡물에 반대 할 것입니다. 그러나 나는 일반적으로 모든 방법으로 개인 방법을 피합니다. 적은 테스트보다 더 많은 테스트를 선호합니다. 나는 사람들이 "무엇을 소비자에게 노출시키고 싶지 않은 어떤 종류의 기능을 가지고 있지 않습니까?" 예, 노출하고 싶지 않은 것이 많습니다. 대신 개인 메서드가있을 때 대신 자체 클래스로 리팩터링하고 원래 클래스의 해당 클래스를 사용합니다. 새 수업은 internal해당 언어가 노출되지 않도록 표시 하거나 해당 언어 로 표시 할 수 있습니다 . 실제로 Kevin Cline의 대답은 이런 종류의 접근 방식입니다.
user9993

3
@ user9993 당신은 그것을 거꾸로 가지고있는 것 같습니다. 더 많은 테스트를받는 것이 중요한 경우 중요한 사항을 놓치지 않도록하는 유일한 방법은 범위 분석을 실행하는 것입니다. 또한 적용 범위 도구의 경우 방법이 개인 또는 공개 또는 기타 다른 경우에는 중요하지 않습니다. 물건을 공개하는 것이 어떻게 든 범위 분석의 부족을 보상하기를 바라고 나는 내가 두려워하는 잘못된 보안 감각을 준다
gnat

2
@gnat 그러나 나는 "보장을받지 않는"것에 대해 아무 말도하지 않았다? "나는 적은 테스트보다 더 많은 테스트를 선호합니다"에 대한 나의 의견은 그 사실을 분명히 보여주었습니다. 정확히 무엇을 얻고 있는지 잘 모르겠습니다. 물론 추출한 코드도 테스트합니다. 그게 요점입니다.
user9993

답변:


52

TDD에서 테스트는 디자인의 실행 가능한 문서 역할을합니다. 디자인이 변경되었으므로 문서도 변경해야합니다!

TDD에서이 attack방법이 나타날 수 있는 유일한 방법 은 실패한 테스트 통과의 결과라는 것입니다. 이는 attack다른 테스트에 의해 테스트되고 있음을 의미 합니다. 이는 의 테스트에 의해 간접적으로 적용됨을 의미합니다 . 이상적으로는로 변경하면 하나 이상의 테스트를 중단해야 합니다.receiveAttackattackreceiveAttackattack

그렇지 않은 경우 receiveAttack더 이상 필요하지 않으며 더 이상 존재하지 않아야하는 기능이 있습니다!

따라서 receiveAttack이미를 통해 테스트되었으므로 테스트를 attack유지할지 여부는 중요하지 않습니다. 경우 귀하의 테스트 프레임 워크를 만드는 것이 쉬운 개인 방법을 테스트하고, 경우에 당신이 개인 방법을 테스트하기로 결정, 당신은 그들을 유지할 수 있습니다. 그러나 테스트 범위와 신뢰도를 잃지 않으면 서 삭제할 수 있습니다.


14
"테스트 프레임 워크를 통해 개인 메소드를 쉽게 테스트 할 수 있고 개인 메소드를 테스트하기로 결정한 경우이를 유지할 수 있습니다." 개인 메소드는 구현 세부 사항이며 직접 테스트해서는 안됩니다.
David Arno

20
@DavidArno : 모듈의 내부를 테스트해서는 안된다는 이유에 동의하지 않습니다. 그러나 모듈의 내부는 매우 복잡 할 수 있으므로 각 개별 내부 기능에 대해 단위 테스트를 수행하는 것이 중요 할 수 있습니다. 단위 테스트는 기능 의 불변성 을 확인하는 데 사용 됩니다. 개인 방법에 불변이있는 경우 (사전 조건 / 사후 조건) 단위 테스트는 가치가 있습니다.
Matthieu M.

8
"이런 이유로 모듈의 내부를 테스트해서는 안된다 ". 이러한 내부는 직접 테스트 해서는 안됩니다 . 모든 테스트는 퍼블릭 API 만 테스트해야합니다. 공개 API를 통해 내부 요소에 도달 할 수 없으면 아무 것도 수행하지 않으므로 삭제하십시오.
David Arno

28
@DavidArno이 논리에 따르면 라이브러리가 아닌 실행 파일을 작성하는 경우 단위 테스트가 전혀 필요하지 않습니다. - "함수 호출은 공개 API의 일부가 아닙니다. 명령 행 인수 만 있습니다! 명령 행 인수를 통해 프로그램의 내부 기능에 도달 할 수 없으면 아무 것도 수행하지 않으므로 삭제하십시오." -개인 함수는 클래스의 공개 API의 일부가 아니지만 클래스의 내부 API의 일부입니다. 클래스의 내부 API를 반드시 테스트 할 필요는 없지만 실행 파일의 내부 API를 테스트하기 위해 동일한 논리를 사용할 수 있습니다.
RM

7
@RM는 경우 '비 모듈 형 방식으로 실행 파일을 만들 수 있었다, 그때 실행 파일을 사용하여 통합 테스트를 내부의 부서지기 쉬운 시험 사이의 선택, 또는만을 사용하여 강제로 및 I / O 런타임 될 것이다. 따라서 strawman 버전이 아닌 실제 논리에 따라 모듈 방식으로 (예 : 라이브러리 세트를 통해) 만들 것입니다. 그런 다음 해당 모듈의 공개 API를 취성 방식으로 테스트 할 수 있습니다.
David Arno

23

방법이 테스트를 수행하기에 충분히 복잡한 경우 일부 클래스에서 공개되어야합니다. 그래서 당신은 리팩토링합니다 :

public class X {
  private int complexity(...) {
    ...
  }
  public void somethingElse() {
    int c = complexity(...);
  }
}

에:

public class Complexity {
  public int calculate(...) {
    ...
  }
}

public class X {
  private Complexity complexity;
  public X(Complexity complexity) { // dependency injection happiness
    this.complexity = complexity;
  }

  public void something() {
    int c = complexity.calculate(...);
  }
}

X.complexity에 대한 현재 테스트를 ComplexityTest로 이동하십시오. 그런 다음 복잡성을 조롱하여 X.something 텍스트를 작성하십시오.

내 경험상 소규모 클래스와 짧은 메소드로 리팩토링하면 큰 이점이 있습니다. 이해하기 쉽고 테스트하기 쉬우 며 기대 이상으로 재사용됩니다.


귀하의 답변은 OP의 질문에 대한 의견에서 설명하려고하는 아이디어를 훨씬 명확하게 설명합니다. 좋은 대답입니다.
user9993

3
답변 주셔서 감사합니다. 실제로 receiveAttack 메소드는 매우 간단합니다 ( this.health = this.health - attackDamage). 아마도 다른 클래스로 추출하는 것은 현재로서는 과도하게 설계된 솔루션입니다.
Héctor

1
이것은 분명히 OP를 과도하게 사용합니다. 달로 날아 가지 않고 상점으로 운전하고 싶지만 일반적인 경우에는 좋은 해결책입니다.

함수가 그렇게 단순한 경우에는 처음부터 함수로 정의되기도합니다.
David K

1
오늘날 과잉 일 수도 있지만 6 개월 동안이 코드에 많은 변화가있을 경우 이점이 분명해질 것입니다. 그리고 요즘 괜찮은 IDE에서 요즘 분명히 바이너리를 분리하여 클래스로 추출하는 것은 런타임 바이너리에서 어쨌든 같은 것으로 보일 것이라는 점을 고려할 때 과도하게 엔지니어링 된 솔루션은 거의 두 번의 키 입력이어야합니다.
Stephen Byrne

6

receiveAttack 메소드를 테스트하는 10 가지 메소드가 있다고 가정 해보십시오. 이제 Character.attack (Character) 메소드 (receiveAttack 메소드를 호출 함)를 추가하고, 일부 TDD 주기로 테스트 한 후에 결정을 내립니다. Character.receiveAttack (Int)는 private이어야합니다.

여기서 명심 해야 할 것은 API에서 메소드를 제거하는 것입니다 . 이전 버전과의 호환성에 대한 제안은

  1. 제거 할 필요가 없으면 API에 그대로 두십시오.
  2. 아직 제거 할 필요가없는 경우 폐기 된 것으로 표시하고 가능한 경우 수명이 다한 문서를 표시하십시오.
  3. 제거해야 할 경우 버전이 크게 변경되었습니다.

API가 더 이상 메소드를 지원하지 않으면 테스트가 제거되거나 교체됩니다. 이 시점에서 private 메소드는 리팩토링 할 수 있어야하는 구현 세부 사항입니다.

이 시점에서 테스트 스위트가 구현에 직접 액세스해야하는지 공개 API를 통해 순수하게 상호 작용해야하는지에 대한 표준 질문으로 돌아 왔습니다. 프라이빗 메소드는 테스트 스위트가 방해받지 않고 대체 할 수 있는 것 입니다. 따라서 테스트가 중단되거나 구현과 함께 테스트 가능한 개별 구성 요소로 이동하여 테스트가 사라질 것으로 기대합니다.


3
지원 중단이 항상 우려되는 것은 아닙니다. 질문에서 : "개발을 시작한다고 가정 해 봅시다 ..."소프트웨어가 아직 출시되지 않은 경우 지원 중단은 문제가 아닙니다. 또한 "역할 게임"이것이 의미 하지 재사용 가능한 라이브러리,하지만 최종 사용자를 대상으로 바이너리 소프트웨어. 일부 최종 사용자 소프트웨어에는 공용 API (예 : MS Office)가 있지만 대부분은 그렇지 않습니다. 심지어 소프트웨어 않는 공개 API를 단지 플러그인, 스크립트 노출 그것의 부분 (LUA 확장자를 가진 예를 들어 게임), 또는 다른 기능이 있습니다. 그러나 OP가 설명하는 일반적인 경우에 대한 아이디어를 제시 할 가치가 있습니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.