TDD 및 리팩토링의 어려움 (또는 왜 이것이 더 고통 스럽습니까?)


20

나는 TDD 접근법을 사용하도록 가르치고 싶었고 한동안 작업하고 싶었던 프로젝트를 가지고있었습니다. 그것은 큰 프로젝트가 아니기 때문에 TDD의 좋은 후보가 될 것이라고 생각했습니다. 그러나 뭔가 잘못되었다고 생각합니다. 예를 들어 보겠습니다.

높은 수준에서 내 프로젝트는 프로젝트를보다 쉽게 ​​추적하고 관리 할 수있는 Microsoft OneNote 용 추가 기능입니다. 또한 언젠가는 나만의 맞춤형 스토리지와 백엔드를 구축하기로 결정한 경우 가능한 한 OneNote와 분리 된 비즈니스 로직을 유지하려고했습니다.

먼저 첫 번째 기능을 수행하기 원하는 기본 개요 단어 수용 테스트로 시작했습니다. 다음과 같이 보입니다 (간결하게 표시).

  1. 사용자 클릭으로 프로젝트 생성
  2. 프로젝트 제목의 사용자 유형
  3. 프로젝트가 올바르게 생성되었는지 확인

UI 관련 내용을 건너 뛰고 중개 계획을 세우면서 첫 번째 단위 테스트를 시작했습니다.

[TestMethod]
public void CreateProject_BasicParameters_ProjectIsValid()
{
    var testController = new Controller();
    Project newProject = testController(A.Dummy<String>());
    Assert.IsNotNull(newProject);
}

여태까지는 그런대로 잘됐다. 빨강, 초록, 리팩터링 등. 이제 실제로 물건을 저장해야합니다. 여기 몇 단계를 잘라 내고 이것으로 마무리합니다.

[TestMethod]
public void CreateProject_BasicParameters_ProjectMatchesExpected()
{
    var fakeDataStore = A.Fake<IDataStore>();
    var testController = new Controller(fakeDataStore);
    String expectedTitle = fixture.Create<String>("Title");
    Project newProject = testController(expectedTitle);

    Assert.AreEqual(expectedTitle, newProject.Title);
}

나는 여전히이 시점에서 기분이 좋다. 아직 구체적인 데이터 저장소가 없지만 예상 한대로 인터페이스를 만들었습니다.

이 게시물이 오래 걸리기 때문에 여기에서 몇 단계를 건너 뛸 것입니다.하지만 비슷한 프로세스를 따르고 결국 데이터 저장소에 대해이 테스트를 수행합니다.

[TestMethod]
public void SaveNewProject_BasicParameters_RequestsNewPage()
{
    /* snip init code */
    testDataStore.SaveNewProject(A.Dummy<IProject>());
    A.CallTo(() => oneNoteInterop.SavePage()).MustHaveHappened();
}

구현하려고 할 때까지 이것은 좋았습니다.

public String SaveNewProject(IProject project)
{
    Page projectPage = oneNoteInterop.CreatePage(...);
}

"..."이있는 곳에 문제가 있습니다. 이 시점에서 CreatePage에 섹션 ID가 필요하다는 것을 알았습니다. 컨트롤러 수준에서 생각할 때 컨트롤러와 관련된 비트 테스트에만 관심이 있었기 때문에 이것을 다시 알지 못했습니다. 그러나 이제는 프로젝트를 저장할 위치를 사용자에게 요청해야한다는 것을 알았습니다. 이제 데이터 저장소에 위치 ID를 추가 한 다음 프로젝트에 하나를 추가 한 다음 컨트롤러에 하나를 추가하고 모든 것을 위해 이미 작성된 모든 테스트에 추가해야합니다. 그것은 매우 지루 해졌고, 도움이 될 수는 없지만 TDD 프로세스 중에 디자인되도록하지 않고 미리 디자인을 스케치하면 더 빨리 잡았을 것입니다.

이 과정에서 내가 잘못한 것을 누군가 설명해 주시겠습니까? 어쨌든 이런 종류의 리팩토링을 피할 수 있습니까? 아니면 이것이 일반적입니까? 일반적인 경우 더 고통없이 만드는 방법이 있습니까?

모두 감사합니다!


이 토론 포럼에 groups.google.com/forum/#!forum/… 주제를 게시하면 매우 통찰력있는 의견을 얻을 수 있습니다 ( TDD 주제에 해당).
Chuck Krutsinger

1
모든 테스트에 무언가를 추가 해야하는 경우 테스트가 제대로 작성되지 않은 것 같습니다. 테스트를 리팩토링하고 현명한 고정구 사용을 고려해야합니다.
Dave Hillier

답변:


19

TDD는 소프트웨어를 디자인하고 성장시키는 방법으로 (올바로) 선전되지만 디자인과 아키텍처를 미리 생각하는 것이 좋습니다. IMO는 "미리 디자인을 내 놓는다"는 공평한 게임이다. 그러나 이것은 종종 TDD를 통해 이끌어 질 디자인 결정보다 높은 수준에있을 것입니다.

