순열이 많은 항목에 대해 TDD를 수행하는 방법은 무엇입니까?


15

AI와 같은 시스템을 만들 때 매우 다양한 경로를 매우 빠르게 사용할 수 있거나 실제로 여러 입력이있는 알고리즘을 사용할 경우 가능한 결과 집합에 많은 수의 순열이 포함될 수 있습니다.

많은 수의 다양한 순열 결과를 출력하는 시스템을 만들 때 TDD를 사용하려면 어떤 방법을 사용해야합니까?


1
AI 시스템의 전반적인 장점은 일반적으로 벤치 마크 입력 세트를 사용한 Precision-Recall 테스트에 의해 측정됩니다. 이 테스트는 "통합 테스트"와 거의 같습니다. 다른 사람들이 언급했듯이 "테스트 중심 디자인 " 보다는 "테스트 중심 알고리즘 연구"와 비슷 합니다.
rwong

"AI"의 의미를 정의하십시오. 특정 유형의 프로그램보다 더 많은 연구 분야입니다. 특정 AI 구현의 경우 일반적으로 TDD를 통해 일부 유형의 사물 (예 : 응급 행동)을 테스트 할 수 없습니다.
Steven Evers

@SnOrfus 저는 가장 일반적인 초보적인 의미의 의사 결정 기계라는 의미입니다.
Nicole

답변:


7

pdr의 답변에 보다 실질적인 접근 . TDD는 테스트보다는 소프트웨어 디자인에 관한 것입니다. 단위 테스트를 사용하여 작업을 확인할 수 있습니다.

따라서 단위 테스트 수준에서는 완전히 결정적인 방식으로 테스트 할 수 있도록 단위를 설계해야합니다. 단위를 비 결정적 (예 : 난수 생성기)으로 만드는 것을 취하고이를 추상화하면됩니다. 이동이 좋은지 아닌지를 결정하는 방법에 대한 순진한 예가 있다고 가정 해보십시오.

class Decider {

  public boolean decide(float input, float risk) {

      float inputRand = Math.random();
      if (inputRand > input) {
         float riskRand = Math.random();
      }
      return false;

  }

}

// The usage:
Decider d = new Decider();
d.decide(0.1337f, 0.1337f);

이 방법은 테스트하기가 매우 어렵고 단위 테스트에서 실제로 확인할 수있는 유일한 것은 한계입니다 ... 그러나 한계에 도달하려면 많은 시도가 필요합니다. 대신 인터페이스를 만들고 기능을 래핑하는 구체적인 클래스를 만들어 무작위 부분을 요약 해 봅시다.

public interface IRandom {

   public float random();

}

public class ConcreteRandom implements IRandom {

   public float random() {
      return Math.random();
   }

}

Decider클래스는 이제 즉, 인터페이스의 추상화를 통해 구체적인 클래스를 사용합니다. 이 작업을 수행하는 방법을 종속성 주입이라고합니다 (아래 예는 생성자 삽입의 예이지만 setter를 사용하여이를 수행 할 수도 있음).

class Decider {

  IRandom irandom;

  public Decider(IRandom irandom) { // constructor injection
      this.irandom = irandom;
  }

  public boolean decide(float input, float risk) {

      float inputRand = irandom.random();
      if (inputRand > input) {
         float riskRand = irandom.random();
      }
      return false;

  }

}

// The usage:
Decider d = new Decider(new ConcreteRandom);
d.decide(0.1337f, 0.1337f);

이 "코드 팽창"이 필요한 이유를 스스로에게 물어볼 수도 있습니다. 글쎄, 우선, 이제는 "계약" Decider을 따르는 종속성이 있기 때문에 알고리즘의 임의 부분의 동작을 조롱 할 수 있습니다 IRandom. 이를 위해 조롱 프레임 워크를 사용할 수 있지만이 예제는 직접 코딩하기에 충분히 간단합니다.

class MockedRandom() implements IRandom {

    public List<Float> floats = new ArrayList<Float>();
    int pos;

   public void addFloat(float f) {
     floats.add(f);
   }

   public float random() {
      float out = floats.get(pos);
      if (pos != floats.size()) {
         pos++;
      }
      return out;
   }

}

