지수 테스트 사례가 필요한 경우 TDD 및 완벽한 테스트 범위


18

고객의 매우 구체적인 요구 사항에 따라 정렬되지 않은 검색 결과 목록을 정렬하는 데 도움이되는 목록 비교기를 작성하고 있습니다. 요구 사항은 다음 순서에 따라 순위 관련 알고리즘을 중요도 순서로 요구합니다.

  1. 이름과 정확히 일치
  2. 검색어의 모든 단어 이름 또는 결과의 동의어
  3. 검색어 이름 또는 결과의 동의어 (% 내림차순)
  4. 설명에있는 검색어의 모든 단어
  5. 설명에서 검색어의 일부 단어 (% 내림차순)
  6. 마지막 수정 날짜 내림차순

이 비교기의 자연스러운 디자인 선택은 2의 거듭 제곱에 따라 점수가 매겨진 순위 인 것처럼 보였습니다. 중요도가 낮은 규칙의 합은 중요도가 높은 규칙에 대한 긍정적 인 일치 이상이 될 수 없습니다. 이것은 다음 점수에 의해 달성됩니다.

  1. 32
  2. 16
  3. 8 (내림차순 %에 따른 2 차 타이 브레이커 점수)
  4. 4
  5. 2 (내림차순 %에 따른 2 차 타이 브레이커 점수)
  6. 1

TDD 정신에서 나는 먼저 단위 테스트부터 시작하기로 결정했습니다. 각 고유 시나리오에 대한 테스트 케이스를 갖는 것은 규칙 3 및 5의 2 차 타이 브레이커 로직에 대한 추가 테스트 케이스를 고려하지 않는 최소 63 개의 고유 테스트 케이스입니다.

실제 테스트는 실제로 적습니다. 실제 규칙 자체에 따라 특정 규칙은 하위 규칙이 항상 참임을 보장합니다 (예 : '모든 검색어 단어가 설명에 나타나는 경우'규칙 '일부 검색어 단어가 설명에 나타나는 경우'규칙은 항상 참임). 이러한 각 테스트 사례를 작성하는 데 여전히 노력의 수준이 있습니까? 이것이 TDD에서 약 100 % 테스트 범위를 말할 때 일반적으로 요구되는 테스트 레벨입니까? 그렇지 않은 경우 수용 가능한 대체 테스트 전략은 무엇입니까?


1
이 시나리오와 비슷한 시나리오는 테스트 코드를 한 번 작성하고 입력과 예상 결과를 포함하는 둘 이상의 배열에 피드를 제공 할 수있는 "TMatrixTestCase"및 열거자를 개발 한 이유입니다.
Marjan Venema

답변:


17

귀하의 질문은 TDD가 "모든 테스트 사례를 먼저 작성"과 관련이 있음을 나타냅니다. IMHO는 "TDD의 정신"이 아니라 실제로는 반대 입니다. TDD는 "테스트 중심 개발 "을 나타내 므로 구현을 실제로 "구동"하는 테스트 사례 만 필요합니다. 또한 각 새로운 요구 사항에 따라 코드 블록 수가 기하 급수적으로 증가하는 방식으로 구현이 설계되지 않은 경우 기하 급수의 테스트 사례도 필요하지 않습니다. 귀하의 예에서 TDD 사이클은 아마도 다음과 같습니다.

  • 목록에서 첫 번째 요구 사항으로 시작하십시오. "이름과 정확히 일치하는"단어는 다른 모든 것보다 높은 점수를 받아야합니다.
  • 이제 이것에 대한 첫 번째 테스트 사례를 작성하고 (예 : 주어진 쿼리와 일치하는 단어) 테스트를 통과시키는 최소한의 작업 코드를 구현하십시오.
  • 첫 번째 요구 사항에 대한 두 번째 테스트 사례를 추가하고 (예 : 검색어와 일치하지 않는 단어) 새 테스트 사례를 추가하기 전에 두 번째 테스트가 통과 될 때까지 기존 코드를 변경하십시오.
  • 구현 세부 사항에 따라 빈 쿼리, 빈 단어 등과 같은 테스트 사례를 자유롭게 추가하십시오 (TDD는 흰색 상자 접근 방식이므로 구현할 때 구현을 알고 있다는 사실을 활용할 수 있습니다) 테스트 사례를 디자인하십시오).

그런 다음 두 번째 요구 사항으로 시작하십시오.

  • "이름 또는 결과의 동의어에있는 검색어의 모든 단어"는 "이름에 대한 정확한 일치"보다 낮은 점수를 받아야하지만 다른 모든 것보다 높은 점수를 받아야합니다.
  • 이제 위와 같이이 새로운 요구 사항에 대한 테스트 케이스를 하나씩 빌드하고 각각의 새 테스트 후에 코드의 다음 부분을 구현하십시오. 코드와 테스트 사례를 리팩토링하는 것을 잊지 마십시오.

