가차 없는 테스트

Posted by epicdev Archive : 2011. 10. 6. 21:01
개발자 대부분은 테스트를 싫어한다. 코드가 어디에서 깨지는지 무의식적으로 알고 약한 지점을 피해 다니면서, 살살 테스트하려 한다. 실용주의 프로그래머들은 다르다. 우리는 지금 당장 버그를 찾아 나서도록 내몰리지만, 그 대신 나중에 다른 사람이 자기 버그를 발견하게 되는 수치를 피할 수 있는 것이다.

버그 찾기는 그물낚시와 비슷하다. 잔챙이를 잡기 위해 촘촘한 그물(단위 테스트)을 쓰기도 하고, 식인상어를 잡기 위해 크고 성긴 그물(통합 테스트)을 쓰기도 한다. 때때로 고기가 용케 도망가기도 한다. 그렇게 되면 프로젝트 웅덩이에서 헤엄쳐 다니는 미끌미끌한 결함들을 많이많이 잡기 위해 구멍 난 데를 있는 대로 찾아다니며 막아야 하는 것이다.

<실용주의 프로그래머 팁>
일찍 테스트하고, 자주 테스트하라. 자동으로 테스트하라.

코드를 작성하자마자 테스트해야 한다. 그 작은 잔챙이들은 꽤나 빨리 자라나 사람을 잡아먹는 거대한 상어가 되는 고약한 성질이 있다. 상어를 잡는 일은 상당히 힘들다. 하지만 그렇다고 그 모든 테스트를 손으로 할 수는 없다.

프로젝트 테스트 계획을 상세하게 짜는 팀도 많다. 심지어 그걸 쓰는 팀도 간혹 있다. 하지만 우리가 보기에 자동화 된 테스트를 사용하는 팀이 성공의 기회가 훨씬 많다. 빌드 할 때마다 하는 테스트는 책장에 꽂아 놓은 테스트 계획보다 훨씬 효과적이다.

버그가 빨리 발견 될수록 고치는 비용이 적어진다. '코드 조금, 테스트 조금'은 스몰토크 세계에서는 유명한 격언이다. 우리는 제품 코드를 만드는 것과 동시에(혹은 이전에) 테스트 코드를 만듦으로써 그 주문을 우리 것으로 할 수 있다.

사실, 훌륭한 프로젝트에는 제품 코드보다도 테스트 코드가 더 많을지 모른다. 테스트 코드를 만들기 위해 소요되는 시간에는 그 노력만큼의 가치가 있다. 길게 보면 이쪽이 훨씬 더 싸게 들며, 결함이 영어 가까운 제품을 만드는 꿈이 정말 이루어지기도 한다.

이 외에도 테스트를 통과했다는 것은 그 코드가 '완료되었다'고 말할 수 있는 높은 수준의 확신을 갖게 하는 것이다.

<실용주의 프로그래머 팁>
모든 테스트가 통과하기 전엔 코딩이 다 된 게 아니다.

우리는 프로젝트 범위에서 이루어지는 테스트의 세 가지 주요 면모를 살펴 보아야 한다. 무엇을 테스트 할지, 어떻게 테스트 할지, 그리고 언제 테스트할지.


무엇을 테스트할지
 

수행해야 할 소프트웨어 테스트에는 대여섯 가지 주요 유형이 있다.

  • 단위 테스트
  • 통합 테스트
  • 유효성 평가와 검증
  • 자원 고갈, 에러, 그리고 복구
  • 성능 테스트
  • 사용 편의성 테스트

어떻게 테스트할지

  • 회귀 테스트
  • 테스트 데이터
  • GUI 시스템 구동
  • 테스트를 테스트하기
  • 철저히 테스트하기

테스트 데이터
 

여기에는 오직 두 종류의 데이터가 있다. 실세계 데이터와 합성 데이터다. 실제로는 이 둘을 모두 사용해야 하는데, 두 데이터가 갖는 다른 특징들이 소프트웨어에서 다른 종류의 버그를 노출시켜 주기 때문이다.

