테스트 목적으로 코드를 엄격하게 수정하는 것은 나쁜 습관입니까?


77

프로그래머 동료와 토론하여 작업 코드를 수정하여 테스트 할 수 있도록 (예 : 단위 테스트를 통해) 좋은지 나쁜지에 대한 토론이 있습니다.

제 생각에는 좋은 객체 지향 및 소프트웨어 엔지니어링 관행 ( "모든 것을 공개하는 것"등)을 유지하는 한도 내에서 괜찮다는 것입니다.

내 동료의 의견은 테스트 목적으로 만 코드를 수정하는 것이 잘못되었다는 것입니다.

간단한 예를 들면, C #으로 작성된 일부 구성 요소에서 사용하는이 코드 조각을 생각해보십시오.

public void DoSomethingOnAllTypes()
{
    var types = Assembly.GetExecutingAssembly().GetTypes();

    foreach (var currentType in types)
    {
        // do something with this type (e.g: read it's attributes, process, etc).
    }
}

이 코드는 실제 작업을 수행하는 다른 메소드를 호출하도록 수정할 수 있다고 제안했습니다.

public void DoSomething(Assembly asm)
{
    // not relying on Assembly.GetExecutingAssembly() anymore...
}

이 방법은 Assembly 객체를 처리하여 자체 어셈블리를 전달하여 테스트를 수행 할 수 있도록합니다. 제 동료는 이것이 좋은 습관이라고 생각하지 않았습니다.

좋은 관행은 무엇입니까?


5
수정 된 메소드는가 Type아닌 매개 변수로 사용해야 합니다 Assembly.
Robert Harvey

당신이 맞습니다-타입 또는 어셈블리, 요점은 코드가 매개 변수로 공급을 허용해야한다는 것이 었습니다.이 기능은 테스트에만 사용되는 것처럼 보일 수 있습니다 ...
liortal

5
자동차에는 "테스트"를위한 ODBI 포트가 있습니다. 모든 것이 완벽하다면 필요하지 않습니다. 내 생각에 당신은 자동차가 그의 소프트웨어보다 더 안정적입니다.
mattnz

24
코드가 "작동"한다는 것은 어떤 보증입니까?
Craige

3
물론입니다. 테스트 된 코드는 시간이 지남에 따라 안정적입니다. 그러나 새로 도입 된 버그가 테스트 가능성에 대한 수정이 아닌 기능 개선과 함께 제공된 경우 훨씬 쉽게 설명 할 수 있습니다.
Petter Nordlander

답변:


135

보다 테스트 가능하도록 코드를 수정하면 테스트 가능성 이상의 이점이 있습니다. 일반적으로 더 테스트 가능한 코드

  • 유지 관리가 더 쉽고
  • 추론하기 쉬우 며
  • 더 느슨하게 결합되어 있으며
  • 전체적으로 더 나은 디자인을 가지고 있습니다.

3
나는 내 대답에 이러한 것들을 언급하지 않았지만 전적으로 동의합니다. 코드를 더 테스트 가능하도록 재 작업하면 몇 가지 부작용이 생길 수 있습니다.
Jason Swett

2
+1 : 일반적으로 동의합니다. 코드를 테스트 가능하게 만드는 것이 이러한 다른 유익한 목표에 해를 끼치는 경우가 분명히 있으며,이 경우 테스트 가능성이 가장 낮은 우선 순위에 있습니다. 그러나 일반적으로 코드가 테스트하기에 충분히 유연하거나 확장 가능하지 않으면 유연하게 사용하거나 적절하게 노화시킬 수 없습니다.
Telastyn

6
테스트 가능한 코드가 더 나은 코드입니다. 코드를 테스트하지 않는 코드를 수정하는 데는 항상 내재 된 위험이 있지만, 매우 짧은 기간에 그 위험을 완화하는 가장 쉽고 저렴한 방법은 코드를 잘 내버려 두는 것입니다. 코드를 변경하십시오. 단위 테스트의 이점을 동료에게 판매해야합니다. 모두가 단위 테스트에 참여하고 있다면 코드를 테스트 할 수 있어야한다는 주장은 없습니다. 모든 사람이 단위 테스트에 참여하고 있지 않다면 아무런 의미가 없습니다.
guysherman

