단위 테스트는 어떻게 설계를 용이하게합니까?


43

우리 동료는 실제로 단위 디자인 테스트를 장려하여 실제로 디자인을 개선하고 리팩토링하는 데 도움을 주지만 방법을 알지 못합니다. CSV 파일을로드하고 파싱하는 경우 단위 테스트 (필드의 값 확인)가 디자인을 확인하는 데 어떻게 도움이됩니까? 그는 커플 링 및 모듈성 등을 언급했지만 나에게는별로 이해가되지 않지만 이론적 인 배경은별로 없습니다.

그것은 당신이 중복으로 표시 한 질문과 같지 않습니다. 나는 이것이 "도움이된다"고 말하는 이론뿐만 아니라 이것이 어떻게 도움이되는 실제 사례에 관심이있을 것입니다. 아래 답변과 의견이 마음에 들지만 더 자세히 알고 싶습니다.


7
TDD
gnat

3
아래 답변은 당신이 알아야 할 전부입니다. 하루 종일 집계 루트 의존성 주입 공장 팩토리 팩토리를 작성하는 사람들 옆에 앉아있는 것은 올바르게 작동하고 검증하기 쉽고 이미 문서화 된 단위 테스트에 대해 간단한 코드를 조용히 작성하는 사람입니다.
Robert Harvey

4
@gnat은 단위 테스트를하는 것이 TDD를 자동으로 암시하지는 않습니다. 다른 질문입니다
Joppe

11
"단위 테스트 (필드의 값 확인)" -입력 유효성 검사와 단위 테스트를 병합하는 것으로 보입니다.
jonrsharpe

1
@jonrsharpe CSV 파일을 구문 분석하는 코드이므로 특정 CSV 문자열이 예상되는 출력을 제공하는지 확인하는 실제 단위 테스트에 대해 이야기하고있을 수 있습니다.
JollyJoker

답변:


3

단위 테스트는 설계를 용이하게 할뿐만 아니라 주요 이점 중 하나입니다.

테스트 우선 작성은 모듈 성과 깨끗한 코드 구조를 이끌어냅니다.

코드를 테스트 우선 작성하면 주어진 코드 단위의 "조건"이 코드에서 가정 할 때 종속성 (일반적으로 모의 또는 스텁을 통해)으로 자연스럽게 전달됩니다.

"조건 x 부여, 행동 y 기대"는 종종 공급에 대한 스텁 x( 현재 테스트에서 현재 구성 요소의 동작을 검증해야하는 시나리오 임) y이되고 모의자가됩니다. 테스트의 종료 ( "반환해야한다"가 아닌 한 y, 테스트는 리턴 값을 명시 적으로 검증합니다).

그런 다음이 장치가 지정된대로 동작 하면 발견 한 종속성 (for xy) 을 작성하는 단계로 넘어갑니다 .

이를 통해 깨끗하고 모듈화 된 코드를 작성하는 것은 매우 쉽고 자연스러운 과정입니다. 그렇지 않은 경우 종종 책임을 흐리게하고 행동을 함께 이해하기가 쉽지 않습니다.

나중에 테스트를 작성하면 코드가 제대로 구성되지 않은시기를 알 수 있습니다.

스텁이나 조롱 할 항목이 너무 많거나 너무 밀접하게 결합되어 코드 조각에 대한 테스트를 작성하는 것이 어려워지면 코드에서 개선해야 할 사항이 있다는 것을 알게됩니다.

단일 단위에 너무 많은 동작이 있기 때문에 "테스트 변경"이 부담이되는 경우 코드에서 개선해야한다는 것을 알 수 있습니다. .

더 추상화해야하기 때문에 시나리오가 너무 복잡해지면 ( "if xand yand z......") 코드를 개선해야합니다.

중복과 중복으로 인해 두 개의 다른 조명기에서 동일한 테스트를 끝내면 코드를 개선해야합니다.

다음은 코드에서 테스트 가능성과 디자인 간의 밀접한 관계를 보여주는 Michael Feathers의 훌륭한 강연입니다 (원래 주석에 displayName으로 게시 됨). 또한 좋은 디자인과 테스트 가능성에 대한 일반적인 불만과 오해도 다루고 있습니다.


