단위 테스트를 가능하게하기 위해 처음부터 코드를 설계해야합니까?


91

현재 팀에서 단위 테스트를 허용하도록 코드 디자인을 수정하는 것이 코드 냄새인지, 또는 코드 냄새가 나지 않고 어느 정도까지 할 수 있는지에 대한 논쟁이 있습니다. 우리는 단지 다른 모든 소프트웨어 개발 회사에 존재하는 관행을 제자리에두기 시작했기 때문에 발생했습니다.

특히, 우리는 매우 얇은 웹 API 서비스를 갖게 될 것입니다. 주요 책임은 웹 요청 / 응답을 마샬링하고 비즈니스 로직을 포함하는 기본 API를 호출하는 것입니다.

한 가지 예는 인증 방법 유형을 반환하는 팩토리를 만들 계획입니다. 우리는 그것이 인터페이스가 될 구체적인 유형 이외의 다른 것을 기대하지 않기 때문에 인터페이스를 상속받을 필요가 없습니다. 그러나 웹 API 서비스를 단위 테스트하려면이 팩토리를 조롱해야합니다.

이것은 본질적으로 우리가 (생성자 또는 설정자를 통해) DI를 받아들이도록 Web API 컨트롤러 클래스를 설계한다는 것을 의미합니다. 즉, DI를 허용하고 우리가 필요로하지 않는 인터페이스를 구현하기 위해 컨트롤러의 일부를 설계하고 있음을 의미합니다. 이런 식으로 컨트롤러를 디자인 할 필요가 없도록 Ninject와 같은 타사 프레임 워크를 사용하지만 여전히 인터페이스를 만들어야합니다.

팀의 일부는 테스트를 위해 코드를 디자인하는 것을 꺼려합니다. 단위 테스트를 원한다면 약간의 타협이 필요한 것 같습니다. 그러나 그들의 우려가 어떻게 완화되는지 확실하지 않습니다.

분명히 이것은 아주 새로운 프로젝트이므로 단위 테스트를 가능하게하기 위해 코드를 수정하는 것은 아닙니다. 단위 테스트가 가능하도록 작성할 코드를 설계하는 것입니다.


33
반복 해 보겠습니다. 동료들은 새로운 코드에 대한 단위 테스트를 원하지만 기존 코드를 손상시킬 위험은 없지만 단위 테스트가 가능한 방식으로 코드 작성을 거부합니까? 그것이 사실이라면, @KilianFoth의 대답을 받아들이고 그의 대답에서 첫 문장을 굵게 강조하도록 요청해야합니다! 동료들은 분명히 자신의 직업이 무엇인지에 대해 매우 큰 오해를 가지고 있습니다.
Doc Brown

20
@Lee : 디커플링이 항상 좋은 아이디어 라고 누가 말 합니까? 일부 구성 인터페이스를 사용하여 인터페이스 팩토리에서 작성된 인터페이스로 모든 것이 전달되는 코드베이스를 본 적이 있습니까? 나는 가지고있다; Java로 작성되었으며, 유지 관리가 불가능하고 버그가 많았습니다. 극단적 분리는 코드 난독 화입니다.
Christian Hackl

8
Michael Feathers의 레거시 코드를 효과적으로 사용 하면이 문제를 매우 잘 처리 할 수 ​​있으며 새로운 코드 기반에서도 테스트의 이점에 대한 좋은 아이디어를 얻을 수 있습니다.
l0b0

8
@ l0b0 이것에 대한 성경입니다. stackexchange에서는 질문에 대한 답변이 아니지만 RL에서는 OP 에게이 책을 읽도록 요청합니다 (적어도 부분적으로). 영업 이익은 얻을 레거시 코드와 효과적으로 협력 하고 그것을 읽고, 적어도 부분적으로 (또는 그것을 얻기 위해 당신의 상사에게). 이와 같은 질문을 다룹니다. 특히 테스트를하지 않았지만 이제 테스트를 시작한 경우 20 년의 경험이있을 수 있지만 이제는 경험이없는 작업을 수행하게됩니다 . 시행 착오에 의해 모든 것을 힘들게 배우는 것보다 그들에 대해 읽는 것이 훨씬 쉽습니다.
R. Schmitz

4
Michael Feathers의 책을 추천 해 주셔서 감사합니다.

답변:


204

테스트를 위해 코드를 수정하지 않으려는 개발자는 개발자가 테스트의 역할을 이해하지 못했으며 조직에서 자신의 역할을 암시합니다.

소프트웨어 비즈니스는 비즈니스 가치를 창출하는 코드 기반을 제공하는 데 중점을 둡니다. 오랜 경험을 통해 우리는 테스트 없이는 사소한 크기의 코드 기반을 만들 수 없다는 것을 알게되었습니다. 따라서 테스트 스위트는 비즈니스에서 없어서는 안될 부분입니다.

많은 코더들은이 원칙에 따라 입술 서비스를 지불하지만 무의식적으로는 그것을 받아들이지 않습니다. 이것이 왜 그런지 이해하는 것은 쉽습니다. 우리 자신의 정신 능력이 무한하지 않다는 사실에 대한 인식은 사실 현대 코드 기반의 엄청난 복잡성에 직면했을 때 놀랍게도 제한되어 있으며, 환영받지 않고 쉽게 억압되거나 합리화됩니다. 테스트 코드가 고객에게 제공되지 않는다는 사실은 "필수"비즈니스 코드와 비교할 때이 코드가 2 등 시민이며 필수적이지 않다는 것을 쉽게 믿게합니다. 그리고 비즈니스 코드에 테스트 코드 추가한다는 아이디어는 많은 사람들에게 두려운 것 같습니다.

