모든 단위 테스트에서 데이터를 하드 코딩해야합니까?


33

대부분의 단위 테스트 학습서 / 예에는 일반적으로 각 개별 테스트에 대해 테스트 할 데이터를 정의하는 것이 포함됩니다. 이것이 "모든 것이 분리되어 테스트되어야한다"는 이론의 일부인 것 같습니다.

그러나 많은 DI 가있는 다중 계층 응용 프로그램을 처리 할 때 각 테스트를 설정하는 데 필요한 코드가 매우 길다 는 것을 알았 습니다. 대신 많은 테스트 기반 클래스를 만들었습니다.이 클래스는 많은 테스트 스캐 폴딩이 사전 빌드되어 상속받을 수 있습니다.

이것의 일부로서, 나는 또한 각각의 "테이블"에 단지 하나 또는 두 개의 행이 있지만 실행중인 응용 프로그램의 데이터베이스를 나타내는 가짜 데이터 세트를 만들고 있습니다.

전부는 아니지만 모든 단위 테스트에서 대부분의 테스트 데이터를 미리 정의하는 것이 허용되는 관행입니까?

최신 정보

아래 의견에서 단위 테스트보다 더 많은 통합을 수행하는 것처럼 느껴집니다.

내 현재 프로젝트는 ASP.NET MVC이며, Entity Framework Code First over Work Unit과 Moq를 사용하여 테스트합니다. 나는 UoW와 저장소를 조롱했지만 실제 비즈니스 로직 클래스를 사용하고 컨트롤러 동작을 테스트하고 있습니다. 테스트는 종종 다음과 같이 UoW가 커밋되었는지 확인합니다.

[TestClass]
public class SetupControllerTests : SetupControllerTestBase {
  [TestMethod]
  public void UserInvite_ExistingUser_DoesntInsertNewUser() {
    // Arrange
    var model = new Mandy.App.Models.Setup.UserInvite() {
      Email = userData.First().Email
    };

    // Act
    setupController.UserInvite(model);

    // Assert
    mockUserSet.Verify(m => m.Add(It.IsAny<UserProfile>()), Times.Never);
    mockUnitOfWork.Verify(m => m.Commit(), Times.Once);
  }
}

SetupControllerTestBasemock UoW를 구축하고을 인스턴스화합니다 userLogic.

많은 테스트에는 데이터베이스에 기존 사용자 나 제품이 있어야하므로이 예제 userData에서는 IList<User>단일 사용자 레코드 만있는 모의 UoW가 반환하는 내용을 미리 채웠 습니다.


4
학습서 / 예의 문제점은 단순해야한다는 것입니다. 그러나 간단한 예에서 복잡한 문제점에 대한 솔루션을 표시 할 수는 없습니다. 적절한 크기의 실제 프로젝트에서이 도구를 사용하는 방법을 설명하는 "사례 연구"를 수반해야하지만 거의 그렇지 않습니다.
Jan Hudec

아마도 당신이 전혀 좋아하지 않는 작은 코드 예제를 추가 할 수 있습니다.
Luc Franken

테스트를 실행하기 위해 많은 설정 코드 가 필요한 경우 기능 테스트를 실행할 위험이 있습니다. 코드를 변경할 때 테스트가 실패하지만 코드에 아무런 문제가없는 경우 확실히 기능 테스트입니다.
Reactgular

"xUnit Test Patterns"책은 재사용 가능한 조명기 및 도우미를 강력하게 지원합니다. 테스트 코드는 다른 코드와 마찬가지로 유지 관리가 가능해야합니다.
Chuck Krutsinger

이 기사는 도움이 될 것입니다 : yegor256.com/2015/05/25/unit-test-scaffolding.html
yegor256

답변:


25

궁극적으로 가능한 한 많은 코드를 작성하여 가능한 많은 결과를 얻으려고합니다. 여러 테스트에서 동일한 코드를 많이 보유하면 a) 복사 붙여 넣기 코딩이 발생하는 경향이 있으며 b) 분석법 서명이 변경되면 많은 깨진 테스트를 수정해야 할 수 있습니다.

나는 일상적으로 사용하는 많은 데이터 유형을 제공하는 표준 TestHelper 클래스를 사용하는 접근 방식을 사용하므로 테스트를 위해 표준 엔터티 또는 DTO 클래스 세트를 작성하여 매번 얻을 수있는 것을 정확하게 알 수 있습니다. 따라서 TestHelper.GetFooRange( 0, 100 )모든 종속 클래스 / 필드가 설정된 100 Foo 객체의 범위를 얻으려면 호출 할 수 있습니다 .