실세계 데이터는 현실에서 온다. 기존 시스템, 경쟁사의 시스템 혹은 어떤 종류의 프로토타입 등에서 자료를 수집한다. 이는 전형적인 사용자 자료이다.
합성 데이터는 어떤 통계적 조건하에서 인공적으로 생성된다.


테스트를 테스트하기
 

완벽한 소프트웨어를 작성 할 수 없기 때문에, 완벽한 소프트웨어 역시 작성 할 수 없다. 그렇다면 테스트를 테스트할 필요가 있다.
어떤 버그를 감지해 내는 테스트를 작성한 후에, 그 버그가 의도적으로 생기도록 한 다음 테스트가 불평을 해대는지 확인하라. 이렇게 하면 실제로 버그가 생겼을 때 테스트가 그걸 잡아 낼 것이라고 확신 할 수 있다.

<실용주의 프로그래머 팁>
파괴자를 써서 테스트를 테스트하라.

정말 테스트에 대해 심각하게 생각한다면, 프로젝트 파괴자를 임명 할 수 있다. 파괴자의 역할은 소스 트리의 카피를 별도로 만들어 취한 다음, 고의로 버그를 심고 테스트가 잡아 낼지 검증하는 것이다.


철저한 테스트
 

테스트가 올바르다는 확신이 들고,  여러분이 만든 버그도 찾아낸다면, 코드베이스를 충분히 철저하게 테스트했다는 것을 어떻게 알 수 있을까?
 
한마디로 답하자면 '알 수 없다'. 그리고 앞으로도 알 수 없을 것이다. 하지만 시장에는 여기에 도움되는 상품들이 있다. 커버리지 분석 도구는 테스트 중에 코드를 지켜보고, 코드의 어느 라인이 실행되지 않았는지 기억한다. 이런 도구들 덕에 여러분의 테스트가 얼마나 포괄적인지에 대한 대체적인 느낌을 가질 수 있다. 하지만 100% 커버리지를 기대하지는 마라.

우연히 코드의 모든 라인이 실행 될지라도, 그게 전부가 아니다. 정말로 중요한 것은 프로그램이 갖는 상태의 개수다. 상태는 코드 라인들과 동등하지 않다.
예컨데, 0에서 999사이의 정수 두 개를 받는 함수를 가정 해 보자.

int test(int a, int b) {
    return a / (a + b)
}

이 세 줄짜리 함수는 이론상으로 1,000,000가지의 논리적 상태를 갖는다. 그 가운데 999,999개는 제대로 작동 할 것이고, 하나는 그렇지 못할 것이다(a, b가 모두 0일 때). 코드의 이 줄을 실행했다는 것을 아는 것만으로는 이런 사실이 드러나지 않는다. 프로그램의 모든 가능한 상태를 분별해야 할 것이다. 불행히도, 일반적으로 이것은 정말로 어려운 문제다.

<실용주의 프로그래머 팁>
코드 커버리지보다 상태 커버리지를 테스트하라.

심지어 훌륭한 코드 커버리지가 있어도 테스트를 위해 사용하는 데이터는 여전히 상당한 영향을 미칠 뿐 아니라, 이보다 더 중요하게 여러분이 코드를 실행하는 순서가 가장 큰 영향을 미칠 수 있다.


언제 테스트할까

많은 프로젝트에서 사람들은 테스트를 마지막 일 분까지 미룬다. 데드라인의 날카로운 모서리에 닿는 순간까지. 그것보다는 훨씬 일찍 시작해야 한다. 실제 제품에 들어갈 코드는 나오자마자 테스트해야 한다.

테스트는 대부분 자동화 되어야 한다. 여기에서 중요한 것은, 우리의 '자동화'는 테스트 결과 해석의 자동화를 포함한다는 점이다.