3
바로 그거죠. 내 코드의 약 10k 소스 라인에 대한 단위 테스트를 작성했던 것을 기억합니다. 코드가 완벽하게 작동한다는 것을 알았지 만 테스트로 인해 일부 상황을 다시 생각해야했습니다. "이 방법이 정확히 무엇을하고 있습니까?" 새로운 관점에서 볼 때만 작업 코드에서 몇 가지 버그를 발견했습니다.
Sulthan

2
위로 버튼을 계속 클릭하지만 한 가지 포인트 만 줄 수 있습니다. 현재 고용주가 단위 테스트를 실제로 구현하기에는 너무 근시안적이며, 테스트 중심의 작업을하는 것처럼 의식적으로 코드를 작성하는 것이 여전히 유리합니다.
AmericanUmlaut 12

59

(겉보기)가 있습니다 반대 세력 플레이는.

  • 한편으로는 캡슐화를 시행하려고합니다.
  • 반면에, 당신은 소프트웨어를 테스트 할 수 있기를 원합니다

모든 '구현 세부 사항'을 비공개로 유지하려는 제안자는 일반적으로 캡슐화를 유지하려는 욕구에 의해 동기가 부여됩니다. 그러나 모든 것을 잠그고 사용할 수없는 상태로 유지하는 것은 캡슐화에 대한 오해 입니다. 모든 것을 사용할 수없는 상태로 유지하는 것이 궁극적 인 목표라면 캡슐화 된 유일한 코드는 다음과 같습니다.

static void Main(string[] args)

동료가 이것을 코드의 유일한 액세스 포인트로 만들 것을 제안하고 있습니까? 외부 발신자가 다른 모든 코드에 액세스 할 수 없습니까?

거의. 그렇다면 어떤 방법을 공개해도 좋을까요? 결국 주관적인 디자인 결정이 아닙니까?

좀 빠지는. 무의식 수준에서도 프로그래머를 안내하는 경향은 캡슐화의 개념입니다. 변하지 않는 것을 적절히 보호 할 때 공개적인 방법을 노출시키는 것이 안전하다고 생각합니다 .

나는 그것의 불변을 보호하지 않는 개인 방법을 보여주고 싶지 않을 것이다, 그러나 수시로 당신이 있도록 수정할 수 있습니다 않습니다 자사의 불변을 보호하고, 다음 TDD와 함께 (물론, 대중에 노출, 당신은 그것을 할 다른 방법으로).

실제로하고있는 것은 Open / Closed Principle을 적용 하기 때문에 테스트 가능성을 위해 API를 여는 것이 좋습니다 .

API 호출자가 한 명 뿐인 경우 API가 실제로 얼마나 유연한 지 알 수 없습니다. 아마 융통성이 없습니다. 테스트는 두 번째 클라이언트 역할 을하여 API의 유연성에 대한 귀중한 피드백을 제공합니다 .

따라서 테스트에서 API를 열어야한다고 제안하면 수행하십시오. 복잡성을 숨기지 않고, 안전한 방법으로 복잡성을 노출시켜 캡슐화를 유지합니다.


3
+1 변하지 않는 것을 적절히 보호 할 때 공개적인 방법을 노출시키는 것이 안전하다고 생각합니다.
shambulator 12