이 관행을 정당화하는 데 어려움은 소프트웨어 비즈니스에서 가치가 창출되는 방식에 대한 전체 그림이 종종 회사 계층 구조의 상위 계층에 의해서만 이해된다는 사실과 관련이 있지만,이 사람들은 테스트를 제거 할 수없는 이유 를 이해하는 데 필요한 코딩 워크 플로우 따라서 시험은 일반적으로 좋은 생각 일 수 있다고 확신하는 실무자에 의해 너무 자주 진정 되지만 "우리는 그런 목발을 필요로하지 않는 엘리트 프로그래머입니다."또는 "지금 우리는 그럴 시간이 없습니다"등. 비즈니스 성공은 숫자 게임이며 기술 부채를 피하고 보장한다는 사실 품질 등은 장기적으로 만 그 가치를 보여줍니다.

짧은 이야기 : 코드를 테스트 할 수있게 만드는 것은 개발 프로세스의 필수 부분으로 다른 분야와 다르지 않습니다 (많은 마이크로 칩은 테스트 목적으로 상당한 비율의 요소로 설계되었습니다 ). 그러나 그 이유를 간과하기 쉽습니다. 그. 함정에 빠지지 마십시오.


39
나는 그것이 변화의 종류에 달려 있다고 주장합니다. 코드를보다 쉽게 ​​테스트 할 수있게하는 것과 프로덕션 환경에서 절대 사용해서는 안되는 테스트 별 후크를 도입하는 것에는 차이가 있습니다. Murphy가 다음과 같은 이유로 개인적으로 후자를 조심합니다.
Matthieu M.

61
단위 테스트는 종종 캡슐화를 깨뜨리고 테스트중인 코드를 필요한 것보다 더 복잡하게 만듭니다 (예 : 추가 인터페이스 유형을 도입하거나 플래그 추가). 소프트웨어 엔지니어링에서 항상 그렇듯이 모든 모범 사례와 모든 규칙에는 부채가 있습니다. 많은 단위 테스트를 맹목적으로 생산하는 것은 테스트 작성 및 유지에 이미 시간과 노력이 필요하다는 것은 말할 것도없이 비즈니스 가치에 해로운 영향을 줄 있습니다. 필자의 경험에 따르면 통합 테스트는 ROI가 훨씬 높고 타협없이 소프트웨어 아키텍처를 개선하는 경향이 있습니다.
Christian Hackl

20
@Lee Sure 그러나 특정 유형의 테스트가 코드 복잡성의 증가를 보장하는지 여부를 고려해야합니다. 저의 개인적인 경험은 단위 테스트가 조롱을 수용하기 위해 기본 디자인 변경이 필요한 시점까지 훌륭한 도구라는 것입니다. 여기서 다른 유형의 테스트로 전환합니다. 단위 테스트를하는 목적으로 만 아키텍처를 훨씬 더 복잡하게 만들면서 단위 테스트를 작성하는 것은 배꼽을 피우는 것입니다.
Konrad Rudolph

21
@ChristianHackl 왜 단위 테스트가 캡슐화를 중단합니까? 내가 작업 한 코드의 경우 테스트를 가능하게 하기 위해 추가 기능을 추가 해야한다는 인식이있는 경우 실제 문제는 테스트하려는 기능이 리팩토링을 필요로하므로 모든 기능이 동일하다는 것입니다 낮은 수준의 코드를 자체 (테스트 가능한) 함수로 옮긴 상태에서 추상화 수준 (보통 추가 코드에이 "필요한"을 생성하는 추상화 수준의 차이)입니다.
Baldrickk

29
@ChristianHackl 단위 테스트는 캡슐화를 중단해서는 안됩니다. 단위 테스트에서 개인, 보호 또는 로컬 변수에 액세스하려고하면 잘못하고 있습니다. 기능 foo를 테스트하는 경우 로컬 변수 x가 두 번째 루프의 세 번째 반복에서 입력 y의 제곱근이 아니라 실제로 작동하는지 테스트합니다. 일부 기능이 비공개 인 경우에도 기능을 전 이적으로 테스트 할 것입니다. 정말 크고 사적인 경우? 그것은 디자인 결함이지만 헤더 구현 분리를 사용하는 C 및 C ++ 외부에서는 가능하지 않을 수도 있습니다.
opa

75

생각만큼 간단하지 않습니다. 그것을 분해하자.

  • 단위 테스트 작성은 확실히 좋은 일입니다.

그러나!

  • 코드를 변경하면 버그가 발생할 수 있습니다. 따라서 좋은 비즈니스 이유없이 코드를 변경하는 것은 좋은 생각이 아닙니다.

  • 귀하의 '매우 얇은'webapi는 단위 테스트에서 가장 큰 경우는 아닙니다.

  • 코드와 테스트를 동시에 변경하는 것은 나쁜 일입니다.

