테스트 소개
게시 됨: 2021-06-16테스트와 관련된 모든 것에 대한 새로운 블로그 시리즈에 오신 것을 환영합니다. 이 블로그 게시물이 Mediatoolkit에서 작성하는 방법과 그 이유에 대한 일반적인 아이디어를 제공할 수 있기를 바랍니다. 예제로 들어가기 전에 테스트 작성의 몇 가지 기본 정의, 아이디어 및 이점부터 시작하겠습니다.
어떤 종류의 테스트가 있습니까?
모든 테스트가 동일하게 생성되는 것은 아닙니다. 다양한 목적을 위한 다양한 종류의 테스트가 있습니다. 이 포스트는 주로 단위 테스트 에 초점을 맞추고 있지만, 우리는 차이점을 알고 각각의 이점을 이해해야 합니다.
1. 수동 테스트
수동 테스트에는 테스터가 수동으로 실행하는 테스트가 포함됩니다. 이러한 작업은 일반적으로 품질 보증 또는 개발자 자신 이 수행합니다. QA는 메인 애플리케이션 테스터가 아니라 최후의 방어선으로 여겨져야 합니다(UI 테스팅의 주된 방법으로 수동 테스팅을 하는 QA는 이 경우에 유효한 예외입니다).
개발자는 일반적으로 일부 작업을 로컬에서 실행하고 코드를 스테이징 단계 또는 프로덕션 단계로 푸시하지 않고 시스템이 어떻게 작동하는지 확인 하고 싶을 때 수동 테스트를 작성합니다. 그러나 이러한 테스트의 목표는 코드의 견고성이 아닙니다. 이러한 테스트의 유일한 역할은 버그를 잡아내고 제품의 품질을 보장하는 것입니다 .
2. 통합 테스트
시스템은 여러 구성 요소로 구성되어 있거나 최소한 그래야 합니다. 통합 테스트는 이러한 구성 요소 의 공개 API를 확인합니다 . REST API뿐만 아니라 Kafka 주제 통신과 같이 공개적으로 노출된 API입니다.
구성 요소가 특정 형식의 주제에 대한 메시지를 출력한다고 "말하면" 그것이 공개 API 또는 계약입니다. 통합 테스트는 개별적으로 개발된 여러 구성 요소가 통신할 수 있는지와 해당 계약이 그룹으로 일치하는지 확인하는 것입니다.
이러한 구성 요소를 개별적으로만 테스트하면 개별 동작이 올바르게 작동할 수 있지만 더 큰 그룹으로 연결하려고 하면 계약이 다르다는 것을 알 수 있습니다.
3. 종단간(E2E) 테스트
종단 간 테스트는 최종 제품의 사용자 경험 흐름을 보장하는 것 입니다. 오늘날 시스템의 복잡성은 테스트로 다루기 어렵습니다. 많은 시스템이 서로 의존하며 E2E 테스트 는 제품이 예상한 대로 작동하는지 확인합니다. QA는 최종 사용자 흐름을 살펴보고 모든 시스템이 예상대로 작동하는지 확인하여 제품의 정확성을 검증합니다.
4. 단위 테스트
단위 테스트는 신뢰할 수 있는 소프트웨어의 중추입니다 . 그들은 다른 테스트의 기초를 만듭니다. 그들은 개별 단위를 테스트합니다.
개발자는 단일 방법으로 단위의 정의를 오해할 수 있습니다. 단위 테스트는 여러 다른 클래스로 구성될 수 있는 구성 요소의 동작을 테스트해야 합니다. 다른 구성 요소에서 액세스할 수 있는 공용 메서드는 테스트해야 하며 보호된 메서드나 클래스는 테스트하지 마십시오. 이는 공개 API의 일부가 아닌 구현 세부정보 입니다.
왜 테스트를 해야 합니까?
좋은 테스트를 작성하고 자주 작성하면 빛을 보기 전에 제품의 품질을 보장할 수 있습니다.
시스템이 오래되면 테스트의 이점이 점점 더 분명해집니다. 우리의 환경은 안정적이 되어 개발 시간을 절약하고 불가피하게 일이 잘못될 때 많은 수고를 덜어줍니다. 동료들이 테스트를 살펴 보고 수동으로 작업을 실행하지 않고도 코드에서 수행해야 하는 작업과 수행하지 말아야 하는 작업을 이해할 수 있을 때 감사할 것 입니다.
강력한 코드
"강력한 코드"에 대해 이야기하기 전에 어떻게 정의해야 할까요? 코드를 강력하게 만드는 요소는 무엇입니까? 그것은 우리가 방어적으로 프로그래밍하고 다른 개발자가 우리 코드를 남용할 수 있는 방법에 대해 생각해야 한다는 것을 의미합니까?
강력한 코드는 항상 간단하고 깨끗합니다. 단순함은 강력하고 복잡함은 취약 합니다. 잘못된 입력을 처리해야 하지만 그렇다고 해서 방어적으로 프로그래밍하고 팀을 신뢰하지 않아도 되는 것은 아닙니다.
갈의 법칙 : 작동하는 복잡한 시스템은 작동하는 간단한 시스템에서 진화한 것으로 항상 밝혀졌습니다.
반대의 명제도 사실인 것 같습니다. 처음부터 설계된 복잡한 시스템은 작동하지 않으며 작동하도록 만들 수도 없습니다. 작동하는 간단한 시스템부터 다시 시작해야 합니다.
안전하게 리팩토링
코드가 테스트에 포함되면 더 이상 기존 코드를 변경하는 것을 두려워하지 않습니다 . 각 변경 후에 테스트를 실행하고 문제가 발생하지 않았는지 확인할 수 있습니다. 테스트가 있을 때 방어적으로 프로그래밍할 필요가 없습니다.
테스트 없는 리팩토링은 잠 못 이루는 밤과 일요일 근무로 끝나는 미끄러운 비탈길을 걷고 있습니다. 이 주제는 여기에서 다루기에는 너무 광범위하며 향후 자체 블로그 게시물을 올릴 가치가 있습니다.
테스트를 작성하지 않는 방법은 무엇입니까?
단위 테스트를 작성하는 방법만큼 단위 테스트를 작성하지 않는 방법을 아는 것도 중요합니다.
- 메소드 호출의 결과를 출력하는 테스트를 작성하는 것은 우리가 원하는 결과를 검증하지 않기 때문에 테스트가 아닙니다.
- 테스트가 Documents 폴더의 파일에서 데이터를 읽는 경우 테스트가 환경에 종속되지 않아야 하므로 실제 단위 테스트 가 아닙니다.
- 모든 개발자 는 다른 작업을 수행하지 않고도 코드를 체크아웃하고 테스트를 성공적으로 실행할 수 있어야 합니다.
- 모든 단위 테스트는 다른 테스트와 독립적 이어야 합니다. 이는 테스트의 실행 순서도 중요하지 않아야 함을 의미합니다.
- 코드를 변경하지 않으면 테스트를 여러 번 실행해도 항상 동일한 결과로 끝나야 합니다.
- 단위 테스트는 개별 메서드 호출이 아니라 동작을 테스트해야 합니다. 모든 클래스와 메서드에 테스트가 필요한 것은 아닙니다.
행동 은 시스템 사용자가 필요로 하는 실제 가치를 생성하는 것입니다. productFactory.create() 가 두 번 호출될 때 동일한 객체를 생성했는지 또는 저장소가 일부 매개변수와 함께 호출되었는지 사용자가 알아야 합니까? 아마 아닐 수도 있지만 여전히 많은 개발자들이 정확히 이러한 종류의 테스트를 작성합니다.
테스트가 그렇게 보이면 구현과 밀접하게 결합됩니다. 구현의 세부 사항을 변경할 때마다 동작이 동일하더라도 테스트를 업데이트해야 합니다. 테스트는 구현 세부 사항이 아니라 동작이 변경될 때만 변경 되어야 합니다. 즉, 코드가 수행 하는 방식 이 아니라 코드가 수행 하는 작업 을 테스트하십시오.
테스트를 어떻게 작성합니까?
우리 의 테스트 는 모범 코드 를 따라야 하고 환경 에 독립적 이어야 하며 빠르게 실행 되어야 합니다 .
테스트 실행 시간을 가능한 짧게 유지하는 것이 중요합니다. 각 테스트는 몇 밀리초 이상 걸리지 않아야 합니다. 테스트를 실행하는 데 시간이 너무 오래 걸리면 사람들은 테스트를 건너뛰고 Jenkins와 같은 CI 서버에 의존하여 배포 실행 파일을 빌드할 수 없을 때 소란을 피우는 경향이 있습니다.
각 테스트는 3개의 'A' 섹션(AAA 패턴) 으로 구성됩니다.
- 마련하다
- 행동
- 주장하다
1. 정리
테스트의 정렬 섹션에서 테스트하려는 동작을 호출하기 전에 시스템이 특정 상태에 있는지 확인합니다. '시스템'은 행동을 생성하기 위해 특정한 방식으로 설정해야 하는 객체일 수 있으며, 임시 파일이나 그러한 특성을 생성합니다.