1
나는 당신의 대답을 loooooove! :) 몇 번이나 듣습니다. "캡슐화는 일부 메서드 및 속성을 비공개로 만드는 프로세스입니다." 객체 지향으로 프로그래밍 할 때 말하는 것과 같습니다. 왜냐하면 객체로 프로그래밍하기 때문입니다. :( 나는 의존성 주입의 마스터로부터 너무 깨끗한 답변이 나왔다는 사실에 놀랄 일이 아니다. 나는 나의 오래된 오래된 레거시 코드에 대해 웃게하기 위해 당신의 대답을 반드시 몇 번 읽을 것이다.
Samuel

귀하의 답변에 약간의 조정을 추가했습니다. 따로, 나는 Encapsulation of Properties 게시물을 읽었으며 자동 속성을 사용하는 것에 대해 들어 본 유일한 이유는 공용 변수를 공용 속성으로 변경하면 이진 호환성이 깨지기 때문입니다. 처음부터 자동 속성으로 노출하면 클라이언트 코드를 손상시키지 않고 유효성 검사 또는 기타 내부 기능을 나중에 추가 할 수 있습니다.
Robert Harvey

21

의존성 주입 에 대해 이야기하고있는 것 같습니다 . 실제로는 일반적이며 IMO는 테스트 가능성에 매우 필요합니다.

코드를 테스트 가능하게 만들기 위해 코드를 수정하는 것이 좋은 아이디어인지에 대한 광범위한 질문을 해결하려면 다음과 같이 생각하십시오. 코드에는 a) 실행, b) 인간이 읽을 수있는 c, 테스트. 세 가지 모두 중요하며 코드가 세 가지 책임을 모두 수행하지 못하면 코드가 좋지 않다고 말하고 싶습니다. 따라서 수정하십시오!


DI는 주요한 질문이 아니며 (예를 들기 위해 노력하고 있었음), 요점은 테스트 목적으로 만 만들어지지 않았던 다른 방법을 제공하는 것이 좋을지 여부였습니다.
liortal

4
알아. 나는 두 번째 단락에서 "예"로 당신의 요점을 다루었다고 생각했습니다.
Jason Swett

13

약간의 닭고기와 계란 문제입니다.

코드의 테스트 범위가 좋은 가장 큰 이유 중 하나는 두려움없이 리팩토링 할 수 있기 때문입니다. 그러나 테스트 범위를 넓히려면 코드를 리팩터링해야하는 상황에 처해 있습니다! 그리고 당신의 동료는 두렵습니다.

동료의 관점을 봅니다. 당신은 (아마도) 작동하는 코드를 가지고 있으며, 어떤 이유로 든 리팩토링하면 그것을 깨뜨릴 위험이 있습니다.

그러나이 코드가 지속적인 유지 관리 및 수정이 필요한 코드 인 경우 작업을 수행 할 때마다 위험을 감수해야합니다. 그리고 리팩토링 지금 어떤 테스트 커버리지를 받고 지금 당신이 통제 된 조건 하에서, 그 위험을 감수하고, 미래의 수정을위한 더 나은 모양으로 코드를 얻을 수 있습니다.

따라서이 특정 코드베이스가 상당히 정적이고 미래에 중요한 작업을 수행 할 것으로 예상되지 않는 한, 원하는 것은 훌륭한 기술 관행입니다.

물론, 그것이 좋은 사업 관행 인지 여부 는 웜의 또 다른 캔입니다.


중요한 관심사. 일반적으로 IDE 지원 리팩토링은 자유롭게 수행 할 수 있으므로 매우 적습니다. 리팩토링이 더 복잡하다면 어쨌든 코드를 변경해야 할 때만 수행합니다. 물건을 바꾸려면 위험을 줄이기 위해 테스트가 필요하므로 비즈니스 가치도 얻습니다.
Hans-Peter Störr

요점은 : 현재 어셈블리를 쿼리 할 개체에 책임을 주거나 공용 속성을 추가하거나 메서드 서명을 변경하고 싶지 않기 때문에 이전 코드를 유지하고 싶습니까? 코드가 SRP를 위반한다고 생각하기 때문에 동료가 아무리 두려워해도 리팩터링해야합니다. 이것이 많은 사용자가 사용하는 공용 API 인 경우, 외관을 구현하는 것과 같은 전략이나 오래된 코드에 너무 많은 변경을 방지하는 인터페이스를 부여하는 데 도움이되는 전략을 고려해야합니다.
사무엘

7