@SSE 커뮤니티 : 오늘 기준으로 단 2 개의 공감대 만 있으면이 답변을 간과하기 쉽습니다. 이 답변에 연결된 Michael Feathers의 토크를 적극 권장합니다.
displayName

103

단위 테스트의 장점은 다른 프로그래머가 코드를 사용하는 방식으로 코드를 사용할 수 있다는 것입니다.

코드가 단위 테스트에 어색하면 사용하기 어려울 것입니다. 후프를 뛰어 넘지 않고 의존성을 주입 할 수 없다면 코드를 사용하기가 어려울 것입니다. 그리고 데이터를 설정하거나 작업 순서를 파악하는 데 많은 시간을 소비해야하는 경우 테스트중인 코드가 너무 많이 연결되어있어 어려움을 겪을 수 있습니다.


7
좋은 대답입니다. 저는 항상 테스트를 코드의 첫 번째 클라이언트로 생각하고 싶습니다. 테스트를 작성하는 것이 고통 스러우면 API를 소비하는 코드 또는 내가 개발중인 것을 작성하는 것이 고통 스러울 것입니다.
Stephen Byrne

41
내 경험상 대부분의 단위 테스트 "다른 프로그래머가 코드를 사용하는 방식으로 코드를 사용 하지 않습니다 ". 로 그들은 당신의 코드를 사용하여 단위 테스트 코드를 사용합니다. 사실, 그들은 많은 심각한 결함을 드러 낼 것입니다. 그러나 단위 테스트 용으로 설계된 API는 일반적인 용도에 가장 적합한 API가 아닐 수 있습니다. 단순하게 작성된 단위 테스트는 종종 너무 많은 내부를 노출시키기 위해 기본 코드가 필요합니다. 다시 한 번, 내 경험을 바탕으로-어떻게 처리했는지 듣는 데 관심이 있습니다. (아래 내 답변 참조)
user949300

7
@ user949300-나는 테스트를 먼저 믿는 사람이 아닙니다. 내 대답은 먼저 코드 아이디어 (그리고 확실히 디자인)를 기반으로합니다. API는 단위 테스트 용으로 설계해서는 안되며 고객 용으로 설계되어야합니다. 단위 테스트는 고객을 근사화하는 데 도움이되지만 도구입니다. 그들은 당신에게 봉사하기 위해 거기에 있습니다. 그리고 그들은 당신이 엉터리 코드를 만드는 것을 막지 않을 것입니다.
Telastyn

3
내 경험에서 단위 테스트의 가장 큰 문제는 좋은 것을 작성하는 것이 처음에 좋은 코드를 작성하는 것만 큼 어렵다는 것입니다. 나쁜 코드에서 좋은 코드를 알 수 없다면 단위 테스트를 작성해도 코드가 더 나아지지는 않을 것입니다. 단위 테스트를 작성할 때 부드럽고 유쾌한 사용법과 "어색한"또는 어려운 것을 구별 할 수 있어야합니다. 코드를 약간 사용하게 만들 수도 있지만, 수행중인 작업이 잘못되었음을 인식하도록 강요하지는 않습니다.
jpmc26

2
@ user949300-여기서 염두에 둔 고전적인 예는 connString이 필요한 저장소입니다. 공개 쓰기 가능 속성으로 공개하고 리포지토리를 new () 후에 설정해야한다고 가정하십시오. 아이디어는 5-6 번째 시간이 지나면 해당 단계를 잊어 버린 테스트를 작성하여 충돌을 일으켜 connString을 클래스 불변 인으로 만드는 경향이 "자연스럽게"기울어 질 것입니다. API를 개선 하고이 트랩을 피하는 프로덕션 코드를 작성할 수있는 가능성을 높입니다 . 보장 할 수는 없지만 도움이됩니다.
Stephen Byrne

31

테스트하는 데 상당한 시간이 걸렸지 만 테스트 중심 개발 ( 단위 테스트 사용 )의 실제 이점 (편집 : 나에게 당신의 마일리지는 다를 수 있음)은 API 디자인먼저해야한다는 것입니다 !

개발에 대한 일반적인 접근 방식은 주어진 문제를 해결하는 방법을 먼저 파악하고 해당 지식과 초기 구현 설계를 통해 솔루션을 호출 할 수있는 방법을 찾는 것입니다. 이것은 다소 흥미로운 결과를 줄 수 있습니다.