특히 ORM 유형 시스템에 복잡한 관계가 구성되어 있으며 올바르게 실행하기 위해 있어야하지만 많은 시간을 절약 할 수있는 이 테스트 에서는 반드시 중요한 것은 아닙니다 .

데이터 수준에 가까운 테스트를 수행하는 상황에서 비슷한 방식으로 쿼리 할 수있는 리포지토리 클래스의 테스트 버전을 만드는 경우가 있습니다 (이는 ORM 유형 환경에 있으며, 실제 데이터베이스), 쿼리에 대한 정확한 응답을 조롱하는 것은 많은 작업이며 종종 작은 이점 만 제공하기 때문입니다.

단위 테스트에서는주의해야 할 사항이 있습니다.

  • 당신의 모의 모의 인지 확인하십시오 . 단위 테스트를 수행하는 경우 테스트중인 클래스 주위에서 작업을 수행하는 클래스는 모의 객체 여야합니다 . DTO / 엔티티 유형 클래스는 실제 일 수 있지만 클래스가 작업을 수행하는 경우이를 조롱해야합니다. 그렇지 않으면 지원 코드가 변경되고 테스트가 실패하면 변경 내용을 파악하기 위해 더 오래 검색해야합니다. 실제로 문제를 일으켰습니다.
  • 수업을 테스트하고 있는지 확인하십시오 . 때로는 단위 테스트 모음을 살펴보면 테스트의 절반이 실제로 테스트 해야하는 실제 코드보다 모의 프레임 워크를 테스트하고 있음이 분명해집니다.
  • 모의 /지지 객체를 재사용하지 마십시오. 코드 지원 단위 테스트를 통해 영리한 작업을 시작하려고 할 때 테스트간에 지속되는 객체를 실수로 생성하기가 매우 어렵 기 때문에 예기치 않은 결과가 발생할 수 있습니다. 예를 들어, 어제 자체적으로 실행될 때 통과하고 클래스의 모든 테스트가 실행될 때 통과했지만 전체 테스트 스위트가 실행될 때 실패한 테스트가있었습니다. 테스트 도우미에서 비열한 정적 객체 방법이 있었는데, 내가 만들 때 분명히 문제가 발생하지 않았을입니다. 그냥 기억 : 시험의 시작에서, 모든 것이 테스트의 끝에서 만든 모든 것을 파괴된다.

10

테스트 의도를 더 읽기 쉽게 만드는 것

경험상 일반적으로 :

데이터가 테스트의 일부인 경우 (예 : 상태가 7 인 행을 인쇄해서는 안 됨) 테스트에서 코드를 작성하여 저자가 의도 한 바를 명확하게합니다.

데이터가 처리하기에 충분한 지 확인하기 위해 필러 인 경우 (예 : 처리 서비스에서 예외가 발생하는 경우 레코드를 완료된 것으로 표시해서는 안 됨) 항상 BuildDummyData 메소드 또는 관련없는 테스트 클래스가 있어야합니다. 데이터를 .

그러나 나는 후자의 좋은 예를 생각하기 위해 고심하고 있습니다. 단위 테스트 픽스처에 많은 것들이 있다면, 해결해야 할 다른 문제가있을 것입니다 ... 아마 테스트중인 방법이 너무 복잡 할 수 있습니다.


+1 동의합니다. 이것은 그가 테스트하는 것이 단위 테스트를 위해 밀접하게 결합 된 것과 같습니다.
Reactgular

5

다른 테스트 방법

먼저 수행중인 작업을 정의하십시오. 단위 테스트 또는 통합 테스트 . 하나의 클래스 만 테스트하므로 레이어 수는 단위 테스트와 관련이 없습니다. 나머지는 당신이 조롱합니다. 통합 테스트의 경우 여러 계층을 테스트해야합니다. 적절한 단위 테스트가있는 경우 통합 테스트를 너무 복잡하지 않게하는 것이 요령입니다.

단위 테스트가 양호하면 통합 테스트를 수행 할 때 모든 세부 사항 테스트를 반복 할 필요가 없습니다.

우리가 사용하는 용어는 비트 플랫폼에 따라 다르지만 거의 모든 테스트 / 개발 플랫폼에서 찾을 수 있습니다.

적용 예

사용하는 기술에 따라 이름이 다를 수 있지만이를 예로 사용하겠습니다.

Product, ProductsController 모델과 제품이 포함 된 HTML 테이블을 생성하는 인덱스 뷰가있는 간단한 CRUD 애플리케이션이있는 경우 :

응용 프로그램의 최종 결과는 활성화 된 모든 제품 목록이있는 HTML 테이블을 표시합니다.

단위 테스트

모델