다음과 같은 접근법을 제안합니다.

  1. 통합 테스트를 작성하십시오 . 코드를 변경하지 않아도됩니다. 기본 테스트 사례를 제공하고 추가 코드 변경으로 인해 버그가 발생하지 않는지 확인할 수 있습니다.

  2. 새 코드 를 테스트 할 수 있고 단위 및 통합 테스트가 있는지 확인하십시오 .

  3. 빌드 및 배포 후에 CI 체인이 테스트를 실행하는지 확인하십시오.

그러한 것들이 설정되면 테스트 가능성을 위해 레거시 프로젝트를 리팩토링하는 것에 대해서만 생각하십시오.

모든 사람이 프로세스에서 교훈을 배우고 테스트가 가장 필요한 위치, 테스트 구성 방법 및 비즈니스에 미치는 가치에 대해 잘 알고 있기를 바랍니다.

편집 :이 답변을 작성한 이후 OP는 기존 코드를 수정하지 않고 새로운 코드에 대해 이야기하고 있음을 보여주기 위해 질문을 명확하게했습니다. "순수하게 테스트하는 것이 좋습니까?" 논쟁은 몇 년 전에 해결되었습니다.

단위 테스트에 어떤 코드 변경이 필요할지 상상하기 어렵지만 어떤 경우에도 원하는 일반적인 모범 사례는 아닙니다. 실제 이의 제기를 검토하는 것이 현명 할 것입니다. 아마도 이의 제기가되는 단위 테스트 스타일 일 것입니다.


12
이것은 받아 들인 것보다 훨씬 나은 대답입니다. 투표 불균형은 실망 스럽다.
Konrad Rudolph

4
@Lee 단위 테스트는 클래스에 해당하거나 일치하지 않는 기능 단위를 테스트해야합니다 . 기능 단위는 인터페이스 (이 경우 API 일 수 있음)에서 테스트해야합니다. 테스트는 디자인 냄새와 일부 다른 레벨링을 적용해야 할 필요성을 강조 할 수 있습니다. 작고 구성 가능한 조각으로 시스템을 구축하면 추론하고 테스트하기가 더 쉬워집니다.
Wes Toleman

2
@ KonradRudolph : OP 가이 질문에 기존 코드를 변경하지 않고 새로운 코드를 디자인하는 것에 관한 질문이 추가되었다는 점을 놓친 것 같습니다. 따라서 깨지는 것이 없으므로이 답변의 대부분을 적용 할 수 없습니다.
Doc Brown

1
나는 단위 테스트 작성이 항상 좋은 것이라는 진술에 강력히 동의하지 않습니다. 단위 테스트는 경우에 따라서 만 유효합니다. 프론트 엔드 (UI) 코드를 테스트하기 위해 단위 테스트를 사용하는 것은 어리석은 일이며 비즈니스 로직을 테스트하기 위해 만들어집니다. 또한 누락 된 컴파일 검사 (예 : Javascript)를 대체하기 위해 단위 테스트를 작성하는 것이 좋습니다. 대부분의 프론트 엔드 전용 코드는 단위 테스트가 아닌 엔드 투 엔드 테스트를 독점적으로 작성해야합니다.
술탄

1
디자인은 "테스트로 인한 손상"으로 인해 어려움을 겪을 수 있습니다. 일반적으로 테스트 가능성은 디자인을 향상시킵니다. 테스트를 작성할 때 무언가를 가져올 수 없지만 전달해야한다는 것을 알면 인터페이스가 더 명확 해집니다. 그러나 때로는 테스트를 위해서만 불편한 디자인이 필요한 것을 우연히 발견하게 됩니다 . 예를 들어 싱글 톤을 사용하는 기존의 타사 코드로 인해 새 코드에 필요한 테스트 전용 생성자가 될 수 있습니다. 그러한 경우 : 테스트 가능성이라는 이름으로 자신의 디자인을 손상시키는 대신, 물러서서 통합 테스트 만 수행하십시오.
Anders Forsgren

18

본질적으로 테스트 가능하도록 코드를 설계하는 것은 코드 냄새가 아닙니다. 오히려 좋은 디자인의 징조입니다. 이를 기반으로 잘 알려진 널리 사용되는 디자인 패턴 (예 : Model-View-Presenter)이 몇 가지있어 쉬운 테스트를 쉽게 제공 할 수 있습니다.

따라서보다 쉽게 ​​테스트하기 위해 구체적인 클래스에 대한 인터페이스를 작성해야하는 경우 좋은 방법입니다. 이미 구체적인 클래스를 가지고 있다면 대부분의 IDE는 인터페이스를 추출하여 필요한 노력을 최소화 할 수 있습니다. 둘을 동기화하는 것이 조금 더 많은 작업이지만 인터페이스는 크게 바뀌지 않아야하며 테스트의 이점이 추가 노력보다 중요 할 수 있습니다.

반면에 @MatthieuM으로. 주석에서 언급했듯이 테스트를 위해 프로덕션에서 절대 사용해서는 안되는 특정 진입 점을 코드에 추가하는 경우 문제가 될 수 있습니다.


이 문제는 정적 코드 분석을 통해 해결할 수 있습니다. 메소드에 플래그를 지정하고 (예 : 이름을 지정해야 함 _ForTest) 테스트되지 않은 코드의 호출에 대해 코드베이스를 확인하십시오.
리킹

13

단위 테스트를 만들려면 테스트 할 코드에 적어도 특정 속성이 있어야한다는 것을 이해하는 것은 매우 간단합니다. 예를 들어, 코드가 개별적 으로 테스트 할 수있는 개별 단위 로 구성되지 않은 경우 "단위 테스트"라는 단어는 의미가 없습니다. 코드에 이러한 속성이 없으면 먼저 변경해야합니다.