TDD를 수행 할 때는 솔루션 을 사용할 코드를 가장 먼저 작성 해야합니다. 입력 매개 변수 및 예상 출력을 통해 올바른지 확인할 수 있습니다. 결과적으로 실제로 필요한 작업을 파악해야하므로 의미있는 테스트를 만들 수 있습니다. 그런 다음에 만 솔루션을 구현합니까? 또한 코드가 달성 해야하는 것을 정확히 알면 더 명확 해집니다.

그런 다음 구현 단위 테스트를 수행하면 리팩토링이 기능을 중단하지 않는지 확인하고 코드 사용 방법에 대한 문서를 제공 할 수 있습니다 (테스트가 통과 한대로 알 수 있습니다). 그러나 이것들은 부차적입니다. 가장 큰 이점은 처음에 코드를 만들 때의 사고 방식입니다.


그것은 확실히 이점이지만 이것이 "실제"이점이라고 생각하지 않습니다. 실제 이점은 코드에 대한 테스트 작성이 자연스럽게 "조건"을 종속성으로 푸시하고 종속성의 과도한 주입을 제거한다는 사실에서 비롯됩니다 (추상화를 더욱 촉진) ) 시작하기 전에.
앤트 P

문제는 해당 API와 일치하는 전체 테스트 세트를 미리 작성하면 필요에 따라 정확하게 작동하지 않으므로 코드와 모든 테스트를 다시 작성해야한다는 것입니다. 공개 API의 경우 변경되지 않을 가능성이 높으며이 방법은 좋습니다. 그러나 내부적으로 만 사용되는 코드의 API는 많은 세미 프라이빗 API가 함께 작동해야하는 기능을 구현하는 방법을 알아 내면 많이 변합니다.
Juan Mendes

@AntP 예, 이것은 API 디자인의 일부입니다.
Thorbjørn Ravn Andersen 님이

@JuanMendes 이것은 드문 일이 아니므로 요구 사항을 변경할 때 다른 코드와 마찬가지로 테스트를 변경해야합니다. 좋은 IDE는 메소드 서명 등을 변경할 때 자동으로 수행되는 작업의 일부로 클래스를 리팩토링하는 데 도움이됩니다.
Thorbjørn Ravn Andersen

@ Juan 좋은 테스트와 작은 단위를 작성한다면 실제로 묘사하는 효과의 영향은 적지 않습니다.
Ant P

6

단위 테스트가 "디자인을 개선하고 리팩토링하는 데 도움이된다"고 100 % 동의합니다.

나는 그들이 당신이 초기 디자인 을하는데 도움이되는지 두 가지 생각을하고 있습니다 . 그렇습니다. 명백한 결함을 드러내고 "어떻게 코드를 테스트 할 수 있습니까"에 대해 생각하도록 강요합니까? 이로 인해 부작용이 줄어들고 구성 및 설정이 쉬워집니다.

그러나 내 경험상, 디자인이 무엇인지 이해하기 전에 작성된 지나치게 단순한 단위 테스트 (하드 코어 TDD의 과장이지만 코더가 너무 많이 생각하기 전에 테스트를 작성하는 경우가 많음) 종종 빈혈로 이어집니다. 내부가 너무 많은 도메인 모델.

TDD에 대한 나의 경험은 몇 년 전 이었으므로 기본 디자인을 지나치게 편향시키지 않는 테스트 작성에 어떤 최신 기술이 도움이 될 수 있는지 듣고 싶습니다. 감사.


많은 메소드 매개 변수는 코드 냄새와 디자인 결함입니다.
Sufian

5

단위 테스트를 통해 기능 간의 인터페이스 작동 방식을 확인할 수 있으며 로컬 설계와 전체 설계를 개선하는 방법에 대한 통찰력을 제공합니다. 또한 코드를 개발하는 동안 단위 테스트를 개발하면 준비된 회귀 테스트 스위트가 있습니다. UI 또는 백엔드 라이브러리를 개발하는 것은 중요하지 않습니다.

프로그램이 개발되면 (단위 테스트) 버그가 발견되면 테스트를 추가하여 버그가 수정되었는지 확인할 수 있습니다.

일부 프로젝트에 TDD를 사용합니다. 교과서 또는 올바른 것으로 간주되는 논문에서 가져온 예제를 작성하는 데 많은 노력을 기울이고이 예제를 사용하여 개발중인 코드를 테스트합니다. 그 방법에 관한 오해는 매우 분명해집니다.