또한 상황이 바뀌면 일반적으로 테스트를 업데이트해야한다는 것도 사실입니다. 완전히 제거 할 방법은 없지만 테스트를 덜 취하고 고통을 최소화하기 위해 할 수있는 일이 있습니다.

  1. 가능한 한 구현 세부 사항을 테스트에서 제외하십시오. 이는 공개 방법을 통해서만 테스트 할 수 있으며 가능한 경우 상호 작용 기반 검증보다 상태 기반을 선호 합니다 . 다시 말해서, 당신이 아닌 무언가 의 결과 를 테스트한다면 단계 에 도달 테스트가 덜 취약해야합니다.

  2. 프로덕션 코드에서와 마찬가지로 테스트 코드에서 중복을 최소화하십시오. 이 게시물 은 좋은 참고 자료입니다. 예를 들어, ID여러 가지 다른 테스트에서 생성자를 직접 호출했기 때문에 생성자에 속성을 추가하는 것이 고통스러운 것처럼 들립니다 . 대신, 메소드의 오브젝트 작성을 추출하거나 테스트 초기화 메소드의 각 테스트마다 한 번씩 초기화하십시오.


나는 상태 기반 대 상호 작용 기반의 장점을 읽었으며 대부분 그것을 이해합니다. 그러나 테스트를 위해 속성을 명시 적으로 노출시키지 않고 모든 경우에 어떻게 가능한지 알 수 없습니다. 위의 예를 들어보십시오. "MustHaveBeenCalled"에 대한 어설 션을 사용하지 않고 데이터 스토어가 실제로 호출되었는지 확인하는 방법을 잘 모르겠습니다. 지점 2는 절대적으로 정확합니다. 모든 편집 후에 그 일을 마무리했지만, 내 접근 방식이 일반적으로 허용되는 TDD 사례와 일치하는지 확인하고 싶었습니다. 감사!
Landon

@Landon 상호 작용 테스트가 더 적절한 경우가 있습니다. 예를 들어 데이터베이스 또는 웹 서비스에 대한 호출이 이루어 졌는지 확인합니다. 기본적으로, 특히 외부 서비스에서 테스트를 분리해야 할 때마다.
jhewlett

@Landon I'ma는 "고전적인 고전 주의자"이기 때문에, 나는 Interation-based testing에 익숙하지 않다 ... 그러나 "MustHaveBeenCalled"에 대한 주장을 할 필요는 없다. 삽입을 테스트하는 경우 쿼리를 사용하여 삽입되었는지 확인할 수 있습니다. 추신 : 데이터베이스 계층을 제외한 모든 것을 테스트 할 때 성능 고려 사항으로 인해 스텁을 사용합니다.
Hbas

@jhewlett 그게 내가 도착한 결론입니다. 감사!
Landon

@Hbas 쿼리 할 데이터베이스가 없습니다. 내가 가지고 있다면 가장 쉬운 방법이라고 생각하지만 OneNote 전자 필기장에 추가하고 있습니다. 대신 내가 할 수있는 최선의 방법은 Interop 도우미 클래스에 Get 메서드를 추가하여 페이지를 가져 오는 것입니다. 나는 그것을하기 위해 테스트를 작성해야했지만 한 번에 두 가지를 테스트하고 싶다고 느꼈습니다. 이것을 저장 했습니까? 내 도우미 클래스가 페이지를 올바르게 검색합니까? 비록 어느 시점에서 테스트가 다른 곳에서 테스트 된 다른 코드에 의존해야 할 수도 있습니다. 감사!
Landon

10

... TDD 프로세스 중에 디자인하지 않고 미리 디자인을 스케치하면 더 빨리 잡았을 것 같습니다.

그럴 수도 있고 아닐 수도 있고

한편으로 TDD는 제대로 작동하여 기능을 구축 할 때 자동 테스트를 제공하고 인터페이스를 변경해야 할 때 즉시 중단됩니다.

반면에 하위 레벨 기능 (CreateProject) 대신 상위 레벨 기능 (SaveProject)으로 시작한 경우 누락 된 매개 변수가 더 빨리 나타납니다.

그렇다면 다시는 없을 것입니다. 반복 할 수없는 실험입니다.

그러나 다음에 강의를 찾고 있다면 맨 위에서 시작하십시오. 그리고 원하는만큼 디자인을 생각하십시오.


0

https://frontendmasters.com/courses/angularjs-and-code-testability/ 약 2:22:00에서 끝까지 (약 1 시간) 동영상이 공짜가 아니 어서 죄송하지만 동영상을 잘 설명하는 공짜 동영상을 찾지 못했습니다.

테스트 가능한 코드 작성에 대한 최고의 프레젠테이션 중 하나가이 단원입니다. AngularJS 클래스이지만 테스트 부분은 Java 코드와 관련이 있습니다. 주로 그가 이야기하는 것은 언어와 관련이 없으며 먼저 테스트 가능한 코드를 작성하는 것과 관련이 있기 때문입니다.

마술은 코드 테스트를 작성하는 대신 테스트 가능한 코드를 작성하는 것입니다. 사용자 인 척하는 코드를 작성하는 것이 아닙니다.

또한 테스트 어설 션 형태로 스펙을 작성하는 데 시간을 보냅니다.

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