이론적으로는 모든 SOLID 원리를 적용하여 테스트 가능한 코드 단위를 먼저 작성한 다음 나중에 원래 코드를 수정하지 않고 테스트를 시도 할 수 있다고 말했다. 불행히도 실제로 단위 테스트가 가능한 코드를 작성하는 것이 항상 간단하지는 않으므로 테스트를 만들 때 감지해야 할 변경 사항이있을 가능성이 큽니다. 이것은 단위 테스트라는 개념을 염두에두고 작성된 코드에서도 마찬가지이며, "단위 테스트 성"이 처음에 안건이 아닌 곳에 작성된 코드의 경우에는 더욱 그렇습니다.

단위 테스트를 먼저 작성하여 문제를 해결하려고하는 잘 알려진 접근 방식이 있습니다.이를 TDD (Test Driven Development)라고하며 처음부터 코드 단위를 더 테스트 할 수 있도록 도와줍니다.

물론, 나중에 코드를 테스트 가능하게 만들기 위해 코드를 변경하는 것을 꺼리는 것은 코드를 수동으로 먼저 테스트하거나 문제를 잘 처리하는 상황에서 자주 발생하므로 변경하면 실제로 새로운 버그가 발생할 수 있습니다. 이를 완화하는 가장 좋은 방법은 회귀 테스트 슈트를 먼저 작성하는 것입니다 (코드베이스에 대한 최소한의 변경만으로도 구현할 수 있음)뿐만 아니라 코드 검토 또는 새로운 수동 테스트 세션과 같은 기타 수반되는 조치도 있습니다. 일부 내부를 재 설계해도 중요한 것이 깨지지 않도록 충분한 확신을 주어야합니다.


TDD에 대해 흥미 롭습니다. 우리는 BDD / TDD를 도입하려고 시도하고 있는데, 이는 또한 "최소 코드 통과"가 실제로 무엇을 의미하는지에 대한 저항을 충족 시켰습니다.

2
@Lee : 조직에 변화를 가져 오면 항상 약간의 저항이 생기고 새로운 것을 적응시키는 데 항상 시간이 필요합니다. 그것은 새로운 지혜가 아닙니다. 이것은 사람들의 문제입니다.
Doc Brown

물론. 우리가 더 많은 시간을 갖기를 바랍니다!

사람들에게 이런 식으로 시간을 절약 할 수 있다는 것을 보여주는 문제가 종종 있습니다. 왜 도움이되지 않는 것이 있습니까?
Thorbjørn Ravn Andersen

@ ThorbjørnRavnAndersen : 마찬가지로 팀은 OP에 접근 방식으로 시간을 절약 할 수 있음을 보여줄 수 있습니다. 누가 알아? 그러나 우리가 실제로 기술적 인 문제가 적은 문제에 직면하지 않았는지 궁금합니다. OP는 자신의 주장에 대한 동맹국을 찾으려고하는 것처럼 그의 팀이 자신의 의견으로 잘못한 것을 알려주기 위해 계속 여기에옵니다. Stack Exchange의 낯선 사람이 아닌 팀 함께 프로젝트를 실제로 논의하는 것이 더 유리할 수 있습니다 .
Christian Hackl

11

나는 당신이 만드는 (확실하지 않은) 주장과 관련하여 문제를 겪습니다.

웹 API 서비스를 단위 테스트하려면이 팩토리를 조롱해야합니다.

반드시 그런 것은 아닙니다. 테스트를 작성 하는 방법 에는 여러 가지 가 있으며 모의를 포함하지 않는 단위 테스트를 작성 하는 방법 이 있습니다 . 더 중요한 것은 기능 테스트 또는 통합 테스트와 같은 다른 종류 의 테스트가 있습니다. 많은 경우 OOP 프로그래밍 언어가 아닌 "인터페이스"에서 "테스트 이음새"를 찾을 수 있습니다 interface.

보다 자연스러운 대체 테스트 솔기를 찾는 데 도움이되는 몇 가지 질문 :

  • 다른 API를 통해 씬 웹 API를 작성하고 싶 습니까?
  • 웹 API와 기본 API 간의 코드 중복을 줄일 수 있습니까? 하나는 다른 것의 관점에서 생성 될 수 있습니까?
  • 전체 웹 API와 기본 API를 단일 "블랙 박스"단위로 취급하고 전체 동작 방식에 대한 의미있는 주장을 할 수 있습니까?
  • 향후 웹 API를 새로운 구현으로 교체해야한다면 어떻게해야할까요?
  • 향후 웹 API가 새로운 구현으로 대체 된 경우 웹 API의 클라이언트가 알 수 있습니까? 그렇다면 어떻게?

입증되지 않은 또 다른 주장은 DI에 관한 것입니다.

DI를 생성하도록 Web API 컨트롤러 클래스를 설계하거나 (생성자 또는 설정자를 통해), 이는 DI를 허용하고 필요하지 않은 인터페이스를 구현하기 위해 컨트롤러의 일부를 설계하거나 타사를 사용함을 의미합니다. Ninject와 같은 프레임 워크는 컨트롤러를 이런 식으로 디자인하지 않아도되지만 여전히 인터페이스를 만들어야합니다.