코드가 먼저 작성되거나 테스트가 먼저 작성되는지는 신경 쓰지 않으므로 일부 동료보다 약간 느슨합니다.


그것은 저에게 큰 대답입니다. 각 사례마다 하나씩 (예 : 디자인에 대한 통찰력이있는 경우) 몇 가지 예를 들어 주시겠습니까?
039402

5

파서 감지 값을 올바르게 구분하여 단위 테스트하려는 경우 CSV 파일에서 한 줄로 전달할 수 있습니다. 테스트를 직접적이고 짧게 만들려면 한 줄을 허용하는 한 가지 방법으로 테스트 할 수 있습니다.

그러면 줄 읽기와 개별 값 읽기가 자동으로 분리됩니다.

다른 수준에서는 테스트 프로젝트에 모든 종류의 물리적 CSV 파일을 배치하고 싶지 않지만 가독성과 테스트 의도를 향상시키기 위해 테스트 내부에 큰 CSV 문자열을 선언하기 만하면 더 읽기 쉬운 작업을 수행 할 수 있습니다. 이렇게하면 다른 곳에서 수행하는 모든 I / O에서 파서를 분리 할 수 ​​있습니다.

기본적인 예를 들어 연습을 시작하면 어느 시점에서 마술을 느낄 것입니다.


4

간단히 말해, 단위 테스트를 작성하면 코드에 결함이 노출 될 수 있습니다.

Jonathan Wolter, Russ Ruffer 및 Miško Hevery가 작성한 테스트 가능한 코드 작성에 대한 이 훌륭한 안내서 에는 테스트를 방해하고 동일한 코드의 재사용 및 유연성을 방지하는 코드의 결함에 대한 수많은 예가 들어 있습니다. 따라서 코드를 테스트 할 수 있으면 사용하기가 더 쉽습니다. "도덕"의 대부분은 코드 디자인을 크게 개선하는 엄청나게 간단한 팁입니다 ( Dependency Injection FTW).

예를 들어 : 캐시가 물건을 제거하기 시작할 때 computeStuff 메소드가 올바르게 작동하는지 테스트하기는 매우 어렵습니다. "bigCache"가 거의 가득 찰 때까지 캐시에 크랩을 수동으로 추가해야하기 때문입니다.

public OopsIHardcoded {

   Cache cacheOfExpensiveComputations;

   OopsIHardcoded() {
       this.cacheOfExpensiveComputation = buildBigCache();
   }

   ExpensiveValue computeStuff() {
      //DOES THIS WORK CORRECTLY WHEN CACHE EVICTS DATA?
   }
}

그러나 의존성 주입을 사용할 때 캐시가 물건을 제거하기 시작할 때 computeStuff 메소드가 올바르게 작동하는지 테스트하는 것이 훨씬 쉽습니다. 우리가하는 일은 new HereIUseDI(buildSmallCache()); 통지 (Notice)라고 부르는 곳에서 테스트를 만드는 것 입니다. 객체를보다 미묘하게 제어하고 배당금을 즉시 지불합니다.

public HereIUseDI {

   Cache cacheOfExpensiveComputations;

   HereIUseDI(Cache cache) {
       this.cacheOfExpensiveComputation = cache;
   }

   ExpensiveValue computeStuff() {
      //DOES THIS WORK CORRECTLY WHEN CACHE EVICTS DATA?
   }
}

코드에 일반적으로 데이터베이스에 보관 된 데이터가 필요한 경우에도 유사한 이점을 얻을 수 있습니다. 필요한 데이터를 정확히 전달하십시오.


2
솔직히, 나는 당신이 모범을 어떻게 의미하는지 잘 모르겠습니다. computeStuff 메소드는 캐시와 어떤 관련이 있습니까?
John V

1
@ user970696-예, "computeStuff ()"가 캐시를 사용한다는 것을 의미합니다. 문제는 "computeStuff ()가 항상 올바르게 작동합니까 (캐시 상태에 따라 다름)"따라서 직접 설정할 수없는 경우 computeStuff ()가 모든 가능한 캐시 상태에 대해 원하는 것을 수행하는지 확인하기가 어렵습니다. "cacheOfExpensiveComputation = buildBigCache ();"라인을 하드 코드했기 때문에 캐시를 빌드하십시오. (생성자를 통해 캐시에 직접 전달하는 것과 대조적으로)
Ivan