그물 조이기

마지막으로 테스트에서 가장 중요한 개념을 밝히고자 한다. 뻔한 것이고, 거의 모든 교과서에서 이렇게 하라고 말하고 있다. 하지만 무슨 이유에서인지 대다수 프로젝트에서 지켜지지 않는다.

현존하는 테스트의 그물을 빠져 나가는 버그가 있으면, 다음번에는 그걸 잡아 낼 수 있도록 새 테스트를 추가해야 한다.

<실용주의 프로그래머 팁>
버그는 한 번만 잡아라.

 인간 테스터가 버그를 찾아내면, 그 때가 인간 테스터가 그 버그를 찾는 마지막 순간이 되어야 한다. 그 순간 이후부터는 무조건, 매번, 예외 없이, 아무리 사소한 것일지라도, 개발자가 "그건 앞으로 절대 다시 일어나지 않을 겁니다."라고 불평을 하더라도 해당 버그를 확인 할 수 있게 자동화 테스트들을 수정해야 한다.

왜냐면 그런 일은 앞으로 다시 일어날 것이기 떄문이다. 게다가 우린 자동화 테스트가 우리를 대신해 찾아 줄 버그까지 추격할 시간이 없다. 우리는 새 코드를(그리고 새 버그도) 작성하는 데 시간을 보내야 한다.


실용주의프로그래머
카테고리 컴퓨터/IT > 프로그래밍/언어
지은이 앤드류 헌트 (인사이트, 2007년)
상세보기
 
  

소프트웨어를 테스트하라

Posted by epicdev Archive : 2011. 10. 5. 10:52
<실용주의 프로그래머 팁>
소프트웨어를 테스트하라. 그렇지 않으면 사용자가 테스트하게 될 것이다.


실용주의프로그래머
카테고리 컴퓨터/IT > 프로그래밍/언어
지은이 앤드류 헌트 (인사이트, 2007년)
상세보기
 

'Archive' 카테고리의 다른 글

가차 없는 테스트  (0) 2011.10.06
다익스트라의 테스팅 관련 명언  (0) 2011.10.06
테스트하기 쉬운 코드  (0) 2011.10.05
/etc/passwd 파일의 포맷  (0) 2011.10.04
Stop Over-Engineering  (0) 2011.10.04
  

테스트하기 쉬운 코드

Posted by epicdev Archive : 2011. 10. 5. 09:45
테스트 가능성이 높은 코드는 디자인에 좋다. 재미있게도, 디자인을 잘 만들려고 할 때보다 테스트 가능성을 높이려고 했을 때 결과 코드의 디자인이 더 나은 경우가 많다.


실용주의프로그래머
카테고리 컴퓨터/IT > 프로그래밍/언어
지은이 앤드류 헌트 (인사이트, 2007년)
상세보기
 

'Archive' 카테고리의 다른 글

다익스트라의 테스팅 관련 명언  (0) 2011.10.06
소프트웨어를 테스트하라  (0) 2011.10.05
/etc/passwd 파일의 포맷  (0) 2011.10.04
Stop Over-Engineering  (0) 2011.10.04
디자인 패턴 공부 순서  (0) 2011.10.04
  

리팩토링

Posted by epicdev Archive : 2011. 10. 4. 16:53
리팩토링이 필요한 코드를 일종의 '종양'이라고 생각하자. 종양을 제거하려면 수술이 필요하다. 지금 바로 수술해서 아직 종양이 작을 때 제거 할 수도 있다. 하지만 종양이 자라고 다른 곳으로 전이 할 때까지 놓아 둘 수도 있다. 하지만 그 때가 되면 제거하는 데 드는 비용도 더 커질 뿐더러 위험도 훨씬 커진다. 시간을 더 끌면, 환자는 생명을 잃을지도 모른다.

<실용주의 프로그래머 팁>
일찍 리팩토링하고, 자주 리팩토링하라