여기 캐치 온다 : 당신이, 당신은 단지 카테고리 "N-1"의 점수가 "N"범주의 점수보다 높은 있는지 확인하기 위해 테스트를 추가해야합니다 요구 사항 / 분류 번호에 대한 테스트 케이스를 추가 "N"때 . 이전에 작성한 테스트에서 해당 범주의 점수가 여전히 올바른 순서인지 확인하므로 범주 1, ..., n-1의 다른 모든 조합에 대해 테스트 사례를 추가하지 않아도됩니다.

따라서 이것은 여러 가지 테스트 사례를 제공 할 것입니다.


1
이 답변이 정말 마음에 듭니다. TDD를 염두에두고이 문제에 접근하기위한 명확하고 간결한 단위 테스트 전략을 제공합니다. 당신은 그것을 아주 멋지게 분해합니다.
maple_shaft

@maple_shaft : 감사합니다. 질문이 정말 좋습니다. 모든 테스트 케이스를 먼저 디자인하는 접근법을 사용하더라도 테스트에 대한 동등성 클래스를 구축하는 고전적인 기술로 지수 성장을 줄일 수 있다고 생각합니다 (그러나 지금까지는 작동하지 않았습니다).
Doc Brown

13

사전 정의 된 조건 목록을 통과하고 모든 성공적인 점검에 대해 현재 점수에 2를 곱하는 클래스를 작성하십시오.

이 테스트는 몇 가지 모의 테스트를 사용하여 매우 쉽게 테스트 할 수 있습니다.

그런 다음 각 조건에 대한 클래스를 작성할 수 있으며 각 경우에 대해 2 개의 테스트 만 있습니다.

유스 케이스를 이해하지 못하지만이 예제가 도움이되기를 바랍니다.

public class ScoreBuilder
{
    private ISingleScorableCondition[] _conditions;
    public ScoreBuilder (ISingleScorableCondition[] conditions)
    {
        _conditions = conditions;
    }

    public int GetScore(string toBeScored)
    {
        foreach (var condition in _conditions)
        {
            if (_conditions.Test(toBeScored))
            {
                // score this somehow
            }
        }
    }
}

public class ExactMatchOnNameCondition : ISingleScorableCondition
{
    private IDataSource _dataSource;
    public ExactMatchOnNameCondition(IDataSource dataSource)
    {
        _dataSource = dataSource;
    }

    public bool Test(string toBeTested)
    {
        return _dataSource.Contains(toBeTested);
    }
}

// etc

2 ^ 조건 테스트는 4+ (2 * 조건)로 빠르게 내려갑니다. 20은 64보다 훨씬 덜 압도적입니다. 나중에 다른 클래스를 추가 할 경우 기존 클래스를 변경하지 않아도 (개방형 원칙) 64 개의 새로운 테스트를 작성할 필요가 없습니다. 2 개의 새로운 테스트로 다른 클래스를 추가하고 ScoreBuilder 클래스에 주입합니다.


재미있는 접근법. 한 번의 비교기 구성 요소를 염두에두고 내 마음이 OOP 접근 방식을 고려하지 않았습니다. 나는 실제로 알고리즘 조언을 찾지 않았지만 이것은 상관없이 매우 유용합니다.
maple_shaft

4
@maple_shaft : 아니요. 그러나 TDD 조언을 찾고 있었으므로 이러한 종류의 알고리즘은 노력을 크게 줄임으로써 노력의 가치가 있는지 여부에 대한 질문을 제거하는 데 완벽합니다. 복잡성을 줄이는 것이 TDD의 핵심입니다.
pdr

+1, 좋은 답변입니다. 비록 그러한 정교한 솔루션이 없다고 믿더라도 테스트 사례의 수가 기하 급수적으로 증가 할 필요는 없습니다 (아래 답변 참조).
Doc Brown

다른 질문에 대한 실제 질문에 대한 답변이 더 좋았 기 때문에 귀하의 답변을 수락하지 않았지만 귀하의 설계 방식이 너무 좋아서 제안한 방식으로 구현하고 있습니다. 이렇게하면 복잡성이 줄어들고 장기적으로 확장 성이 향상됩니다.
maple_shaft

4

이러한 각 테스트 사례를 작성하는 데 여전히 노력의 수준이 있습니까?