0

'Unit Tests'의 의미에 따라, 실제로는 저수준 Unit 테스트가 약간 더 높은 수준의 통합 테스트 만큼 좋은 디자인을 촉진 한다고 생각하지 않습니다 .-테스트 그룹의 액터 (클래스, 함수 등)를 테스트합니다. 코드는 적절하게 결합되어 개발 팀과 제품 소유자간에 합의 된 바람직한 동작을 제공합니다.

이러한 수준에서 테스트를 작성할 수 있다면, 많은 의존성을 필요로하지 않는, 논리적이고 API와 비슷한 코드를 작성해야합니다. 간단한 테스트 설정을 원하면 자연스럽게 많은 것을 갖지 못하게됩니다. 미친 의존성 또는 밀접하게 연결된 코드.

실수하지 마십시오- 단위 테스트로 인해 나쁜 디자인과 좋은 디자인으로 이어질 수 있습니다. 개발자가 이미 훌륭한 논리적 설계와 단일 관심사를 가진 약간의 코드를 가져 와서 테스트 목적으로 순수하게 더 많은 인터페이스를 도입하여 코드를 읽기 어렵고 변경하기가 더 어려워졌습니다. 개발자가 낮은 수준의 단위 테스트를 많이한다는 것이 더 높은 수준의 테스트를 할 필요가 없다고 결정한 경우 더 많은 버그가있을 수 있습니다. 가장 좋아하는 예는 클립 보드의 정보를 가져 오는 것과 관련된 매우 세분화 된 '테스트 가능한'코드가 많이있는 버그를 수정 한 것입니다. 모든 인터페이스, 테스트의 많은 모의 및 기타 재미있는 것들과 함께 매우 작은 세부 수준으로 분리 및 분리되었습니다. 단 하나의 문제-실제로 OS의 클립 보드 메커니즘과 상호 작용하는 코드는 없었습니다.

단위 테스트는 확실히 디자인을 이끌어 낼 수 있지만 자동으로 좋은 디자인으로 안내하지는 않습니다. '이 코드는 테스트되었으므로 테스트 할 수 있으므로 좋은 것'을 넘어서는 훌륭한 디자인에 대한 아이디어가 필요합니다.

물론 당신이 '단위 테스트'가 'UI를 통해 구동되지 않는 자동 테스트'를 의미하는 사람들 중 하나라면, 그 경고 중 일부는 관련이 없을 수 있습니다. 레벨 통합 테스트는 설계를 추진할 때 종종 더 유용한 테스트입니다.


-2

단위 코드는 코드가 이전의 모든 테스트를 통과 할 때 리팩토링에 도움이됩니다 .

서두르고 성능에 대해 걱정하지 않기 때문에 bubblesort를 구현했다고 가정하지만 이제 데이터가 길어지기 때문에 quicksort를 원한다고 가정하십시오. 모든 테스트가 통과되면 상황이 좋아 보입니다.

물론이 작업을 수행하려면 테스트가 포괄적이어야합니다. 필자의 예제에서는 거품 정렬과 관련이 없으므로 테스트에서 안정성을 다루지 않을 수 있습니다 .


1
이것은 사실이지만 코드 디자인 품질에 직접적인 영향을주는 것보다 유지 관리가 더 쉽습니다.
Ant P

@AntP, OP는 리팩토링 및 단위 테스트에 대해 질문했습니다.
om

1
문제 리팩토링에 관한 것이지만 실제 질문은 단위 테스트가 어떻게 코드 디자인을 개선 / 검증 할 수 있는지에 관한 것이 었습니다.
앤트 P

-3

단위 테스트는 프로젝트의 장기적인 유지 관리를 촉진하는 데 가장 중요하다는 것을 알았습니다. 몇 달 후에 프로젝트로 돌아와서 많은 세부 사항을 기억하지 못하는 경우 테스트를 실행하면 문제가 발생하지 않습니다.


6
그것은 확실히 테스트의 중요한 측면이지만 질문에 실제로 답하지는 않습니다 (테스트가 좋은 이유가 아니라 디자인에 미치는 영향).
Hulk
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.