리팩토링해야 할 것들의 명단을 만들고 유지하라. 어떤 것을 지금 당장 리팩토링하기 힘들다면, 일정에 그것을 리팩토링 할 시간을 확실히 포함시켜 두도록 한다. 그 코드를 사용하는 사람들이 코드가 조만간 리팩토링 될 것이라는 사실과 그 사실이 그들의 코드에 어떤 영향을 주게 될지 인지하도록 만들어야 한다.

리팩토링은 천천히, 신중하게, 조심스럽게 진행해야 하는 작업이다. 마틴 파울러는 손해보다 이득이 큰 방향으로 리팩토링을 하기 위한 다음 몇가지 간단한 조언을 제공한다.

1. 리팩토링과 새로운 기능 추가를 동시에 하지 말라.
2. 리팩토링을 시작하기 전 든든한 테스트 집합이 있는지 먼저 확인한다. 할 수 있는 한 자주 테스트들을 돌려본다. 이렇게 하면 여러분의 변경 때문에 무엇이 망가졌을 경우 재빨리 그 사실을 알 수 있다.
3. 단계를 작게 나누어서 신중하게 작업한다. 필드를 한 클래스에서 다른 클래스로 옮기기, 비슷한 메소드를 합쳐서 수퍼클래스로 옮기기. 리팩토링에서는 국지적인 변경들이 많이 모여서 커다란 규모의 변화를 낳는 일이 자주 발생한다. 단계를 작게 나누고, 한 단계가 끝날 때마다 테스트를 돌린다면, 기나긴 시간의 디버깅 작업을 피할 수 있다.

모듈에 큰 변화가 있다면, 즉 모듈의 인터페이스나 기능을 이전과 호환성을 유지 할 수 없을 정도로 변경하는 변화가 있다면, 일부러 빌드를 실패하도록 변화를 주는 기법도 유용하다. 리팩토링 대상 코드에 의존하는 옛날 코드들이 컴파일이 안 되게 만들어 버리는 것이다. 그러면 리팩토링 대상 코드에 어떤 코드들이 의존하는지 쉽게 찾아내서 지금 상황에 맞도록 고칠 수 있다.

그러므로 다음 번에 여러분이 마땅하다고 생각하는 수준에 못 미치는 코드를 보게 되면, 그 코드와 더불어 그 코드에 의존하는 모든 것도 함께 고치도록 한다. 고통을 관리하자. 지금 고통스럽더라도, 앞으로 더욱 고통스러워질 것 같으면 지금 고치는 편이 낫다.

실용주의프로그래머
카테고리 컴퓨터/IT > 프로그래밍/언어
지은이 앤드류 헌트 (인사이트, 2007년)
상세보기
  

동시성을 고려한 설계를 하면 좋은 이유

Posted by epicdev Archive : 2011. 10. 4. 11:03
동시성 요소가 포함 된 아키텍처를 설계 한 다음에는, 수많은 동시적 서비스들을 다루는 것에 대해 생각하기도 더 쉬워진다. 동시성 모델은 도처에 스며든다.

이제 애플리케이션을 어떻게 배치할지, 곧 독립 애플리케이션으로 할지, 클라이언트-서버로 할지, n-티어로 할지 결정하는 문제에 대해서도 유연하게 대응 할 수 있다. 시스템을 독립적인 서비스들로 구성 된 아키텍처로 만듦으로써, 설정 역시 동적으로 만들 수 있다. 동시성을 고려해서 계획하고 작업들의 시간적 결합을 끊음으로써, 동시성을 이용하지 않기로 선택한, 독립 애플리케이션을 포함해서 모든 옵션을 다 이용 할 수 있게 된다.

