Python Clean Code


8. 단위 테스트와 리팩토링

  • 애자일과 자동화된 테스트
  • 단위테스트와 코드 품질, 이를 도와줄 프레임워크와 도구
  • 단위테스트와 문서화, 도메인 문제
  • TDD

8-1. 디자인 원칙과 단위테스트

단위 테스트는 다른 코드의 일부분이 유효한지를 검사하는 코드.
단위 테스트는 소프트웨어의 핵심.

단위 테스트는 비즈니스 로직이 특정조건을 보장하는지 확인하기 위해 여러 시나리오를 검증하는 코드

  • 격리: 다른 외부 에이전트와 완전히 독립. 비즈니스 로직에만 집중. 임의의 순서로 실행될 수 있어야 한다.
  • 성능: 반복적으로 여러 번 실행될 수 있어야 한다.
  • 자체 검증: 실행만으로 결과를 결정할 수 있어야 한다.

테스트 도구에서 파일의 내용을 호출하면 테스트가 실행된다.
실패하면 프로세스는 오류 코드와 함께 종료.
일반적으로 테스트에 성공하면 ‘.’을 출력하고 실패하면 ‘F’,예외가 있으면 ‘E’를 출력한다.


자동화된 다른 테스트

단위테스트는 함수 또는 메서드와 같은 매우 작은 단위를 확인하기 위한 것이다. 단위 테스트는 최대한 자세하게 코드를 검사하는 것이 목적이다.

클래스를 테스트하려면 단위 테스트가 아닌 단위 테스트의 집합인 테스트 스위트(test suite)를 사용한다.

단위 테스트는 여러 방법으로 할 수 있으며, 모든 오류를 잡을 수 있는 것도 아니다. 인수 테스트통합 테스트 같은 것도 있다.

통합 테스트에서는 한 번에 여러 컴포넌트를 테스트. 종합적으로 잘 동작하는지 확인.

인수테스트는 유스케이스(use case)활용하여 사용자의 관점에서 시스템의 유효성을 검사.

이 두 가지 테스트는 시간이 많이 걸린다.

좋은 개발환경을 구축했다면:
개발자는 전체 테스트 스위트를 만들고 코드에 수정이 생길 때마다 반복적으로 단위 테스트리팩토링을 할 수 있어야 한다.

코드를 수정하면 CI서비스가 실행되어 해당 브랜치에 빌드를 실행.
통합 테스트나 인수 테스트가 있는 경우는 빌드 중에 단위테스트도 함께 수행.
일반적으로 단위 테스트는 항상 수행되길 원하고, 통합테스트와 인수테스트는 그보다 덜 자주 수행되길 바란다.

실용성이 이상보다 우선


단위 테스트와 애자일

최근의 소프트웨어 개발은 가능한 신속하고도 지속적으로 가치를 제공하려고 한다.
변화에 효과적으로 대응할 수 있는 소프트웨어를 개발하고자 한다면 유연하며 확장 가능해야 한다.

코드 자체만으로는 변경에 충분히 유연하다는 보장을 할 수 없다.
유연한지 확장가능한지 공식적인 증거가 없다면 확신 있게 답변할 수 없을 것이다.

단위테스트가 바로 프로그램이 명세에 따라 정확하게 동작한다는 증거가 될 수 있다.
단위 테스트(혹은 자동화된 테스트) 우리의 코드가 기대한 것처럼 동작한다는 확신을 줄 수 있는 안전망이 될 수 있다.

이러한 도구로 무장한 코드가 있으면 보다 효율적으로 개발이 진행될 것이다. 궁극적으로 팀의 개발 속도(또는 범위)를 향상 시킬 수 있다.


테스트 경계

테스트에는 노력이 필요하다. 무엇을 테스트 할지 주의하지 않으면 끝없이 테스트 해야 하고 뚜렷한 결실도 없이 시간만 낭비.

테스트의 범위는 우리가 작성한 코드의 범위로 한정.

그렇지 않고 외부라이브러리나 모듈과 같은 의존성까지 확인해야한다면, 의존성의 의존성을 확인해야하고, 끝없는 여행을 해야 할지도 모른다.