가장 중요한 부분은 이것이 "실제적인"구체적인 구현을 완전히 대체 할 수 있다는 것입니다. 코드는 다음과 같이 쉽게 테스트 할 수 있습니다.

@Before void setUp() {
  MockedRandom mRandom = new MockedRandom();

  Decider decider = new Decider(mRandom);
}

@Test
public void testDecisionWithLowInput_ShouldGiveFalse() {

  mRandom.addFloat(0f);

  assertFalse(decider.decide(0.1337f, 0.1337f));
}

@Test
public void testDecisionWithHighInputRandButLowRiskRand_ShouldGiveFalse() {

  mRandom.addFloat(1f);
  mRandom.addFloat(0f);

  assertFalse(decider.decide(0.1337f, 0.1337f));
}

@Test
public void testDecisionWithHighInputRandAndHighRiskRand_ShouldGiveTrue() {

  mRandom.addFloat(1f);
  mRandom.addFloat(1f);

  assertTrue(decider.decide(0.1337f, 0.1337f));
}

희망 사항을 테스트하여 모든 대소 문자를 테스트 할 수 있도록 응용 프로그램을 디자인하는 방법에 대한 아이디어를 얻으십시오.


3

엄격한 TDD는 좀 더 복잡한 시스템의 경우 조금씩 분해되는 경향이 있지만 실제적인 용어에서는 그다지 중요하지 않습니다. 개별 입력을 분리 할 수없는 경우 합리적인 적용 범위를 제공하는 테스트 사례를 선택하여 사용하십시오.

이를 위해서는 구현이 잘 수행 될 것인지에 대한 지식이 필요하지만 이론적 인 문제가 될 것입니다. 기술이 아닌 사용자가 세부적으로 지정한 AI를 작성하지는 않을 것입니다. 하드 코딩하여 테스트 사례에 테스트를 전달하는 것과 같은 범주에 있습니다. 공식적으로 테스트는 사양이며 구현은 정확하고 가능한 가장 빠른 솔루션이지만 실제로는 발생하지 않습니다.


2

TDD는 테스트에 관한 것이 아니라 디자인에 관한 것입니다.

복잡성에서 벗어나지 않고 이러한 상황에서 뛰어납니다. 더 작은 조각으로 더 큰 문제를 고려하게되면 더 나은 디자인으로 이어질 것입니다.

알고리즘의 모든 순열을 테스트하도록 설정하지 마십시오. 테스트 후 테스트를 빌드하고 가장 간단한 코드를 작성하여베이스를 덮을 때까지 테스트를 수행하십시오. 다른 부분을 테스트하는 동안 문제의 일부를 가짜로 만들어 100 억 개의 순열에 대해 100 억 개의 테스트를 작성하지 않아도되기 때문에 문제를 해결하는 것에 대한 의미를 알 수 있습니다.

편집 : 예를 추가하고 싶었지만 시간이 없었습니다.

적절한 정렬 알고리즘을 고려해 봅시다. 우리는 배열의 상단, 배열의 하단 및 모든 종류의 이상한 조합을 다루는 테스트를 작성할 수 있습니다. 각각에 대해, 우리는 어떤 종류의 객체로 구성된 완전한 배열을 만들어야합니다. 시간이 걸릴 것입니다.

또는 네 부분으로 문제를 해결할 수 있습니다.

  1. 배열을 탐색하십시오.
  2. 선택한 항목을 비교하십시오.
  3. 항목을 전환하십시오.
  4. 위 세 가지를 조정하십시오.

첫 번째는 문제의 유일한 복잡한 부분이지만 나머지 부분에서 추상화함으로써 훨씬 더 간단하게 만들었습니다.

두 번째는 객체 자체에 의해 거의 확실하게 처리되며, 적어도 선택적으로 많은 정적 유형 프레임 워크에는 해당 기능이 구현되었는지 여부를 표시하는 인터페이스가 있습니다. 따라서 이것을 테스트 할 필요가 없습니다.

세 번째는 테스트하기 매우 쉽습니다.