매우 쉽게 테스트 할 수있는 모델입니다. 다른 방법이 있습니다. 우리는 비품을 사용합니다. 이것이 바로 "가짜 데이터 세트"라고 생각합니다. 따라서 각 테스트를 실행하기 전에 테이블을 만들고 원래 데이터를 넣습니다. 대부분의 플랫폼에는이를위한 방법이 있습니다. 예를 들어, 테스트 클래스에서 각 테스트 전에 실행되는 setUp () 메소드.

그런 다음 testGetAllActive 제품 과 같은 테스트를 실행 합니다.

따라서 테스트 데이터베이스로 직접 테스트합니다. 우리는 데이터 소스를 조롱하지 않습니다. 우리는 항상 동일하게 만듭니다. 이를 통해 예를 들어 데이터베이스의 새 버전으로 테스트 할 수 있으며 쿼리 문제가 발생합니다.

현실 세계에서 항상 100 % 단일 책임을지는 것은 아닙니다 . 이 작업을 더 잘하려면 모의 데이터 소스를 사용할 수 있습니다. 기존 기술을 테스트하는 것과 같은 느낌을주는 우리 (ORM 사용)를 위해. 또한 테스트는 훨씬 더 복잡해지고 실제로 쿼리를 테스트하지는 않습니다. 그래서 우리는 이것을 이렇게 유지합니다.

하드 코딩 된 데이터는 조명기에 별도로 저장됩니다. 따라서 조명기는 create table 문을 사용하고 사용하는 레코드를 삽입하는 SQL 파일과 같습니다. 많은 레코드로 테스트해야 할 필요가없는 한 작은 크기로 유지합니다.

class ProductModel {
  public function getAllActive() {
    return $this->find('all', array('conditions' => array('active' => 1)));
  }
}

제어 장치

컨트롤러는 모델을 테스트하지 않기 때문에 더 많은 작업이 필요합니다. 우리가하는 일은 모델을 조롱하는 것입니다. 의미 : 우리는 테스트 : 레코드 목록을 반환 해야하는 index () 메소드.

따라서 모델 메소드 getAllActive ()를 조롱하고 고정 된 데이터를 추가합니다 (예 : 두 개의 레코드). 이제 컨트롤러가 뷰로 보내는 데이터를 테스트하고이 두 레코드를 실제로 다시 가져 오는지 비교합니다.

function testProductIndexLoggedIn() {
  $this->setLoggedIn();
  $this->ProductsController->mock('ProductModel', 'index', function(return array(your records) ));
  $result=$this->ProductsController->index();
  $this->assertEquals(2, count($result['products']));
}

충분 해. 테스트를 어렵게 만들기 때문에 컨트롤러에 기능을 거의 추가하지 않습니다. 그러나 물론 코드가 항상 있습니다. 예를 들어, 다음과 같은 요구 사항을 테스트합니다. 로그인 한 경우에만이 두 레코드를 표시하십시오.

따라서 컨트롤러에는 일반적으로 하나의 모의와 하드 코딩 된 작은 데이터가 필요합니다. 로그인 시스템의 경우 다른 시스템 일 수 있습니다. 테스트에는 setLoggedIn () 도우미 메소드가 있습니다. 로그인이나 로그인없이 간단하게 테스트 할 수 있습니다.

class ProductsController {
  public function index() {
    if($this->loggedIn()) {
      $this->set('products', $this->ProductModel->getAllActive());
    }
  }
}

조회수

뷰 테스트는 어렵다. 먼저 반복되는 논리를 분리합니다. 우리는 그것을 도우미에 넣고 엄격한 수업을 테스트합니다. 우리는 항상 같은 결과를 기대합니다. 예를 들어 generateHtmlTableFromArray ()입니다.

그런 다음 프로젝트 별 뷰가 있습니다. 우리는 그것들을 테스트하지 않습니다. 그것들을 단위 테스트하는 것은 실제로 바람직하지 않습니다. 통합 테스트를 위해 보관합니다. 많은 코드를 뷰로 가져 왔기 때문에 여기에서 위험이 줄어 듭니다.

테스트를 시작하면 대부분의 프로젝트에 유용하지 않은 HTML을 변경할 때마다 테스트를 변경해야합니다.

echo $this->tableHelper->generateHtmlTableFromArray($products);

통합 테스트

여기 플랫폼에 따라 사용자 스토리 등으로 작업 할 수 있습니다. Selenium 과 같은 웹 기반 일 수 있습니다. . 또는 기타 유사한 솔루션 .