의존성 주입이 반드시 새로운 것을 만드는 것은 아닙니다 interface. 예를 들어 인증 토큰의 원인 : 프로그래밍 방식 으로 실제 인증 토큰을 만들 수 있습니까? 그런 다음 테스트에서 이러한 토큰을 만들어 주입 할 수 있습니다. 토큰을 검증하는 프로세스는 어떤 종류의 암호화 비밀에 의존합니까? 나는 당신이 비밀을 하드 코딩하지 않았기를 바랍니다. 어떻게 든 스토리지에서 읽을 수 있기를 기대하며,이 경우 테스트 케이스에서 다른 (잘 알려진) 비밀을 사용할 수 있습니다.

이것은 결코 새로운 것을 만들지 말아야한다는 것은 아닙니다 interface. 그러나 시험을 작성하는 한 가지 방법이나 행동을 위조하는 한 가지 방법만으로는 고쳐지지 마십시오. 상자 밖에서 생각하면 일반적으로 최소한의 코드 왜곡이 필요한 솔루션을 찾을 수 있지만 원하는 효과를 얻을 수 있습니다.


인터페이스에 관한 주장에 대해 언급했지만, 우리가 인터페이스를 사용하지 않더라도 개체를 어떻게 주입해야하는지, 이것이 팀의 나머지 부분의 관심사입니다. 즉, 팀의 일부는 구체적인 구현을 인스턴스화하고 그대로 두는 매개 변수없는 ctr에 만족할 것입니다. 실제로 한 멤버가 리플렉션을 사용하여 목을 주입한다는 아이디어를 떠 올렸으므로이를 수용하기 위해 코드를 디자인 할 필요가 없습니다. 위험한 코드 냄새 imo
Lee

9

이것이 새로운 프로젝트이므로 운이 좋았습니다. Test Driven Design은 좋은 코드를 작성하는 데 매우 효과적이라는 것을 알았습니다 (그래서 우리는 처음에 그것을 수행합니다).

파악함으로써 정면 실제 입력 데이터와 주어진 코드를 호출 한 후 의도 한대로 당신이, 당신이 초기 과정에서 API 설계를 할 수있다 확인할 수 있습니다 실제 출력 데이터를 가져오고을 얻기의 좋은 기회를 가지고하는 방법 수용하기 위해 다시 작성해야하는 기존 코드에 방해받지 않기 때문에 유용한 디자인. 또한 동료가 이해하기가 더 쉬워 프로세스 초기에 다시 좋은 토론을 할 수 있습니다.

위 문장에서 "유용하다"는 결과 메소드가 호출하기 쉬울뿐만 아니라 통합 테스트에서 조작하기 쉽고 깔끔한 인터페이스를 만들고 모형을 작성하는 경향이 있음을 의미합니다.

생각해 봐. 특히 동료 검토의 경우. 내 경험상 시간과 노력의 투자는 매우 빨리 반환 될 것입니다.


TDD에도 문제가 있습니다. 즉 "최소 코드 통과"를 구성하는 요소입니다. 나는이 과정을 팀에 보여 주었고 우리가 이미 디자인 한 것을 작성하는 것만이 아니라 예외를 이해했다. "최소한"정의되지 않은 것 같습니다. 테스트를 작성하고 계획과 디자인이 분명하다면 테스트를 통과하기 위해 작성하지 않겠습니까?

@Lee "최소 코드 통과"... 글쎄, 이것은 약간 바보처럼 들릴지 모르지만 문자 그대로 말합니다. 예를 들어 test가 있다면 테스트 UserCanChangeTheirPassword에서 (아직 존재하지 않는) 함수를 호출하여 암호를 변경 한 다음 암호가 실제로 변경되었다고 주장합니다. 그런 다음 테스트를 실행할 수있을 때까지 함수를 작성하고 예외를 던지거나 잘못된 어설 션을 갖지 않습니다. 이 시점에서 코드를 추가해야 할 이유가있는 경우 해당 이유는 다른 테스트 (예 :)로 진행됩니다 UserCantChangePasswordToEmptyString.
R. Schmitz

@Lee 궁극적으로 테스트는 종이에 잉크가 아닌 자체적으로 수행되는지를 확인하는 문서를 제외하고 코드가 수행하는 작업에 대한 문서가됩니다. 또한 비교 대상 이 질문 하는 방법 - CalculateFactorial그냥 120를 반환하고 테스트를 통과. 즉 입니다 최소. 또한 분명히 의도하지거야,하지만 당신이 무엇을 표현하는 또 다른 시험이 필요 의미 구성.
R. Schmitz

1
@이 작은 단계. 코드가 사소한 수준 이상으로 올라갈 때 생각보다 최소한이 될 수 있습니다. 또한 전체를 한 번에 구현할 때 수행하는 디자인은 아직 테스트를 작성하지 않은 상태에서 어떻게 구현해야하는지에 대한 가정을하기 때문에 최적이 아닐 수 있습니다. 코드는 처음에는 실패해야합니다.
Thorbjørn Ravn Andersen

1
또한 회귀 테스트가 매우 중요합니다. 그들은 팀의 범위에 있습니까?
Thorbjørn Ravn Andersen

8

코드를 수정해야하는 경우 코드 냄새가납니다.