외부 의존성에 대해 올바른 파라미터를 호출하면 정상적으로 실행된다는 것만 확인해도 충분하다. 이보다 더 많은 노력을 할 필요는 없다.


8-2 테스트를 위한 프레임워크와 도구

  • unitest

테스트를 위한 파이썬 표준 라이브러리.
모든 종류의 테스트를 작성할 수 있는 풍부한 API를 제공

  • pytest pip install pytest를 통해 설치.

코드 커버리지

테스트 코드가 실행될 때 얼마나 많은 코드를 실행되었나를 나타내는 지표.

  • coverage pip install coverage

모든 CI 도구와 쉽게 통합가능. 가장 권장되는 기능은 테스트되지 않은 행을 알려주는 기능.

높은 테스트 커버리지를 갖는 것은 좋은 것이지만, 클린코드를 위한 조건으로는 부족하다.

맹점

라인이 실행되었다는 것이 가능한 모든 조합에 대해 테스트되었다는 것을 의미하는 것은 전혀 아니다.
제공된 데이터에 대해 테스트 통과했다는 것은 해당 데이터에 대해 문제가 없다는 것이지, 그 이외의 모든 데이터 조합에 대해서도 안전하다는 것을 의미하는 것은 전혀 아니다.


모의(mock)객체

어떤 시스템이 실제로 서비스되기 위해서는 외부 서비스와 연결을 하게 된다.
이런 외부 서비스에는 필연적으로 부작용이 존재한다.

단위테스트에서는 외부서비스를 호출하여 사용하지 않는다. 단지 호출 되는지만 확인한다.

테스트 더블: 더미(dummy), 스텁(stub), 스파이(spy),모의(mock) 와 같이 테스트 스위트에서 실제 코드 대신 실제인 것처럼 동작하는 코드를 말한다.

모의객체는 가장 일반적인 유형의 융통성 있는 객체.
호출시 응답해야하는 값이나 행동을 특정할 수 있다.
이를 통해 애플리케이션의 동작을 검증한다.


8-3. 리팩토링

리팩토링은 소프트웨어 유지 관리에서 중요한 활동이지만 단위 테스트가 없다면 정확성을 보장받기 어려울 것이다.

코드를 리팩토링할 때는 구조를 개선하여 보다 나은 코드로 만들려는 경우가 있고, 때로는 좀 더 일반적인 코드로 수정하여 가독성을 높이려는 경우가 있다. 중요한 점은 이러한 수정 작업 이전과 이후가 완전히 동일한 기능을 유지해야 한다는 것이다.

회귀 테스트(regression test) 를 실행해야 함을 의미한다.
이를 효율적으로 수행하려면 자동화하는 것이 유일한 방법이다. 자동 테스트의 가장 효율적인 버전이 바로 단위 테스트이다.


단위 테스트에 대한 추가 논의

단위 테스트를 통해 코드에 대한 확신을 얻지만, 충분한 단위테스트였는지 어떻게 알 수 있을까?
속성기반의 테스트, 변형 테스트를 통해 해답을 얻을 수 있다.

속성기반 테스트: 이전 단위 테스트에서 다루지 않았던 것으로 테스트를 실패하게 만드는 데이터를 찾는 것. hypothesis 라이브러리가 유용

변형테스트: 원래 코드를 변경한 새로운 버전(돌연변이 - mutant)으로 코드가 수정되고 테스트를 통해 돌연변이를 죽임(kill). 이때 돌연변이가 생존하게 되면 나쁜 징후임.

변형테스트의 경우 품질을 보장하는 좋은 방법이지만, 분석에 약간의 노력과 주의가 필요. 비용 또한 많이 듬.


8-4. 테스트 주도 개발 간략 소개

TDD의 요점은 기능의 결함으로 실패하게 될 테스트를 상용화 전에 미리 작성해야 한다는 것이다.

  1. 구현 내용을 기술하는 단위테스트 작성 후 테스트
  2. 해당 조건을 충족시키는 최소한의 필수 코드를 구현하고 테스트
  3. 리팩토링

이러한 사이클이 red-green-refactor 처음에는 코드가 없으므로 테스트가 실패 (빨간색) 다음에는 통과 (초록색) 마지막은 리팩토링. 이 과정을 반복.