네 번째는 두 개의 포인터를 처리하고 순회 클래스에 포인터를 움직여달라고 요청하고, 비교를 요구하며, 그 비교 결과에 따라 교환 될 항목을 요구합니다. 처음 세 가지 문제를 해결했다면이를 쉽게 테스트 할 수 있습니다.

우리는 어떻게 더 나은 디자인을 이끌어 냈습니까? 간단하게 유지하고 거품 정렬을 구현했다고 가정 해 봅시다. 그것은 작동하지만 생산에 가고 백만 개의 물체를 처리해야 할 때 너무 느립니다. 새로운 순회 기능을 작성하고 교체하기 만하면됩니다. 다른 세 가지 문제를 처리하는 복잡성을 처리 할 필요는 없습니다.

이것은 유닛 테스트와 TDD의 차이점입니다. 유닛 테스터는 이것이 테스트를 취약하게 만들었다 고 말합니다. 단순한 입력 및 출력을 테스트했다면 이제 새로운 기능에 대해 더 많은 테스트를 작성할 필요가 없습니다. TDDer는 내가 가진 각 수업이 한 가지 일만 잘 수행 할 수 있도록 우려 사항을 적절히 분리했다고 말할 것입니다.


1

변수가 많은 계산의 모든 순열을 테스트 할 수는 없습니다. 그러나 그것은 새로운 것이 아니며, 장난감 복잡성 이상의 모든 프로그램에서 항상 사실이었습니다. 테스트 포인트 는 계산 속성 을 확인하는 것 입니다. 예를 들어, 1000 개의 숫자로 목록을 정렬하려면 약간의 노력이 필요하지만 모든 개별 솔루션을 매우 쉽게 확인할 수 있습니다. 이제 1000이 있지만! 해당 프로그램에 대한 가능한 (클래스) 입력을 모두 테스트 할 수는 없으며, 1000 개의 입력을 무작위로 생성하고 출력이 실제로 정렬되어 있는지 확인하는 것으로 충분합니다. 왜? 일반적으로 정확 하지 않고 무작위로 생성 된 1000 개의 벡터를 안정적으로 정렬하는 프로그램을 작성하는 것은 거의 불가능하기 때문에 (특정 마술 입력을 조작하도록 의도적으로 조작하지 않는 한 ...)

일반적으로 상황이 좀 더 복잡합니다. 정말이 그들이 금요일 사용자 이름에 'F'와 요일을되어있는 경우 우편물이 사용자에게 이메일을 제공하지 않을 버그이었다. 그러나 나는 그런 기이함을 예상하기 위해 노력을 낭비한다고 생각합니다. 테스트 스위트는 시스템이 예상 한 입력에 대해 기대하는 것을 수행한다는 확실한 확신을 제공해야합니다. 특정 펑키 사례에서 펑키 한 일을하는 경우 첫 번째 펑키 사례를 시도한 후 곧 눈에 띄게 알 수 있으며 해당 사례에 대해 구체적으로 테스트를 작성할 수 있습니다 (일반적으로 유사한 사례의 전체 클래스도 포함).


1000 개의 입력을 무작위로 생성하면 출력을 어떻게 테스트합니까? 분명히 그러한 테스트에는 자체 테스트되지 않은 논리가 포함됩니다. 테스트를 테스트 하시겠습니까? 어떻게? 요점은 상태 전이를 사용하여 로직을 테스트해야한다는 것입니다. 입력 X에 대해 출력은 Y 여야합니다. 로직과 관련된 테스트는 테스트하는 로직만큼 오류가 발생하기 쉽습니다. 논리적으로 말해서, 다른 주장으로 논쟁을 정당화하면 회의론적인 회귀 경로에 놓이게 됩니다. 몇 가지 주장 을 해야합니다 . 이 주장은 당신의 테스트입니다.
Izhaki

0

에지 케이스와 임의의 입력을 취하십시오.

정렬 예를 보려면 :

  • 랜덤리스트 몇 개 정렬
  • 이미 정렬 된 목록을 작성하십시오
  • 역순으로 목록을 작성
  • 거의 정렬 된 목록을 가져옵니다.

이것들에 대해 빨리 작동한다면, 모든 입력에 대해 잘 작동 할 것입니다.

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