이것은 단지 다른 답변에서 강조에 차이가있을 수 있습니다,하지만 난 코드가해야한다고 말하고 싶지만 하지 리팩토링 할 수 엄격하게 테스트 용이성을 개선 할 수 있습니다. 테스트 가능성은 유지 관리에 매우 중요하지만 테스트 자체는 끝 이 아닙니다 . 따라서이 코드가 비즈니스 목적을 더욱 발전시키기 위해 유지 보수가 필요할 것으로 예상 할 때까지 이러한 리팩토링을 연기합니다.

이 코드는 약간의 유지 보수를 필요로 할 것을 결정하는 시점에서, 테스트 용이성을 위해 리팩토링 할 수있는 좋은 시간이 될 것입니다. 귀하의 비즈니스 사례에 따라 모든 코드에 결국 약간의 유지 보수가 필요 하다고 가정 할 수 있습니다. 이 경우 다른 답변 ( 예 : Jason Swett의 답변 )으로 그린 구별이 사라집니다.

요약하면 : 테스트 가능성만으로는 코드 기반을 리팩토링 할 수있는 충분한 이유가 아닙니다. 테스트 가능성은 코드 기반을 유지 관리하는 데 중요한 역할을하지만 리팩토링을 유도하는 코드 기능을 수정하는 것은 비즈니스 요구 사항입니다. 그러한 비즈니스 요구 사항이 없다면 고객이 관심을 가질만한 일을하는 것이 좋습니다.

(물론 새로운 코드는 활발히 유지되고 있으므로 테스트 할 수 있도록 작성해야합니다.)


2

동료가 틀렸다고 생각합니다.

다른 사람들은 이것이 이미 좋은 이유를 언급했지만, 당신이 이것을하기 위해 앞서가는 한, 당신은 괜찮을 것입니다.

이 경고의 이유는 코드를 변경하면 코드를 다시 테스트해야하기 때문에 발생합니다. 수행 한 작업에 따라이 테스트 작업 자체가 실제로 큰 노력 일 수 있습니다.

리팩토링 결정과 회사 / 고객에게 도움이 될 새로운 기능에 대한 결정을 내릴 필요는 없습니다.


2

코드를 통한 모든 경로가 실행되는지 확인하기 위해 단위 테스트의 일부로 코드 적용 도구를 사용했습니다. 나 자신에 대한 꽤 좋은 코더 / 테스터로서, 나는 보통 코드 경로의 80-90 %를 다루고 있습니다.

발견되지 않은 길을 연구하고 그 중 일부를 위해 노력할 때, "절대 일어나지 않을"오류 사례와 같은 버그를 발견하게됩니다. 따라서 코드를 수정하고 테스트 범위를 확인하면 코드가 향상됩니다.


2

여기서 문제는 테스트 도구가 쓰레기라는 것입니다. 이 간단한 예제는 정말 간단하고 수정하기 쉽지만 훨씬 더 복잡한 일이 발생하기 때문에 해당 객체를 모의하고 테스트 메소드를 변경하지 않고 호출 할 수 있어야합니다.

