본문 바로가기

Dot Programming/Spring

생산성 향상을 위해 반드시 테스트 코드를 작성하자

    테스트 코드를 작성하자

    클라이언트가 우리의 제품을 이용하다가 장애가 발생하였다. 이렇게 잦은 장애로 충성 고객들이 하나둘 떠나 버려 결국엔 앱 운영이 중지되는 사태에 이르렀다.소프트웨어에서 오류가 발생할 가능성은 무궁무진하기 때문에 사전에 방지해줘야 한다. 어제는 정상적으로 동작하던 코드가 갑자기 멈출 수도 있다. 그리고 앱의 비즈니스 요구사항은 매번 변하고 확장은 계속해서 일어난다.(안일어난다면 그 개발을 멈춰라) 유연한 확장과 코드 수정을 위해서는 코드를 단순히 하는 리팩토링 과정이 필수이다. 그 리팩토링을 원활하게 진행하기 위해서는 테스트 코드 작성은 반드시 필요하다.

     

    테스트 코드 장점

    1. 앱 안전성이 증가한다. 앱 결함을 사전에 발견하여 고칠 수 있다.
    2. 리팩토링 코드 안전성이 증가한다. 우리가 실제 사용하는 앱이나 프로그램이 간단할 수 있지만 대부분은 복잡하다. 이는 리팩토링 및 기능 확장을 할 경우 Side Effect가 나는게 당연하다는 소리이다. 하지만 이를 테스트 코드 실행으로 쉽게 감지할 수 있어 마음 편히 리팩토링을 진행할 수 있다.
    3. 잘 짠 테스트 코드는 문서 역할을 한다. 테스트 코드를 통해 작성자의 의도, 사용법, 주의사항 등을 확인할 수 있다.
    4. 재사용성이 좋은 코드 작성을 가능하게 해준다. 테스트 코드를 작성하다보면 의존성 문제에 걸리거나 이상하게 재사용되는 코드를 발견하는 등 이러한 문제점을 감지하여 역할 분담 및 관심사의 분리 등을 진행 하여 좀더 나은 방향의 구조를 유도 할 수 있다.

    테스트 코드 작성을 하면 개발자들이 온전히 '개발에만 집중'할 수 있게 해주어 생산성 높은 개발 환경을 갖출 수 있게 된다.

     

    테스트 유형

    • 승인 테스트(Acceptance test): 전체 시스템이 의도한 대로 작동하는지 테스트한다.
    • 통합 테스트(Integration test): 시스템 컴포넌트 또는 기능이 함께 정상적으로 작동하는지 테스트한다.
    • 단위 테스트(Unit test): 각 시스템 단위가 예상하는대로 동작하는지 테스트한다. 단위는 앱에서 테스트할 수 있는 가장 작은 구성 요소이다.
    • 기능 테스트(Functional test): 기능 요구 사항을 기반으로 비즈니스 시나리오를 에뮬레이션하여 기능을 확인한다. 블랙박스 테스트는 기능 테스트의 가장 일반적인 방법이다.
    • 성능 테스트(Performance test): 다양한 워크로드에서 소프트웨어가 어떻게 수행되는지 테스트한다. 예를 들어 부하 테스트는 실제 부하 조건에서 성능을 평가하는 데 사용된다.
    • 스트레스 테스트(Stress test): 시스템이 실패하기 전에 얼마나 많은 부담을 줄 수 있는지 테스트한다. 일종의 비기능 테스트로 간주된다.
    • 사용성 테스트(Usability test): 클라이언트가 작업을 완료하기 위해 시스템 또는 웹 애플리케이션을 얼마나 잘 사용할 수 있는지 검증한다.
    • 회귀 테스트(Regression test): 새로운 기능이 기능을 손상시키거나 저하시키는지 확인한다. 온전성 테스트는 전체 회귀 테스트를 할 시간이 없을 때 표면 수준에서 메뉴, 기능 및 명령을 확인하는 데 사용할 수 있다.

     

    테스트 기본 원칙

    앱 안전성과 생산성 향상을 위해 다양한 테스트 유형을 적용하는 것은 매우 좋은 행동이지만 테스트의 원칙을 지키는 것이 우선되어야 한다. 

     

    일곱 테스트 원칙 (Seven Testing Principles)

    소프트웨어 테스팅 분야에서 40여년 간 제안되고 발전해 온 일곱 개의 기본 원칙이디. 

    • 테스팅은 결함의 존재를 보여주는 것이다.
    • 완벽한 테스트는 불가능하다.
    • 테스트 구성은 가능한 빠른 시기에 시작한다.
    • 결함은 군집되어 있다.
    • 살충제 역설(Pesticide Paradox) — 비슷한 테스트가 반복되면 새로운 결함을 발견할 수 없다.
    • 테스팅은 정황에 의존적이다.
    • 오류 부재의 오해 — 사용되지 않는 시스템이나 사용자의 기대에 부응하지 않는 기능의 결함을 찾고 수정하는 것은 의미가 없다.

     

    F.I.R.S.T 단위 테스트 원칙

    단위 테스트의 기본 원칙이다. 단위 테스트는 가장 작은 단위의 테스트이며, Bottom-up 관점에서 모든 테스트의 시작점이다. 

    • Fast: 수천 개의 단위 테스트가 있더라도 개발 주기 어느 시점에서든 주저하지 않고 단위 테스트를 작성해야 한다. 그리고 작성된 유닛 테스트 결과는 빠르게 출력되어야 한다.
    • Isolated:  결과가 다른 요인의 영향을 받지 않도록 항상 독립적이어야 한다. 다른 테스트에 종속적인 테스트는 절대로 작성하지 않는다. 테스트의 3A(Arrange, Act, Assert)를 따라야 한다. 일부 문헌에서는 GiveWhenThen이라고 한다.
    • Repeatable: 실행할 때마다 동일한 결과를 출력하는 반복 가능한 테스트를 만들어야 한다. 반복 가능한 테스트를 수행하려면 외부 환경으로 부터 테스트를 격리해야 한다. 그러기 위해서 Mock 객체를 사용하면 좋다.
    • Self-validating: 테스트 통과 여부를 수동으로 확인할 필요가 없어야 한다. 테스트는 스스로 결과물이 옳은지 그른지 판단할 수 있도록 만들어야 한다. 
    • Timely(for TDD): 유닛 테스트는 프로덕션 코드가 테스트를 성공하기 직전에 구성되어야 한다. 이는 테스트 주도 개발(TDD) 방법론에 적합한 원칙이다.
    • Thorough: 100% 코드 적용을 목표로 하는 것이 아니라 모든 사용 사례 시나리오를 다뤄야 한다.
      • 기능이 실패할 것이라고 느껴지는 모든 케이스를 테스트한다.
      • 잘못된 argument나 parameter를 테스트한다.
      • 모서리/가장자리/경계값을 테스트한다.
      • 보안 및 다른 이슈를 테스트한다.

     

     


    참고

    https://www.ibm.com/topics/software-testing

    https://www.boxuk.com/insight/the-seven-principles-of-testing/

    https://medium.com/@tasdikrahman/f-i-r-s-t-principles-of-testing-1a497acda8d6