"가치"를 정의해야합니다. 이런 종류의 시나리오의 문제점은 테스트에서 유용성에 대한 수익이 감소한다는 것입니다. 확실히 당신이 쓰는 첫 번째 시험은 그만한 가치가있을 것입니다. 우선 순위에서 명백한 오류를 발견 할 수 있으며 단어를 분석하려고 할 때 구문 분석 오류와 같은 것들도 있습니다.

두 번째 테스트는 코드를 통한 다른 경로를 다루므로 다른 우선 순위 관계를 확인하기 때문에 가치가 있습니다.

63 번째 테스트는 코드의 논리 나 다른 테스트에서 99.99 %의 확신을 가지고 있기 때문에 그만한 가치가 없을 것입니다.

이것이 TDD에서 약 100 % 테스트 범위를 말할 때 일반적으로 요구되는 테스트 레벨입니까?

내 이해는 100 % 적용 범위는 모든 코드 경로가 연습되었음을 의미합니다. 이것은 규칙의 모든 조합을 수행한다는 것을 의미하지는 않지만 코드가 다운 될 수있는 모든 다른 경로를 지적합니다 (일부 조합은 코드에 존재할 수 없음). 그러나 TDD를 수행하고 있으므로 아직 경로를 확인할 "코드"가 없습니다. 이 과정의 서한은 모두 63 세 이상이라고합니다.

개인적으로, 나는 100 % 적용 범위가 파이프 꿈이라고 생각합니다. 그 외에도 실용적이지 않습니다. 단위 테스트는 당신을 위해 존재합니다. 더 많은 테스트를하면 이익에 대한 수익이 줄어 듭니다 (테스트에서 버그를 예방할 가능성 + 코드가 정확하다는 확신). 코드의 기능에 따라 슬라이딩 스케일에서 테스트를 중지하는 위치를 정의합니다. 코드에서 원자로를 운영하는 경우 63 개 이상의 테스트가 모두 가치가 있습니다. 코드에서 음악 보관함을 구성하는 경우 훨씬 적은 비용으로 도망 갈 수 있습니다.


"커버리지"는 일반적으로 코드 범위 (모든 코드 행이 실행 됨) 또는 분기 범위 (모든 분기가 가능한 모든 방향으로 한 번 이상 실행 됨)를 나타냅니다. 두 유형의 커버리지 모두 64 가지 테스트 사례가 필요하지 않습니다. 적어도 64 가지 경우 각각에 대한 개별 코드 부분을 포함하지 않는 심각한 구현은 아닙니다. 따라서 100 % 적용 범위가 완전히 가능합니다.
Doc Brown

@DocBrown-물론,이 경우-다른 것들을 테스트하기가 더 어렵거나 불가능합니다. 메모리 부족 예외 경로를 고려하십시오. 행동을 시행하기 위해 '서신에 의한'TDD에 64 개가 모두 요구되지는 않습니까?
Telastyn

글쎄, 내 의견은 질문과 관련이 있으며 귀하의 답변은 OP의 경우 100 % 적용 범위를 얻는 것이 어려울 수 있다는 인상을줍니다 . 의심 스럽다. 그리고 100 % 적용 범위를 달성하기 어려운 사례를 만들 수는 있지만 동의하지 않았다는 데 동의합니다.
Doc Brown

4

나는 이것이 완벽 하다고 주장한다 TDD에 사례 .

테스트 할 알려진 기준 세트가 있으며 이러한 경우를 논리적으로 분석합니다. 현재 또는 나중에 단위 테스트를 수행한다고 가정하면 알려진 결과를 취하고 그 주위에 구축하여 실제로 각 규칙을 독립적으로 다루는 것이 좋습니다.

또한 새 검색 규칙을 추가하면 기존 규칙이 깨지는 지 확인할 수 있습니다. 코딩이 끝날 때이 모든 작업을 수행하면 아마도 하나를 수정하여 하나를 수정하고 다른 하나는 중단하고 다른 하나는 중단해야 할 위험이 더 커질 것입니다. 또는 조정이 필요합니다.


1

100 % 테스트 범위를 모든 단일 방법에 대해 사양을 작성하거나 코드의 모든 순열을 테스트하는 것으로 엄격하게 해석하는 팬은 아닙니다. 이것을 광신적으로 수행하는 것은 비즈니스 로직을 제대로 캡슐화하지 않는 테스트 중심의 클래스 설계로 이끄는 경향이 있으며 지원되는 비즈니스 로직을 설명하는 관점에서 일반적으로 의미가없는 테스트 / 사양을 산출합니다. 대신, 비즈니스 규칙과 매우 유사한 테스트를 구성하는 데 중점을두고 테스트를 통해 일반적으로 유스 케이스와 동일하게 테스트를 쉽게 이해할 수 있다는 테스트를 통해 코드의 모든 조건부 분기를 테스트하려고 노력합니다. 구현 된 비즈니스 규칙.