다른 길을 가는 것(비동시적 애플리케이션에 동시성을 추가하려고 하는 것)은 훨씬 힘들다. 동시성을 허용하도록 설계한다면, 확장가능성이나 성능에 대한 요구사항이 들어올 때 더 쉽게 그것에 맞추어 줄 수 있으며, 그런 일이 들어오지 않더라도 여전히 깔끔한 설계의 이점을 누리게 된다.

 
실용주의프로그래머
카테고리 컴퓨터/IT > 프로그래밍/언어
지은이 앤드류 헌트 (인사이트, 2007년)
상세보기
  
쓰레드를 사용하는 프로그래밍은 몇 가지 설계상의 제약을 받게 되는데, 이것은 좋은 일이다. 이 제약들은 워낙 도움이 많이 되어서 다른 어떤 프로그래밍을 하더라도 꼭 지키고 싶어지는 것이다. 이 제약들은 코드의 결합을 끊고 '우연에 맡기는 프로그래밍'과 싸우는 데 도움이 된다.

직선형 코드에서는 엄밀하지 않은 프로그래밍으로 이끌리는 전제들을 남발하기 쉽다. 하지만 동시성을 염두에 둔다면 여러 가지 일들을 더 주의 깊게 생각하게 될 수 밖에 없다. 더 이상 혼자 마음대로 놀 수 없는 것이다. 이제는 여러 일이 '동시에' 일어 날 수 있기 때문에, 갑자기 전에 못 보던 시간에 관련된 의존성들이 보이기 시작한다.

일단 제일 먼저, 모든 전역 변수나 정적 변수들을 동시 접근으로부터 보호해야 한다. 지금이 왜 애초에 전역 변수가 필요했는지 스스로에게 물을 수 있는 좋은 기회다. 게다가, 호출 순서와 관계없이 일관성 있는 상태 정보를 보일 수 있는지도 확인 해 봐야 한다. 예를 들어 언제 객체의 상태에 대해 물을 수 있는가? 만약 어떤 호출들 사이에서 객체가 유효하지 않은 상태에 있다면, 여러분은 아무도 그 시점에서는 그 객체를 호출하지 않을 것이라는 우연에 기대고 있는 셈이다.

위젯이 먼저 만들어지고 그 다음에 화면에 표시되는 두 단계 구조의 윈도우 하위시스템이 있다고 해보자. 위젯이 화면에 나오기 전에는 위젯의 상태를 설정 할 수 없다. 코드가 어떻게 되어있는지에 따라, 생성된 위젯이 화면에 보이기 전까지는 다른 객체가 그 위젯을 사용 할 수 없다는 사실에 의존하고 있는지도 모른다.

하지만 동시성 있는 시스템에서는 이것이 사실이 아닐지도 모른다. 객체는 호출 될 때 언제나 유효한 상태에 있어야 하는데, 객체들은 가장 불편한 시각에도 호출 될 수 있는 것이다. 호출 될 가능성이 있는 모든 시간에 언제나 객체가 유효한 상태에 있도록 만들어야 한다. 이 문제는 생성자와 초기화 루틴을 별개로 정의하는 클래스에서 종종 나타난다.

<실용주의 프로그래머 팁>
언제나 동시성을 고려해 설계하라

 
실용주의프로그래머
카테고리 컴퓨터/IT > 프로그래밍/언어
지은이 앤드류 헌트 (인사이트, 2007년)
상세보기

'Archive' 카테고리의 다른 글

리팩토링  (0) 2011.10.04
동시성을 고려한 설계를 하면 좋은 이유  (0) 2011.10.04
세부사항을 코드에서 몰아내라  (0) 2011.10.03
Camera의 setDisplayOrientation 메소드  (0) 2011.10.03
Java 필드 초기화  (0) 2011.10.03
  

세부사항을 코드에서 몰아내라

Posted by epicdev Archive : 2011. 10. 3. 22:57
세부사항은 우리의 깔끔한 코드를 어질러 놓는다. 특히 변화가 잦을 때는 더더욱 그러하다. 그러므로 우리는 "세부사항에서 벗어나라!"고 말한다. 세부사항을 코드에서 몰아내라. 이렇게 함으로써 우리의 코드는 매우 설정 가능(configurable)하게 되고 '소프트' 해진다. 즉 변화에 쉽게 적응 할 수 있게 되는 것이다.