이 섹션의 코드는 일반적으로 다른 두 개를 합친 것보다 큽니다 .
이 섹션을 작게 유지하는 데 특히 유용한 것으로 입증되어야 하는 한 디자인 패턴은 Object Mother 입니다. 이 디자인 패턴은 Factory 와 매우 유사하지만 미리 구성된 개체를 빌드하는 보다 구체적인 방법이 있습니다. 표준 Factory 에는 createCar(carDescription) ObjectMother 같은 메서드가 있을 수 있지만 ObjectMother에는 createRedFerrari() , createBlackTesla() 또는 createBrokenYugo() 와 같은 메서드가 있습니다.
2. 행위
테스트의 이 섹션에는 한 줄 이 있어야 합니다. 이 줄은 테스트 중인 동작을 실행합니다. 이 섹션에 대해 한 줄 이상을 작성하고 있는 자신을 발견했다면 아마도 당신의 행동을 올바르게 캡슐화하지 못했을 것입니다. 클라이언트가 특정 순서로 개체의 여러 메서드를 호출할 것으로 예상해서는 안 됩니다. 그렇다면 테스트를 수행해야 하는 이유는 무엇입니까?
이 줄은 테스트하려는 메서드 호출입니다. 이 메서드가 결과를 반환하면 해당 값을 변수에 저장하여 Assert 단계에서 예상한 값인지 확인해야 합니다.
3. 주장
Arrange 섹션에서 시스템을 준비하고 Act 섹션에서 테스트 중인 작업을 실행한 후에는 작업 결과를 검증해야 합니다. 우리는 일반적으로 여기에서 메서드의 결과를 확인하지만 때로는 메서드가 값을 반환하지 않지만 여전히 부작용을 생성합니다. 코드가 객체의 상태를 변경하거나, 파일을 생성하거나, List 에서 무언가를 제거할 것으로 예상되는 경우 정확히 그렇게 했는지 확인해야 합니다.
스텁 대 모의
대부분의 개발자는 모의( mock )와 스텁 (stub)이라는 용어를 같은 의미로 사용하지만 차이점이 있습니다.
스텁은 테스트에 실패할 수 없으며 모의는 할 수 있습니다.
스텁은 상태 기반 이며 하드 코딩된 값을 반환합니다. "내가 얻은 결과는?"
Mock은 행동 기반 이며, 당신의 행동이 어떻게 통과하는지 확인하는 데 사용합니다. "내가 어떻게 결과를 얻었습니까?"
단위 테스트가 모의로 가득 차 있으면 테스트가 매우 취약해집니다. 즉, 구현 세부 정보 중 하나를 변경할 때마다 모의 호출을 모두 업데이트해야 합니다.
한 가지만 테스트하십시오.
우리는 하나의 행동을 분리하고 그것이 효과가 있음을 증명할 수 있어야 합니다. 해당 동작이 다른 입력과 다르게 작동해야 하는 경우 각 동작에 대한 새 테스트를 작성해야 합니다. 한 번에 여러 항목을 테스트하는 대규모 테스트가 있는 경우 테스트가 실패한 이유를 알기 어렵습니다. 또한 더 이상 필요하지 않은 기능을 제거하고 새 코드를 추가할 때 어떤 기능이 손상되었는지 확인하는 것이 더 어려워집니다.
테스트 중인 동작을 여러 번 호출할 필요가 없는 한 테스트의 마지막 섹션에 여러 개의 assert를 사용하는 것이 좋습니다. 그렇다면 잘못된 동작을 찾아 수정하기가 어려울 것입니다.
한 테스트에서 여러 동작을 주장할 때 테스트에서 첫 번째 실패만 보고하고 나머지는 건너뛰기 때문에 정확히 무엇이 작동하지 않는지에 대한 명확한 그림을 얻지 못합니다 . 어떤 변경이 필요하고 얼마나 많은 것들이 예상대로 작동하지 않는지 이해하기가 훨씬 더 어려워집니다.
네이밍 테스트
좋은 테스트와 훌륭한 테스트를 구분하는 한 가지는 테스트 이름입니다. 테스트는 무엇을 하는지 뿐만 아니라 언제 하는지도 알려주어야 합니다.
사용할 수 있는 좋은 이름 지정 패턴이 많이 있으므로 가장 설명적인 이름을 선택하고 그대로 사용하세요. 다음은 훌륭한 테스트 이름의 몇 가지 예입니다.
- RegistrationServiceShould.createNewAccountWhenEmailIsNotTaken
- RegistrationServiceTest.whenEmailIsFree_createNewAccount
- RegistrationServiceTest.if_freeEmail_when_userCreatesAccount_then_create
단위 테스트를 작성할 때 최상의 메서드 명명 방법을 따르는 것보다 테스트 중인 내용을 전달하는 것이 훨씬 더 중요합니다. 예를 들어, Java에서는 메소드를 작성할 때 camelCase를 사용하지만 테스트 이름에서 상태를 작업과 구분하기 위해 밑줄(_)을 사용하는 것은 완벽하게 유효합니다.
깨끗한 테스트
작성하는 테스트는 코드에 적용하는 모든 깨끗한 코드 관행을 따라야 합니다. 테스트는 2급 시민이 아니므로 나머지 코드를 읽을 수 있도록 하는 것과 동일한 수준의 주의를 기울여야 합니다.
테스트에서 코드 중복의 정의는 매우 중요합니다. DRY 원칙 (Don't Repeat Yourself)은 같은 이유로 변화하는 행동을 추출하는 데 적용됩니다. 테스트는 여러 가지 이유로 변경되므로 실제로 동일한 이유로 변경되지 않는 경우 테스트에서 다양한 항목을 추출합니다. 스포일러 경고, 그들은 종종하지 않습니다.
if 문은 테스트에 속하지 않습니다. if 문은 우리의 테스트가 적어도 두 가지 다른 일을 하며 우리의 테스트를 두 개의 다른 테스트로 다시 작성하는 것이 더 나을 것이라고 알려줍니다. 적절한 이름을 지정하면 테스트가 수행하는 작업과 모든 다른 동작이 무엇인지 이해하기가 더 쉬울 것입니다.
언제 테스트를 작성해야 합니까?
TDD 원칙에 따라 새 코드를 작성하기 전에 테스트를 작성해야 합니다.
새로운 기능을 추가해야 할 때 먼저 원하는 동작을 새 테스트로 설명합니다. 우리는 다른 테스트를 방해하지 않고 해당 테스트를 통과하는 데 필요한 최소한의 변경을 수행합니다.
그렇지 않으면 시간이 지날수록 테스트되지 않은 코드가 많아지고 버그가 발생하거나 과도한 엔지니어링이 발생할 가능성이 높아집니다 .
또한 지금 테스트할 코드가 더 많기 때문에 테스트가 더 복잡해지며 일반적으로 개발자가 테스트를 코드에 맞게 조정합니다. 즉, 코드가 수행하는 작업과 일치하도록 동작을 조정하는 대신 반대로 동작을 조정합니다.
테스트를 일찍 작성하면 문제의 정의가 더 작아지고 보다 일반적이고 복잡한 동작보다 이러한 문제에 대해 머리를 감싸는 것이 더 쉽습니다.
마지막 생각들
테스트를 작성하는 것은 선택적인 일처럼 보일 수 있지만 좋은 기초부터 시작하는 것이 중요합니다. 코딩은 이미 어렵습니다. 읽기, 이해 및 유지 관리가 더 쉬운 테스트 가능한 코드를 작성하여 자신과 팀원 모두가 쉽게 코딩할 수 있습니다. 마지막으로 테스트를 원하는 대로 만드는 데 어려움이 있는 경우 테스트보다 코드에 문제가 있을 가능성이 더 큽니다.
깨끗하고 테스트 가능한 코드 작업에 관심이 있으십니까?
시니어 백엔드 개발자 의 채용 공고를 확인하세요.
또는 공개 신청서 를 보내주십시오!