개인적인 경험으로 볼 때 내 코드가 테스트를 작성하기 어려운 경우 나쁜 코드입니다. 설계된대로 실행되거나 작동하지 않기 때문에 나쁜 코드가 아닙니다. 왜 작동하는지 빨리 이해할 수 없기 때문에 나쁩니다. 버그가 발생하면 문제를 해결하는 데 오랜 시간이 걸릴 것입니다. 코드는 재사용이 어렵거나 불가능합니다.

좋은 (깨끗한) 코드는 작업을 한눈에 알기 쉽게 (또는 최소한보기 좋게) 더 작은 섹션으로 나눕니다. 이러한 작은 섹션을 테스트하는 것은 쉽습니다. 하위 섹션에 대해 확신이있는 경우 비슷한 코드베이스 청크 만 테스트하는 테스트를 작성할 수도 있습니다 (재 테스트는 이미 테스트 했으므로 여기에서도 유용합니다).

코드를 쉽게 테스트하고, 리팩토링하고, 처음부터 재사용하기 쉽도록 유지하십시오. 변경해야 할 때마다 스스로를 죽이지 않을 것입니다.

더 깔끔한 코드로 버려 질 프로토 타입이었던 프로젝트를 완전히 재건하면서 이것을 입력하고 있습니다. 화면에서 몇 시간 동안 화면을 응시하는 대신 가능한 빨리 시작하고 나쁜 코드를 리팩터링하는 것이 부분적으로 작동하는 무언가를 깨뜨리는 것을 두려워하는 것을 두려워하는 것이 훨씬 좋습니다.


3
"Throwaway 프로토 타입"-모든 단일 프로젝트는 인생을 시작하는 프로젝트 중 하나로 시작합니다. 내가 이걸 입력하는 것 같아 ...;가 아닌 것으로 판명 된 버림받은 프로토 타입을 리팩토링
Algy Taylor

4
버리기 프로토 타입을 버리려면 프로덕션에서는 절대 사용할 수없는 프로토 타입 언어로 작성하십시오. Clojure와 Python은 좋은 선택입니다.
Thorbjørn Ravn Andersen

2
@ ThorbjørnRavnAndersen 그게 나를 방해했다. 그 언어를 파헤쳐보아야합니까? :)
Lee

@남자 이름. 아니요, 프로덕션에는 적합하지 않은 언어의 예만 있습니다. 일반적으로 조직 의 언어 에 익숙하지 않고 학습 곡선이 가파르 기 때문에 조직의 아무도 언어를 유지할 수 없기 때문입니다. 허용되는 경우 허용되지 않는 다른 것을 선택하십시오.
Thorbjørn Ravn Andersen

4

단위 테스트 할 수없는 코드를 작성 하는 것은 코드 냄새 라고 주장합니다 . 일반적으로 코드를 단위로 테스트 할 수없는 경우 모듈식이 아니므로 이해, 유지 관리 또는 향상하기가 어렵습니다. 코드가 통합 테스트 측면에서만 의미가있는 글루 코드 인 경우 단위 테스트 대신 통합 테스트를 대체 할 수 있지만 통합에 실패 할 경우 문제를 격리해야하며 단위 테스트는 다음과 같은 좋은 방법입니다. 해.

당신은 말한다

인증 방법 유형을 반환하는 팩토리를 만들 계획입니다. 우리는 그것이 인터페이스가 될 구체적인 유형 이외의 다른 것을 기대하지 않기 때문에 인터페이스를 상속받을 필요가 없습니다. 그러나 웹 API 서비스를 단위 테스트하려면이 팩토리를 조롱해야합니다.

나는 이것을 정말로 따르지 않는다. 무언가를 생성하는 팩토리를 갖는 이유는 팩토리를 변경하거나 팩토리가 생성하는 것을 쉽게 변경할 수 있기 때문에 코드의 다른 부분을 변경할 필요가 없기 때문입니다. 인증 방법이 변경되지 않으면 공장에서 쓸모없는 코드 팽창입니다. 그러나 프로덕션에서와 다른 테스트 방법을 테스트하려면 프로덕션에서와 다른 테스트 방법을 테스트하는 팩토리를 갖는 것이 좋은 솔루션입니다.

이를 위해 DI 또는 Mocks가 필요하지 않습니다. 다른 인증 유형을 지원하고 구성 파일 또는 환경 변수와 같은 방식으로 구성 할 수 있도록 팩토리가 필요합니다.


2

내가 생각할 수있는 모든 엔지니어링 분야에서 양질의 품질을 달성 할 수있는 유일한 방법은 다음과 같습니다.

디자인의 검사 / 테스트를 설명합니다.

이는 건축, 칩 설계, 소프트웨어 개발 및 제조에 적용됩니다. 그렇다고해서 테스팅이 모든 디자인이 반드시 필요하지는 않다는 것을 의미하는 것은 아닙니다. 그러나 모든 설계 결정에서 설계자는 테스트 비용에 미치는 영향을 명확하게 파악하고 트레이드 오프에 대해 신중한 결정을 내려야합니다.

경우에 따라 수동 또는 자동 (예 : 셀레늄) 테스트가 단위 테스트보다 편리 할뿐만 아니라 자체적으로 허용 가능한 테스트 범위를 제공합니다. 드물게 거의 완전히 테스트되지 않은 것을 버릴 수도 있습니다. 그러나 이들은 의사 결정에 따라 의식이 있어야합니다. "코드 냄새"를 테스트하는 디자인을 호출하면 경험이 심각하지 않음을 나타냅니다.


