테스트에서 로직을 피하면서 컬렉션을 반환하는 테스트 방법을 통합하는 방법


14

데이터 객체 컬렉션을 생성하는 방법을 테스트하고 있습니다. 객체의 속성이 올바르게 설정되어 있는지 확인하고 싶습니다. 일부 속성은 동일한 것으로 설정됩니다. 나머지는 컬렉션에서의 위치에 따라 다른 값으로 설정됩니다. 이 작업을 수행하는 자연스러운 방법은 반복되는 것 같습니다. 그러나 Roy Osherove는 단위 테스트에서 로직 사용을 강력히 권장합니다 ( Art of Unit Testing , 178). 그는 말한다 :

논리가 포함 된 테스트는 일반적으로 한 번에 두 가지 이상을 테스트하는 것으로 테스트가 덜 읽기 쉽고 취약하기 때문에 권장되지 않습니다. 그러나 테스트 로직은 숨겨진 버그를 포함 할 수있는 복잡성을 추가합니다.

일반적으로 테스트는 제어 흐름이없고 짝수도 아니고 try-catch어설 션 호출 이있는 일련의 메서드 호출이어야 합니다.

그러나 디자인에 문제가있는 것을 볼 수 없습니다 (데이터 객체 목록을 생성하는 방법, 일부는 값이 시퀀스의 위치에 따라 달라지는가? — 정확히 별도로 생성 및 테스트 할 수 없음). 내 디자인에 테스트에 친화적이지 않은 것이 있습니까? 아니면 Osherove의 가르침에 너무 단단하게 헌신하고 있습니까? 아니면이 문제를 피할 수있는 비밀 단위 테스트 마술이 있습니까? (C # / VS2010 / NUnit으로 작성하고 있지만 가능하면 언어에 구애받지 않는 답변을 찾고 있습니다.)


4
반복하지 않는 것이 좋습니다. 테스트 결과 세 번째 항목의 막대가 Frob로 설정되어 있으면 세 번째 항목의 막대가 Frob인지 구체적으로 테스트하는 테스트를 작성하십시오. 그것은 그 자체로 하나의 테스트입니다. 테스트에서 5 가지 컬렉션을 얻는 것이면 하나의 테스트이기도합니다. 그것은 결코 당신이 루프 (명시 적이거나 다른)를 가지고 있지 않다는 것을 말하는 것이 아니며, 종종 당신이 자주 필요로 하지 않는 것입니다. 또한 Osherove의 책을 실제 규칙보다 더 많은 지침 으로 취급하십시오 .
Anthony Pegram

1
@AnthonyPegram 세트는 순서가 정해지지 않습니다-Frob는 때때로 3 일 수도 있고 2 일 수도 있습니다. in테스트가 "기존 콜렉션에 성공적으로 추가되었습니다"인 경우 루프 (또는 Python과 같은 언어 기능)를 작성하여이를 사용할 수 없습니다 .
이즈 카타

1
@Izbata는 그의 질문에 특히 순서가 중요하다고 언급했다. 그의 말 : "다른 사람들은 컬렉션에서 그들의 위치에 의존하는 값으로 설정 될 것입니다." C #에는 삽입 순서가 지정된 많은 컬렉션 유형이 있습니다. 그 문제에 대해서는 언급 한 언어 인 파이썬으로 된 목록을 사용하여 순서에 의존 할 수도 있습니다.
Anthony Pegram

또한 컬렉션에서 Reset 메서드를 테스트한다고 가정 해 봅시다. 컬렉션을 반복하고 각 항목을 확인해야합니다. 컬렉션의 크기에 따라 루프에서 테스트하지 않는 것은 말도 안됩니다. 또는 컬렉션의 각 항목을 증가시켜야하는 것을 테스트한다고 가정 해 봅시다. 모든 항목을 동일한 값으로 설정하고 증가분을 호출 한 다음 확인할 수 있습니다. 그 시험은 짜증나. 그중 몇 개를 다른 값으로 설정하고, 증가분을 호출하고, 다른 모든 값이 올바르게 증가했는지 확인해야합니다. 컬렉션에서 임의의 항목 하나만 확인하면 많은 가능성이 있습니다.
헤니

나는이 방법으로 대답하지 않을 것입니다. 왜냐하면 gazillion downvotes가 생길 것입니다. 그러나 종종 toString()Collection 만 있고 그것이 있어야하는 것과 비교됩니다. 간단하고 작동합니다.
user949300

답변:


16

TL; DR :

  • 시험 쓰기
  • 테스트가 너무 많이 수행되면 코드가 너무 많이 수행 될 수 있습니다.
  • 단위 테스트가 아닐 수도 있습니다 (그러나 나쁜 테스트는 아닙니다).

테스트를위한 첫 번째는 도그마가 도움이되지 않는 것입니다. 나는 교리와 관련된 몇 가지 문제를 지적하는 The Test of Testivus 를 읽는 것을 즐깁니다 .

작성해야 할 시험을 작성하십시오.

시험을 어떤 방식으로 작성해야한다면 그렇게 작성하십시오. 테스트를 이상적인 테스트 레이아웃으로 만들거나 전혀 갖지 않는 것은 좋지 않습니다. 오늘 테스트를하는 것이 나중에 "완벽한"테스트를하는 것보다 낫습니다.

또한 추한 테스트에서 비트를 가리킬 것입니다.

코드가보기 흉한 경우 테스트가보기 흉할 수 있습니다.

못생긴 테스트를 작성하는 것은 좋지 않지만 못생긴 코드는 가장 많이 테스트해야합니다.

추악한 코드로 테스트 작성을 중단하지 말고 추악한 코드로 테스트를 더 작성하지 못하게하십시오.

이것들은 오랫동안 따라온 사람들에게 진실로 간주 될 수 있습니다 ... 그리고 그들은 생각과 작문 시험 방식에 깊이 뿌리 박습니다. 그 시점에 도달하지 않은 사람들은 미리 알림이 도움이 될 수 있습니다 (그들을 다시 읽는 것은 교리에 빠지지 않도록 도와줍니다).


못생긴 테스트를 작성할 때 코드가 코드가 너무 많은 것을 시도하고 있음을 나타내는 것일 수 있습니다. 테스트중인 코드가 너무 복잡하여 간단한 테스트를 작성하여 제대로 연습 할 수없는 경우 간단한 테스트로 테스트 할 수있는 작은 부분으로 코드를 나누는 것이 좋습니다. 하나는 (그것이하지 않을 수도 있습니다 모든 것을 수행하는 단위 테스트 작성하지 않아야 단위 후 테스트). '신 개체'가 나쁜 것처럼 '신 단위 테스트'도 나쁜 것이므로 코드를 다시 살펴보아야한다는 표시가되어야합니다.

이러한 간단한 테스트를 통해 합리적인 범위의 모든 코드를 연습 할 수 있어야 합니다. 더 큰 질문을 다루는 엔드 투 엔드 테스트를 더 많이 수행하는 테스트 ( "규칙에 따라 웹 서비스로 전송 된 XML로 마샬링 된 규칙을 통해 다시 정렬 및 비 정렬 화됨")는 훌륭한 테스트입니다. '은 T 단위 테스트를 (그리고 통합 테스트 영역에 속하는 -는 테스트를 위해 메모리 데이터베이스에서이 호출하는 서비스 및 사용자 정의를 조롱 경우에도). 테스트에는 여전히 XUnit 프레임 워크를 사용할 수 있지만 테스트 프레임 워크는이를 단위 테스트로 만들지 않습니다.


7

원래의 질문과 답변을 쓴 시점과 관점이 다르기 때문에 새로운 답변을 추가하고 있습니다. 그것들을 하나로 합치는 것은 의미가 없습니다.

나는 원래 질문에서 말했다

그러나 디자인에 문제가있는 것을 볼 수 없습니다 (어떻게 데이터 객체의 목록을 생성합니까? 일부는 값이 시퀀스의 위치에 따라 달라지는가? — 정확히 별도로 생성 및 테스트 할 수 없음)

이것은 내가 잘못한 곳이다. 작년에 함수형 프로그래밍을 수행 한 후에는 누산기로 수집 작업이 필요하다는 것을 알게되었습니다. 그런 다음 함수를 순수한 함수로 작성하고 표준 라이브러리 함수를 사용하여 컬렉션에 적용 할 수 있습니다.

그래서 나의 새로운 대답은 : 함수형 프로그래밍 기술을 사용하면 대부분이 문제를 완전히 피할 수 있습니다. 단일 사물에서 작동하도록 함수를 작성하고 마지막 순간에 사물 모음에만 적용 할 수 있습니다. 그러나 순수한 경우 컬렉션을 참조하지 않고 테스트 할 수 있습니다.

보다 복잡한 논리를 위해서는 속성 기반 테스트 에 의존하십시오 . 그들이 논리를 가지고 있다면, 그것은 테스트중인 코드의 논리보다 작고 역이어야하고, 각 테스트는 소량의 논리가 그만한 가치가 있는지 대소 문자 기반 단위 테스트보다 훨씬 더 많은 것을 검증합니다.

무엇보다도 항상 당신의 타입에 의지하십시오 . 당신이 할 수있는 가장 강한 타입을 얻고 그것을 유리하게 사용하십시오. 이렇게하면 처음에 작성해야하는 테스트 수가 줄어 듭니다.


4

한 번에 너무 많은 것을 테스트하려고하지 마십시오. 컬렉션에있는 각 데이터 개체의 각 속성이 한 번의 테스트에 비해 너무 많습니다. 대신 다음을 권장합니다.

  1. 컬렉션이 고정 길이 인 경우 길이를 확인하기 위해 단위 테스트를 작성하십시오. 가변 길이 인 경우 동작의 특성을 나타내는 길이에 대한 여러 테스트를 작성하십시오 (예 : 0, 1, 3, 10). 어느 쪽이든,이 테스트에서 속성을 확인하지 마십시오.
  2. 각 속성을 확인하기 위해 단위 테스트를 작성하십시오. 컬렉션이 고정 길이이고 짧으면 각 테스트에 대한 각 요소의 속성 하나에 대해 단언하십시오. 길이가 고정되어 있지만 길다면 대표적이지만 소량의 요소 샘플을 선택하여 각각 하나의 특성에 대해 어설 션하십시오. 가변 길이 인 경우 비교적 짧지 만 대표적인 컬렉션 (예 : 세 가지 요소)을 생성하고 각각의 속성 하나에 대해 어설 션합니다.

이 방법을 사용하면 루프를 떠나는 것이 고통스럽지 않을 정도로 테스트가 충분히 작아집니다. 테스트중인 메소드가 제공된 C # / 단위 예 ICollection<Foo> generateFoos(uint numberOfFoos):

[Test]
void generate_zero_foos_returns_empty_list() { ... }
void generate_one_foo_returns_list_of_one_foo() { ... }
void generate_three_foos_returns_list_of_three_foos() { ... }
void generated_foos_have_sequential_ID()
{
    var foos = generateFoos(3).GetEnumerable();
    foos.MoveNext();
    Assert.AreEqual("ID1", foos.Current.id);
    foos.MoveNext();
    Assert.AreEqual("ID2", foos.Current.id);
    foos.MoveNext();
    Assert.AreEqual("ID3", foos.Current.id);
}
void generated_foos_have_bar()
{
    var foos = generateFoos(3).GetEnumerable();
    foos.MoveNext();
    Assert.AreEqual("baz", foos.Current.bar);
    foos.MoveNext();
    Assert.AreEqual("baz", foos.Current.bar);
    foos.MoveNext();
    Assert.AreEqual("baz", foos.Current.bar);
}

"플랫 유닛 테스트"패러다임 (중첩 구조 / 논리 없음)에 익숙하다면 이러한 테스트는 매우 깨끗해 보입니다. 따라서 루프에서 부족하지 않고 한 번에 너무 많은 특성을 테스트하려고 시도하여 원래 문제점을 식별하여 테스트에서 논리를 피할 수 있습니다.


1
Osherove는 3 번의 주장으로 머리를 접시에 담았습니다. ;) 첫 번째 실패는 나머지를 검증하지 않는다는 것을 의미합니다. 또한 루프를 실제로 하지는 않았습니다 . 당신은 그것을 명시 적으로 실행 된 형태로 확장했다. 어려운 비판이 아니라 테스트 사례를 가능한 한 최소한으로 격리하는 연습을 더하고, 실패했을 때 더 구체적인 피드백 을 제공 하면서도 여전히 통과 할 수있는 다른 사례를 계속 확인하면서 실패 할 수 있습니다. 자신의 특정 피드백).
Anthony Pegram

3
@AnthonyPegram 테스트 당 하나의 어설 션 패러다임에 대해 알고 있습니다. 나는 "한 가지 테스트"만트라를 선호한다 (Bob Martin이 주장한 바와 같이 Clean Code의 테스트 당 한 번의 주장에 반대 ). 참고 : '예상'과 '어설 션'이있는 단위 테스트 프레임 워크는 훌륭합니다 (Google 테스트). 나머지 부분은 예를 들어 제안을 전체 답변으로 나누지 않겠습니까? 나는 이익을 얻을 수 있다고 생각합니다.
Kazark
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.