ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 11. 테스트 하기
    Technique/Programmer 2017. 2. 22. 01:41
    반응형

    TDD는 더 나은 소프트웨어를 만들기 위한 중요한 기법 중 하나다. 그런데 테스트 주도와 유닛 테스트가 실제로 무엇을 의미하는가에 대해서는 명확하지 않은 점이 있다. 우선 이를 명확히 한 뒤 개발자 테스트에 대한 적절한 방법을 찾아본다면, 더 나은 코드를 짤 수 있을 것이다.


    1. 왜 테스트 하는가?

    자신의 코드는 자신이 테스트해야 한다. 생각할 필요도 없는 당연한 일이다.


    1-1. 피드백 과정 줄이기

    위대한 소프트웨어를 제대로 만들기 위해서는 피드백을 받아야 한다. 가능하면 자주 그리고 빨리 피드백을 받는 것이 좋다. 좋은 텟트 전략이란 피드백 절차를 간소화하는 것으로, 이를 통해 더 효율적으로 일할 수 있다.

    피드백 과정이 짧을수록 설계 변경을 더 빠르게 반복할 수 있고 코드에 대해 더 강하게 확신할 수 있다. 문제를 빨리 알수록 수정은 시워지고 비용은 낮아진다. 그 이유는 코드를 짜는 시점에 가까운 만큼 코데에 대해 더 명확하게 파악하고 있기 때문이다.

    자동화된 테스트를 통해 피드백 과정을 줄이면, 코드 개발 과정에 도움이 될 뿐만 아니라 재활용도 가능하다는 이점이 있다. 즉, 한 번 일련의 테스트 코드를 작성하고 나면 테스트 풀에 넣어두고 계속해서 돌릴 수 있다. 


    1-2. 테스트 코드 짜기가장 이상적인 방법은 최대한 많은 개발자 테스트를 자동화하는 것이다.

    더 열심히 일하려 하기보다 더 영리하게 일하자.

    컴퓨터는 테스트를 빠르고 반복적으로 수행함으로써 피드백 과정을 줄일 수 있다.

    사실 이런 테스트 코드를 작성하려면 소중한 시간을 들여야 한다. 코드에 대한 확신은 곧 테스트 코드의 품질에 따라 결정된다. 하지만 코드의 품질을 높일수 있고 코드 작성 결과에 안전해지는 테스트 전략을 채택하는 것은 그리 어려운 일이 아니다.

    즉 더 서둘러야 하는 상황에서는 오히려 더 천천히 가라는 것이다.

    물론 테스트로 인해 속도가 더뎌질 수도 있따. 테스트를 제대로 이해하지 못하여 코드 작성 중에 ㅋ테스트로 인한 오류가 마구 발생하거나, 또는 코드 구조가 경직되어 하나의 함수 일부를 고치는 데 수백만 개의 테스트를 재작성해야 하는 경우가 그렇다.

    이는 잘못된 테스트 코드들을 작성하면 안 되는 이유가 되지 못한다는 것과 같은 이치이다.


    1-3. 누가 테스트 코드를 짜야 할까?

    코더 자신이 자신의 코드에 대한 테스트 코드를 작성하는 것이 가장 바람직하다. 코드를 작성하면서 자신이 그 코드를 테스트 하는 것이다.

    ※모든 단계의 소프트웨어 스택과 개발 프로세스에 대해 테스트를 수행해야 한다. 특히 가장 작은 범위에서 테스트해야 한다. 그래야만 피드백 과정을 줄이고, 가능한 빠르고 쉽게 고품질의 소프트웨어를 만들 수 있다.



    2. 테스트 유형

    유닛 테스트( Unit tests )
    가장 작은 단위의 기능에 대한 테스트를 단독으로 수행하는 것으로, 각각의 함수가 정확히 작동 하는지 확인하기 위한 것이다. 단독으로 즉 출시 코드에 다른 단위들이 포함되지 않은 채, 단일 단위의 코드에 대해 테스트가 수행되지 않는다면, 그것은 유닛 테스트가 아니다.

    단독이라 함은 그 어떤 외부에 대한 접속도 하지 않는다는 뜻이다. 데이터베이스와 네워크, 파일 시스템 없이 실행된다는 뜻이다.


    통합 테스트 ( Integration tests )

    각각의 단위 모듈을 더 큰 결합체로 통합하여 작동시키는 복합 기능을 검증할 수 있다. 통합된 단위 요소들이 서로 잘 통합되어 있고 정확하게 상호 작동하는지 확인할 수 있다.


    시스템 테스트 ( System tests )

    전체 시스템에 대한 요구사항 명세를 확인할 수 있다. 통합된 소프트웨어 스택 전체에 수행되는 테스트로서, 프로젝트에 대한 검수 기준으로 사용된다.

    시스템 테스트는 외부 노출 API나 시스템 시작점에 대해 실행되는 코드로 작성될 수 있다, 테스트 범위가 넓은 탓에 전체 테스트를 수행하는 데 많은 시간이 걸릴 수 있다. 많은 네트워크 트래픽이 유발되거나 느린 데이터베이스 접속이 필요할 수도 있따. 혹은 시스템 테스트를 할 때마다 테스트 대상 시스템에 테스트를 붙이거나 뗴어내느라 막대한 비용이 발생할 수 있다.



    3.언제 테스트를 작성할까?

    TDD라는 용어는 테스트 우선 개발 이라는 용어와 통합되어 있지만, 이 둘은 서로 별개의 용어이다 독단적으로 테스트를 먼저 작성하지 않아도 테스트를 통한 피드백을 통해 설계를 개선해나갈 수 있다.

    그러나 테스트 작성을 미룰수록 테스트는 덜 효과적이게 될 것이다. 코드가 어떻게 작동할지를 잊어버리거나, 극단적인 경우에 대한 테스트를 작성하지 못할 수도 있다. 극단적인 경우에 대한 테스트를 작성하지 못할 수도 있다. 혹은 아예 테스트 코드 작성을 잊어버릴 수도 있다 테스트 작성을 미룰수록 피드백 절차는 더 늦어지고 덜 효과적이게 될 것이다.

    • 다음으로 규현해야 하는 기능을 결정한 뒤 해당 기능에 대한 테스트를 작성하자
    • 테스트를 작성한 뒤 최대한 간단한 방법으로 기능을 구현하자.
    • 중요하지만 자주 간과되는 부분이 있다. 코드를 정리하라. 이상한 공통분모를 리팩토링하라, 테스트 대상 시스템을 재 구조화하여 더 나은 내부구조로 개선하자.
    • 첫 단계로 돌아가서 요구사항에 대한 테스트 케이스 전체를 통과할 때까지 테스트를 반복하자

    테스트 작성을 위한 또 하나의 적절한 시기는 바로 출시 코드에 대해 버그를 수정할 떄이다 코드 수정에 무작정 달려들기보다는 우선 버그의 원인을 설명하는 실패 유닛 테스트를 작성하라. 때로는 이 테스트를 작성하는 과정에서 다른 관련 버그를 찾아낼 수도 있따, 그런 뒤에 버그를 수정하고 테스트를 통과하도록 하라, 테스트 풀에 테스트를 집어넣고 나면 이후 다시는 똑같은 버그가 나타나지 않도록 항상 테스트 할 수 있다.



    4. 언제 테스트를 실행하는가

    각각의 기능을 구현 하면서 테스트를 계속 실행할 수 있다. 정확하고 충분하게 구현 되었는지 지속적으로 검증할 수 있다.

    버전 관리 도구에 구현 코드와 테스트 코드를 모두 추가하자. 테스트는 사라지지 않고 이전부터 존재해온 테스트 들과 함께 하게 된다 소프트웨어가 지속적으로 기대에 걸맞게 작동 하는지 확인하며, 코드와 함께 살아간다 만약 이후에 누군가 코드를 잘못 수정한다면, 너무 늦어버리기 전에 그에 대한 경고를 받게 될 것이다.

    코드 수준의 자동화된 테스트를 하더라도 출시 전 사람에 의한 QA 검수 과정은 필요하다. 지넞ㅇ한 테스트 전문가에 의한 탐색적 테슽는 매우 유용하다.

    이는 얼마나 많은 유닛 테스트나 통합 테스트, 시스템 테스트가 존재하는가와는 별개의 문제다. 자동화된 일련의 테스트를 이용하면서 QA 과정에서 간단한 버그들을 찾아내느라 시간을 낭비하지 않아도 된다. QA전문가드른 간단한 버그뿐 아니라 정말 심각한 버그들도 찾아낼 수 있다는 점을 명심하자.



    5. 무엇을 테스트할 것인가

    애플리케이션에서 중요한 부분은 모두 테스트하자. 

    테스트를 통해 요구사항에 맞게 각각의 코드가 작동하는지, 그리고 각 코드가 저확한 결과를 반환하는지 확인할 필요가 있다. 하지만 애플리케이션에 있어서 성능이 주요 요구사항이라면, 코드의 성능을 모니터링하는 테스트도 수행해야 한다. 

    서버에서 특정 시간대에 어떤 응답을 해야한다면 그 조건에 대한 테스트도 포함하자.



    6. 좋은 테스트

    좋은 테스트 코드를 작성하려면 연습과 경험이 필요하다. 테스트 작성을 시작하는 것이 가장 중요하며, 테스트가 쓸모 없을까 걱정하여 무력해 져서는 안된다.

    테스트 수행에 시간이 오래 걸릴수록 테스트 수행과 이용 빈도가 줄어들 것이고 피드백 역시 조금만 받게 될 것이다. 즉 더 적은 가체를 얻게 될 것이다.


    좋은 테스트의 특징

    • 짧고 명확한 이름을 가지고 있어 실패했을 때 무엇이 문제인지 쉽게 알 수 있다.
    • 유지 보수가 가능하다. 작성은 물론 읽고 수정 하기도 쉽다.
    • 수행에 오랜 시간이 걸리지 않는다.
    • 최신 구현 코드에 맞춰져 있다.
    • 특별한 머신 설정이 필요 없다.
    • 다른 테스트에 대 한 의존성이 없어서 특정 테스트 전후에 실행할 필요가 없다. 외부 상태나 코드상의 어떤 공유 변수에 대한 의존성이 없다.
    • 실제 구현 코드를 테스트한다.


    나쁜 테스트의 특징

    • 때로는 성공하고 때로는 실패하는 테스트
    • 이상해 보이고, 읽거나 수정하기 힘든 테스트
    • 지나치게 큰 테스트
    • 하나의 테스트 케이스에서 둘 이상을 수행하는 테스트
    • 클래스 API에 대해 개별 형태를 확인하는 것이 아닌 함수마다 공격을 하는 테스트
    • 직접 작성하지 않은 서드파티 코드에 대한 테스트
    • 클래스의 주요 기능이다 행태에 대해 실제로 테스트를 하지 않는데다 별 다른 필요없는 테스트들로 상황을 숨기는 테스트
    • 지나치게 상세하여 중구난방인 테스트
    • SUT의 내부 구현에 대한 지식을 바탕으로 한 화이트박스식 테스트( 이는 구현 변경시에 테스트도 변경해야 한드는 것을 의미 )
    • 단 하나의 머신에서만 수행 가능한 테스트



    7. 테스트는 어떻게 해야 하는가

    각 테스트에는 몇 가지 준비 과정이 필요하고 준비가 되면 실제 실행하게 되며, 마지막으로 실행 결과를 검증한다.

    배치 - 실행 - 확인 패턴이다. 유닛 테스트에서는 일반적으로 확인 단계에서 한 가지만 확인한다. 만약 여러가지를 확인해야 한다면, 하나의 테스트 케이스만으로 다룰 수 없다.

    다른 입력 값과 그에 대한 기대 결과를 다루는 테스트들도 필요하다. 각 테스트 는 새로운 테스트 함수로 만들어져야 하며, 다른 테스트에 통합되어서는 안된다.


    7-1 테스트 이름

    하나의 기능 검증에 집중하는 테스트에는 명확한 이름이 붙어 있어서 간단한 무장처럼 읽힌다. 

    만약 테스트 케이스 이름을 간단히 붙일 수 없다면, 요구사항이 모호하거나 혹은 여러가지를 한꺼번에 테스트 하려는 것일 수 있다.

    테스트의 이름을 굳이 구현 코드와 같은 형식으로 지을 필요는 없다. 각자 서로에게 맞는 영역의 연어가 있을 수 있다. 보통 더 길면서 서술적인 메서드 명에 밑줄 표시를 자유롭게 쓰는 경우를 많이 볼 수 있다.



    8 테스트 구조

    테스트가 코드의 중요기능을 모두 다룬다는 점을 보장하자. 정상적인 입력 값들은 물론, 일반적인 실패 케이스를 비롯해 빈 값이나 0과 같은은 경곗값에서 발생할 수 있는 모든 경우의 수를 고려하자.

    테스트는 중복해서 수행하지 않는다. 중복 수행은 노력과 혼란 유지 보수 비용을 가증시킨다. 각 테스트 케이스는 하나의 정의만을 검증해야한다.

    함수 단위의 테스트는 거의 소용이 없는데, 하나의 함수만 단독으로 테스트 하기가 어렵기 때문이다.

    하나의 메소드를 호출한 후에 다른 메소드를 호출해봐야만 객체의 상태를 확인할 수 있다. 코드의 특정 행태를 확인하는 테스트를 작성하자. 이를 통해 더 응집되고 명확한 테스트를 작성할 수 있다.


    8-1. 테스트 유지 보수

    테스트 코드는 구현 코드만큼 중요하므로, 테스트 코드의 외관과 구조를 적절하게 다듬어야 한다. 엉망이 되어버렸다면, 깔끔하게 다듬고 리팩토링 하자. 클래스의 행태를 변경한 탓에 테스트에 실패했다고 해서 테스트를 막아버리고 도망쳐서는 안된다 테스트 코드도 유지보수 하자. 완료 일정을 맞추기 위해 테스트 코드를 깔끔히 하는 작업을 무시하고 싶은 충동을 느낄 수도 있다. 하지만 서두르는 과정에서 세심하지 못하면 결국 뒷통수를 맞게 될 것이다.


    8-2. 테스트 프레임워크 고르기

    테스트 프레임워크에 따라 테스트 코드의 와관과 체크형태, 그리고 구조가 결정된다.

    따라서 좋은 유닛 테스트 프레임워크를 선택하는 게 중요하다. 너무 복잡하거나 기능아 많을 필요는 없다. 거추장스러운 도구는 고르지 않는 편이 낫다.



    9. 어떤 코드도 혼자가 아니다.

    유닛 테스트 코드를 작성할 때는 독립화된 유닛의 코드를 테스트 대상 시스템에 넣는 것을 목표로 한다,

    유닛은 나머지 시스템 없이도 존재할 수 있어야 한다.

    하나의 코드 유닛이 제공하는 인터페이스는 단순히 어떤 메소드이거나 멤버함수, 이벤트, 속성일 수 있따, 또는 콜백 형태의 인터페이스 일 수도 있다. 이러한 인터페이스는 API를 통해 상호 작동하는 객채들에 의해 결정된다.

    하나의 클래스와 성호작동하는 객체들은 생성자의 매개 변수로서 전달되어야 한다. 이를 통해 클래스의 다른 코드에 대한 상호작동하는 객체들의 자료형에 대해 직접적으로 의존하기보다 객체들이 특정 인터페이스를 통해 간접적으로 연결됨으로써, 클래스와 객체들 간의 상호작동에 대한 테스트를 수행할 수 있다.




    프로그램 테스트를 통해 버그의 존재를 확인할 수는 있지만, 버그가 없음을 확신할 수는 없다.

    그 어떤 테스트도 완벽하지는 않지만, 테스트의 존재를 통해 작성중인 코드나 유지 보수하는 코드에 대한 확신을 키울 수 있다.



    작성해야 할 만큼 중요한 코드라면 테스트 해야할 만큼 중요한 것이다.

    구현한 코드에 대해 개발자 테스트를 작성하자. 코드의 설계를 개선하기 위해 테스트를 이용하자. 구현 코드를 작성하면서 테스트 코드를 작성하자, 테스트의 실행을 자동화 하며, 피드백 과정을 줄여나가자.



    반응형

    'Technique > Programmer' 카테고리의 다른 글

    응집도와 결합도  (0) 2017.03.16
    12. 복잡도 다루기  (0) 2017.03.16
    10. 버그 사냥하기  (0) 2017.02.08
    9. 예상하지 못한 것을 예상하기  (0) 2017.02.06
    8. 오류 무시하지 않기  (0) 2017.02.06

    댓글

Designed by Tistory.