1

단위 테스트 (및 다른 유형의 자동 테스트)가 코드 냄새를 줄이는 경향이 있음을 발견했으며 코드 냄새가 나는 단일 사례를 생각할 수 없습니다. 단위 테스트는 일반적으로 더 나은 코드를 작성하도록합니다. 테스트중인 메서드를 쉽게 사용할 수 없다면 왜 코드에서 더 쉬워야합니까?

잘 작성된 단위 테스트는 코드의 사용법을 보여줍니다. 실행 가능한 문서 형식입니다. 나는 단순히 이해할 수없는 엄청나게 쓰여진 지나치게 긴 단위 테스트를 보았습니다. 그것들을 쓰지 마십시오! 클래스를 설정하기 위해 긴 테스트를 작성해야하는 경우 클래스 리팩토링이 필요합니다.

단위 테스트는 코드 냄새의 일부를 강조합니다. Michael C. Feathers의 레거시 코드로 효과적으로 작업하는 것이 좋습니다. 프로젝트가 새로운 것이더라도, 아직 (또는 많은) 단위 테스트가 없다면 코드를 잘 테스트하기 위해 명백하지 않은 기술이 필요할 수 있습니다.


3
테스트 할 수 있도록 많은 간접 레이어를 도입하고 싶은 경우에는 예상대로 사용하지 마십시오.
Thorbjørn Ravn Andersen

1

간단히 말해서 :

테스트 가능한 코드는 (보통) 유지 관리 가능한 코드입니다. 테스트하기 어려운 코드 는 일반적 으로 유지 관리하기 가 어렵 습니다. 테스트 할 수없는 코드를 설계하는 것은 수리 할 수없는 기계를 설계하는 것과 유사 합니다. 결국 수리 를 맡게 될 가난한 shmuck을 불쌍히 여깁니다 (당신이 될 수도 있습니다).

한 가지 예는 인증 방법 유형을 반환하는 팩토리를 만들 계획입니다. 우리는 그것이 인터페이스가 될 구체적인 유형 이외의 다른 것을 기대하지 않기 때문에 인터페이스를 상속받을 필요가 없습니다.

3 년 안에 5 가지 유형의 인증 방법이 필요하다는 것을 알고 있습니다. 요구 사항 변경 및 설계 오버 엔지니어링을 피해야하지만 테스트 가능한 설계는 설계에 너무 많은 이음새가 너무 많은 고통없이 변경 될 수 있음을 의미합니다. 변경 사항이 아무런 영향을 미치지 않습니다.


1

의존성 주입을 중심으로 디자인하는 것은 코드 냄새가 아니며 모범 사례입니다. DI를 사용하는 것은 테스트 가능성만을위한 것이 아닙니다. DI를 중심으로 컴포넌트를 구축하면 모듈 성과 재사용 성을 높이고 데이터베이스 인터페이스 레이어와 같은 주요 컴포넌트를보다 쉽게 ​​교체 할 수 있습니다. 복잡성을 추가하는 동시에 올바르게 수행하면 계층을보다 효과적으로 분리하고 기능을 격리하여 복잡성을보다 쉽게 ​​관리하고 탐색 할 수 있습니다. 이를 통해 각 구성 요소의 동작을 올바르게 검증하고 버그를 줄이고 버그를 쉽게 추적 할 수 있습니다.


1
"올바른 일"은 문제입니다. DI가 잘못 수행 된 두 가지 프로젝트를 유지해야합니다 (하지만 "올바른 일"을 목표로하지만). 이로 인해 DI 및 단위 테스트가없는 기존 프로젝트보다 코드가 끔찍하고 훨씬 나빠집니다. DI를 올바르게 얻는 것은 쉽지 않습니다.
1

@Jan 흥미 롭습니다. 그들은 어떻게 잘못 했습니까?