많은 사람들이 이러한 코드 변경이 필요한 조롱 및 단위 테스트 도구를 사용하여 단위 테스트를 가능하게하기 위해 IoC, DI 및 인터페이스 기반 클래스를 도입하기 위해 코드를 수정했습니다. 나는 그것들이 건강하다는 것을 얇게 생각하지 않습니다. 매우 간단하고 간단한 코드가 각각의 모든 클래스 메소드를 다른 모든 것과 완전히 분리시켜야 할 필요성으로 인해 복잡한 상호 작용의 악몽 혼란으로 바뀌는 것을 볼 때 . 그리고 부상에 대한 모욕을 추가하기 위해 개인 메소드를 단위 테스트해야하는지 여부에 대한 많은 논란이 있습니다! (물론 그들은 무엇을해야합니까?

물론 문제는 테스트 툴링의 특성에 있습니다.

이러한 설계 변경 사항을 영원히 적용 할 수있는 더 나은 도구가 제공됩니다. Microsoft에는 정적 객체를 포함하여 콘크리트 객체를 스터브 할 수있는 가짜 (nee Moles)가 있으므로 더 이상 도구에 맞게 코드를 변경할 필요가 없습니다. 귀하의 경우 Fakes를 사용하는 경우 GetTypes 호출을 유효하고 유효하지 않은 테스트 데이터를 반환 한 자신의 것으로 바꾸어야합니다. 이는 매우 중요하지만 제안 된 변경은 전혀 제공하지 않습니다.

대답 : 동료는 옳지 만 잘못된 이유로 가능합니다. 테스트 할 코드를 변경하거나 테스트 도구 (또는 전체 테스트 전략을 세분화하지 않고보다 통합적인 단위 테스트를 수행하도록 변경)를 변경하지 마십시오.

Martin Fowler는 그의 기사 Mocks aren 's Stubs 에서이 영역에 대해 토론했습니다.


1

일반적으로 단위 테스트 및 디버그 로그 를 사용하는 것이 좋습니다 . 단위 테스트를 통해 프로그램을 더 변경해도 이전 기능이 중단되지 않는지 확인할 수 있습니다. 디버그 로그는 런타임시 프로그램을 추적하는 데 도움이됩니다.
때로는 그 이상으로도 테스트 목적으로 만 무언가가 필요합니다. 이를 위해 코드를 변경하는 것은 드문 일이 아닙니다. 그러나 이로 인해 프로덕션 코드가 영향을받지 않도록주의해야합니다. C ++ 및 C에서는 컴파일 시간 엔터티 인 MACRO를 사용하여이 작업을 수행합니다 . 그런 다음 프로덕션 환경에서는 테스트 코드가 전혀 표시되지 않습니다. 그러한 조항이 C #에 있는지 모릅니다.
또한 프로그램에 테스트 코드를 추가 할 때 명확하게 표시 되어야합니다이 코드 부분은 테스트 목적으로 추가되었습니다. 그렇지 않으면 코드를 이해하려는 개발자는 단순히 코드의 해당 부분에 땀을 흘릴 것입니다.


1

예제 간에는 몇 가지 심각한 차이점이 있습니다. 의 경우 현재 어셈블리의 유형에 적용 할 수 DoSomethingOnAllTypes()있는 의미가 do something있습니다. 그러나 DoSomething(Assembly asm)명시 적으로 당신이 통과 할 수 있음을 나타냅니다 어떤 그것에 어셈블리.

내가 지적한 이유는 테스트를위한 많은 의존성 주입 전용 단계가 원래 객체의 경계를 벗어난 단계이기 때문입니다. 나는 당신이 " '모든 것을 공개하는 것 "이 아니라고 말 했지만, 그것은 그 모델의 가장 큰 오류 중 하나이며, 그다음에 밀접하게 사용됩니다.


0

귀하의 질문에 동료가 주장한 내용이 많지 않아 추측의 여지가 있습니다.

"나쁜 연습"여부가에 따라 방법 변경이 이루어집니다.

내 선택에 따라 방법을 추출하는 예제 DoSomething(types)는 괜찮습니다.

그러나 나는 이것처럼 좋지 않은 코드를 보았다 :

public void DoSomethingOnAllTypes()
{
  var types = (!testmode) 
      ? Assembly.GetExecutingAssembly().GetTypes() 
      : getTypesFromTestgenerator();

  foreach (var currentType in types)
  {
     if (!testmode)
     {
        // do something with this type that made the unittest fail and should be skipped.
     }
     // do something with this type (e.g: read it's attributes, process, etc).
  }
}

이러한 변경 사항으로 인해 가능한 코드 경로 수가 증가했기 때문에 코드를 이해하기가 더 어려워졌습니다.

내가 뭘으로 의미 하는 방법 :

실제 구현이 있고 "테스트 기능 구현"을 위해 변경 한 경우 DoSomething()메소드 가 손상되었을 수 있으므로 애플리케이션을 다시 테스트해야합니다 .

if (!testmode)추출 방법보다 더 이해하기 difficulilt 및 테스트입니다.

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