이 아이디어를 염두에두고, 서로 독립적으로 나열한 6 가지 순위 요소를 철저히 단위 테스트하고 결과를 예상되는 전체 순위 값으로 롤업 할 수 있도록 2 또는 3 개의 통합 스타일 테스트를 수행했습니다. 예를 들어, 사례 # 1, 이름과 정확히 일치하는 경우, 정확한 경우와 그렇지 않은 경우 및 두 시나리오가 예상 점수를 반환하는지 테스트하기 위해 최소 두 개의 단위 테스트가 필요합니다. 대소 문자를 구분하면 "정확한 일치"대 "정확한 일치"및 구두점, 추가 공백 등과 같은 다른 입력 변형도 테스트하는 경우도 예상 점수를 반환합니다.

순위 점수에 기여하는 모든 개별 요소를 검토 한 후에는 기본적으로 이러한 요소가 통합 수준에서 올바르게 작동한다고 가정하고 결합 된 요소가 최종 예상 순위 점수에 올바르게 기여하는지 확인하는 데 중점을 둡니다.

사례 # 2 / # 3 및 # 4 / # 5가 동일한 기본 메소드로 일반화되었다고 가정하지만 다른 필드를 전달하는 경우 기본 메소드에 대해 하나의 단위 테스트 세트 만 작성하고 특정 추가 테스트를 작성하기 위해 간단한 추가 단위 테스트 만 작성하면됩니다. 필드 (제목, 이름, 설명 등) 및 지정된 팩터링에서 점수를 매기므로 전체 테스트 노력의 중복성이 더욱 줄어 듭니다.

이 접근 방식을 사용하면 위에서 설명한 접근 방식은 사례 # 1에서 3 또는 4 개의 단위 테스트를 생성 할 수 있으며, 일부 / 모든 동의어에 대해 10 개의 사양이 고려 될 수 있습니다. 최종 날짜 주문 순위에 대한 3 가지 사양, 6 가지 경우 모두 가능한 방식으로 결합 된 3 ~ 4 개의 통합 수준 테스트 (코드에서 확실하게 확인해야하는 문제가 명확하게 표시되지 않는 한 지금은 모호한 경우는 잊어 버리십시오. 해당 조건이 처리됨) 또는 이후 개정에 의해 위반 / 파손 되지 않도록하십시오 . 작성 된 코드의 100 %를 직접 호출하지는 않았지만 작성된 코드의 100 %를 실행하는 약 25 정도의 사양이 생성됩니다.


1

나는 100 % 테스트 범위의 팬이 아니었다. 내 경험상, 하나 또는 두 개의 테스트 케이스로 테스트하기에 충분히 간단한 것이 있다면 거의 실패하지 않을 정도로 간단합니다. 실패하면 일반적으로 테스트 변경이 필요한 아키텍처 변경으로 인한 것입니다.

즉, 당신과 같은 요구 사항에 대해, 나는 아무도 테스트하지 않는 개인 프로젝트에서도 항상 철저한 단위 테스트를 수행합니다. 단위 테스트는 시간과 악화를 줄여주는 경우이기 때문입니다. 무언가를 테스트하는 데 필요한 단위 테스트가 많을수록 단위 테스트가 더 많이 절약됩니다.

한 번에 너무 많은 물건을 머리에 담을 수 있기 때문입니다. 63 개의 다른 조합에서 작동하는 코드를 작성하려고하면 다른 조합을 깨지 않고 한 조합을 수정하기가 어려운 경우가 많습니다. 다른 조합을 계속해서 수동으로 테스트하게됩니다. 수동 테스트는 속도가 훨씬 느리므로 변경할 때마다 가능한 모든 조합을 다시 실행하고 싶지 않습니다. 따라서 무언가를 놓칠 가능성이 높아지고 모든 경우에 효과가없는 길을 찾는 데 시간을 낭비 할 가능성이 높습니다.

수동 테스트와 비교하여 절약되는 시간 외에도 정신적 부담이 훨씬 적으므로 실수로 회귀가 발생하는 것에 대해 걱정하지 않고 당면한 문제에 쉽게 집중할 수 있습니다. 따라서 번 아웃없이 더 빠르고 더 오래 일할 수 있습니다. 제 생각에는 정신 건강상의 이점만으로도 복잡한 코드를 단위 테스트하는 비용은 시간을 절약하지 않아도 가치가 있습니다.

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