1
@Lee One 프로젝트는 빠른 시작 시간이 필요하지만 모든 클래스 초기화가 DI 프레임 워크 (Castle Windsor in C #)에 의해 사전에 수행되므로 시작시 끔찍한 서비스입니다. 이 프로젝트에서 볼 수있는 또 다른 문제는 DI를 "새로운"개체로 만드는 것과 DI를 섞어 DI를 회피하는 것입니다. 그것은 다시 테스트를 어렵게 만들고 불쾌한 경쟁 조건을 초래했습니다.
1

1

이것은 본질적으로 우리가 (생성자 또는 설정자를 통해) DI를 받아들이도록 Web API 컨트롤러 클래스를 설계한다는 것을 의미합니다. 즉, DI를 허용하고 우리가 필요로하지 않는 인터페이스를 구현하기 위해 컨트롤러의 일부를 설계하고 있음을 의미합니다. 이런 식으로 컨트롤러를 디자인 할 필요가 없도록 Ninject와 같은 타사 프레임 워크를 사용하지만 여전히 인터페이스를 만들어야합니다.

테스트 할 수있는 것의 차이점을 살펴 보겠습니다.

public class MyController : Controller
{
    private readonly IMyDependency _thing;

    public MyController(IMyDependency thing)
    {
        _thing = thing;
    }
}

테스트 할 수없는 컨트롤러 :

public class MyController : Controller
{
}

전자 옵션에는 문자 그대로 5 줄의 코드가 추가되어 있으며 그 중 2 줄은 Visual Studio에서 자동 생성 할 수 있습니다. IMyDependency런타임에 구체적인 유형을 대체하도록 종속성 주입 프레임 워크를 설정하면 -괜찮은 DI 프레임 워크의 또 다른 한 줄의 코드 인-그냥 작동합니다. 지금은 컨트롤러를 마음의 내용으로 조롱하고 테스트 할 수 있습니다. .

테스트 가능성을 높이기위한 6 줄의 추가 코드 ... 그리고 동료들이 "너무 많은 일"이라고 주장하고 있습니까? 그 주장은 나와 함께 날지 않으며, 당신과 함께 날지 않아야합니다.

또한 테스트를위한 인터페이스를 생성하고 구현할 필요가 없습니다 . 예를 들어 Moq를 사용하면 단위 테스트 목적으로 콘크리트 유형의 동작을 시뮬레이션 할 수 있습니다. 물론 테스트중인 클래스에 해당 유형을 주입 할 수없는 경우에는 그다지 유용하지 않습니다.

의존성 주입은 일단 이해하면 "이것없이 어떻게 작업 했습니까?"라고 생각하는 것들 중 하나입니다. 간단하고 효과적이며 단지 감각을 만듭니다. 동료가 새로운 것을 이해하지 못하여 프로젝트를 테스트 할 수 없도록하십시오.


1
"새로운 것들에 대한 이해 부족" 으로 당신이 너무 빨리 해산 하는 것은 오래된 것들에 대한 좋은 이해로 판명 될 수 있습니다. 의존성 주입은 확실히 새로운 것이 아닙니다. 아이디어와 아마도 가장 초기의 구현은 수십 년 전입니다. 그리고 네 대답은 단위 테스트로 인해 코드가 더 복잡 해지는 예이며 캡슐화를 깨는 단위 테스트의 예 일 것입니다 (누가 클래스에 공개 생성자가 있다고 말했기 때문에)? 나는 종종 다른 사람으로부터 상속받은 코드베이스에서 의존성 주입을 제거했습니다.
Christian Hackl

컨트롤러에는 항상 MVC가 필요하기 때문에 공개 생성자가 있습니다. "복잡한"-생성자가 어떻게 작동하는지 이해하지 못하는 경우. 캡슐화-어떤 경우에는 가능하지만 DI 대 캡슐화 토론은 여기서 도움이되지 않는 지속적이고 매우 주관적인 토론이며, 특히 대부분의 응용 프로그램에서 DI는 캡슐화 IMO보다 더 나은 서비스를 제공합니다.
이안 켐프

퍼블릭 생성자에 관해서는 : 그것은 실제로 사용되는 프레임 워크의 특이성입니다. 프레임 워크에 의해 인스턴스화되지 않은 일반 클래스의보다 일반적인 경우에 대해 생각하고있었습니다. 복잡성을 추가 한 것으로 추가 메소드 매개 변수를 보는 것이 생성자가 작동하는 방식에 대한 이해가 부족하다고 생각하는 이유는 무엇입니까? 그러나 DI와 캡슐화 사이에 상충 관계가 있음을 인정합니다.
Christian Hackl

0

단위 테스트를 작성할 때 코드 내에서 무엇이 잘못 될 수 있는지 생각하기 시작합니다. 코드 디자인을 개선하고 단일 책임 원칙 (SRP)을 적용하는 데 도움이됩니다 . 또한 몇 달 후에 동일한 코드를 수정하려고하면 기존 기능이 손상되지 않았 음을 확인하는 데 도움이됩니다.

순수한 기능을 최대한 많이 사용하는 경향이 있습니다 (서버리스 앱). 단위 테스트를 통해 상태를 분리하고 순수한 함수를 작성할 수 있습니다.

특히, 우리는 매우 얇은 웹 API 서비스를 갖게 될 것입니다. 주요 책임은 웹 요청 / 응답을 마샬링하고 비즈니스 로직을 포함하는 기본 API를 호출하는 것입니다.

기본 API에 대한 단위 테스트를 먼저 작성하고 충분한 개발 시간이있는 경우 Thin Web API 서비스에 대한 테스트도 작성해야합니다.

TL; DR, 단위 테스트는 코드 품질을 향상시키고 향후 코드 변경을 위험없이 수행 할 수 있도록 도와줍니다. 또한 코드의 가독성을 향상시킵니다. 의견을 제시하기 위해 주석 대신 테스트를 사용하십시오.


0

결론과 마지 못한 로트에 대한 당신의 주장은 갈등이 없다는 것입니다. 큰 실수는 누군가가 테스트를 싫어하는 사람들에게 "테스트를위한 디자인"이라는 아이디어를 만들어 낸 것 같습니다. 그들은 "올바른 일을하기 위해 시간을 내보자"와 같이 입을 다물거나 다르게 말을해야했다.

무언가를 테스트 할 수있게하려면 "인터페이스를 구현해야"한다는 생각이 잘못되었습니다. 인터페이스는 이미 구현되었으며 클래스 선언에서 아직 선언되지 않았습니다. 기존의 공개 메소드를 인식하고 서명을 인터페이스에 복사 한 후 클래스 선언에서 해당 인터페이스를 선언해야합니다. 프로그래밍, 기존 로직 변경 없음

분명히 어떤 사람들은 이것에 대해 다른 생각을 가지고 있습니다. 이 문제를 먼저 해결하는 것이 좋습니다.

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