우선 시스템을 되도록 설정가능하게 만들기 바란다. 배경 색, 프롬프트 텍스트 뿐 아니라 알고리즘의 선택, 사용 할 데이터베이스 제품, 미들웨어 기술, 사용자 인터페이스 스타일 등 시스템의 심층까지 말이다. 이런 아이템들은 통합하거나 엔지니어링하지 말고 설정 옵션으로 구현해야 한다.

<실용주의 프로그래머 팁>
 통합하지 말고 설정하라

<실용주의 프로그래머 팁>
코드에는 추상화를, 메타데이터에는 세부 내용을

실용주의프로그래머
카테고리 컴퓨터/IT > 프로그래밍/언어
지은이 앤드류 헌트 (인사이트, 2007년)
상세보기
  

디미터 함수 법칙 (혹은 디미터 법칙)

Posted by epicdev Archive : 2011. 10. 3. 14:39
디미터 법칙은 객체의 모든 메소드는 다음에 해당하는 메소드만을 호출해야 한다고 말한다.

1. 객체 자신의 메소드
2. 메소드의 매개변수로 넘어온 인자의 메소드
3. 메소드 내부에서 생성 된 객체의 메소드
4. 메소드가 포함하고 있는 객체의 메소드

class Demeter {
    private A a;

    private int func() { return 0; }

    public void example(B b) {
        C c = new C();
        int f = func(); // 1번의 경우
        b.invert(); // 2번의 경우
        a = new A();
        a.setActive(); // 3번의 경우
        c.print(); // 4번의 경우
    }
}
실용주의프로그래머
카테고리 컴퓨터/IT > 프로그래밍/언어
지은이 앤드류 헌트 (인사이트, 2007년)
상세보기

'Archive' 카테고리의 다른 글

Camera의 setDisplayOrientation 메소드  (0) 2011.10.03
Java 필드 초기화  (0) 2011.10.03
패키지의 순환적 의존성  (0) 2011.10.03
응답집합  (0) 2011.10.03
객체간의 의존 관계가 combinatorial explosion 할 때의 징후  (0) 2011.10.03
  

패키지의 순환적 의존성

Posted by epicdev Archive : 2011. 10. 3. 14:25
패키지 구성의 원리 중 패키지 간의 의존 관계가 순환관계를 형성하면 안 된다는 ADP (Acyclic Dependencies Principle)이 있다. 뭔가를 고치면 그 영향이 다른 것들을 거쳐 전파되다가 다시 애초의 시작점으로 되먹임 되어 순환 할 수 있기 때문이다.

 
실용주의프로그래머
카테고리 컴퓨터/IT > 프로그래밍/언어
지은이 앤드류 헌트 (인사이트, 2007년)
상세보기
  

응답집합

Posted by epicdev Archive : 2011. 10. 3. 14:21
응답집합 (response set 혹은 RFC 혹은 response for a class)이 큰 클래스는 작은 클래스보다 에러를 발생시키기 쉽다고 한다. 이 때 응답집합은 클래스의 메소드가 직접 호출하는 함수의 수를 의미한다.


응답집합의 정의

클래스의 객체에 메시지가 보내졌을 때, 그 결과로 호출되는 모든 (내외부) 메소드들의 집합의 원소 개수
대략적인 근사값을 얻기 위해서는 해당 클래스 메소드 바디에서의 메소드 호출 종류만 세기도 한다. 응답집합은 테스트 가능성에 대한 직접적인 metric 중 하나다.

 
실용주의프로그래머
카테고리 컴퓨터/IT > 프로그래밍/언어
지은이 앤드류 헌트 (인사이트, 2007년)
상세보기
  
 «이전 1 2 3  다음»