일반적으로 비품과 함께 데이터베이스를로드하고 어떤 데이터를 사용할 수 있는지 확인합니다. 완전한 통합 테스트를 위해 일반적으로 매우 전 세계적인 요구 사항을 사용합니다. 따라서 : 제품을 활성으로 설정 한 다음 제품을 사용할 수 있는지 확인하십시오.

올바른 필드를 사용할 수 있는지 여부와 같이 모든 것을 다시 테스트하지는 않습니다. 여기서는 더 큰 요구 사항을 테스트합니다. 컨트롤러 나 뷰에서 테스트를 복제하고 싶지 않기 때문에. 응용 프로그램의 핵심 부분이거나 핵심적인 부분이거나 보안상의 이유로 (암호를 확인할 수 없음) 해당 항목이 올바른지 확인하기 위해 추가합니다.

하드 코딩 된 데이터는 조명기에 저장됩니다.

function testIntegrationProductIndexLoggedIn() {
  $this->setLoggedIn();
  $result=$this->request('products/index');

  $expected='<table';
  $this->assertContains($expected, $result);

  // Some content from the fixture record
  $expected='<td>Product 1 name</td>';
  $this->assertContains($expected, $result);
}

이것은 완전히 다른 질문에 대한 훌륭한 답변입니다.
pdr

피드백 감사드립니다. 내가 너무 구체적으로 언급하지 않았을 수도 있습니다. 자세한 답변의 이유는 질문에서 테스트 할 때 가장 어려운 것들 중 하나를 볼 수 있기 때문입니다. 격리 테스트는 다양한 종류의 테스트에 어떻게 적용되는지에 대한 개요입니다. 그렇기 때문에 모든 부분에서 데이터 처리 방법을 추가했습니다. 내가 더 명확하게 얻을 수 있는지 살펴볼 것입니다.
Luc Franken

모든 종류의 다른 클래스를 호출하지 않고 테스트하는 방법을 설명하기 위해 일부 코드 예제로 답변이 업데이트되었습니다.
Luc Franken

4

"실제"데이터 소스를 사용하기까지 많은 DI 및 배선과 관련된 테스트를 작성하는 경우 일반 단위 테스트 영역을 벗어나 통합 테스트 영역에 들어갔을 수 있습니다.

통합 테스트의 경우 일반적인 데이터 설정 논리를 갖는 것이 나쁘지 않다고 생각합니다. 이러한 테스트의 주요 목표는 모든 것이 올바르게 구성되었는지 증명하는 것입니다. 이것은 시스템을 통해 전송 된 구체적인 데이터와는 무관합니다.

반면에 단위 테스트의 경우 테스트 클래스의 대상을 단일 "실제"클래스로 유지하고 다른 모든 것을 조롱하는 것이 좋습니다. 그런 다음 테스트 데이터를 하드 코딩하여 가능한 많은 특수 / 이전 버그 경로를 다룰 수 있도록해야합니다.

테스트에 세미 하드 코딩 / 랜덤 요소를 추가하기 위해 임의 모델 팩토리를 소개합니다. 내 모델의 인스턴스를 사용하는 테스트에서 이러한 팩토리를 사용하여 유효하지만 완전히 임의의 모델 객체를 만든 다음 현재 테스트에 관심있는 속성 만 하드 코딩합니다. 이 방법을 사용하면 테스트에서 모든 관련 데이터를 직접 지정하는 동시에 다른 모델 필드에 의도하지 않은 종속성이없는 모든 관련 데이터 및 (어느 정도) 테스트도 지정할 필요가 없습니다.


-1

테스트를 위해 대부분의 데이터를 하드 코딩하는 것이 일반적이라고 생각합니다.

특정 데이터 세트로 인해 버그가 발생하는 간단한 상황을 고려하십시오. 특정 데이터에 대한 단위 테스트를 작성하여 수정 프로그램을 실행하고 버그가 다시 발생하지 않도록 할 수 있습니다. 시간이 지남에 따라 테스트에는 여러 테스트 사례를 다루는 일련의 데이터가 있습니다.

미리 정의 된 테스트 데이터를 사용하면 광범위하고 알려진 상황을 포괄하는 데이터 세트를 작성할 수 있습니다.

즉, 테스트에 임의의 데이터가 있으면 가치가 있다고 생각합니다.


제목뿐만 아니라 질문을 실제로 읽었습니까?
Jakob

테스트에서 임의의 데이터를 얻는 것에 대한 가치 -예, 매주 한 번 실패한 테스트에서 발생한 일을 파악하는 것과는 전혀 다른 이유가 없습니다.
pdr

헤이즈 / 퍼징 / 입력 테스트를위한 테스트에 임의의 데이터를 갖는 것이 가치가 있습니다. 그러나 단위 테스트에서는 그렇지 않습니다.
glenatron
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.