Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CHAPTER 2 명세 기반 테스트 #2

Open
moto6 opened this issue Aug 15, 2023 · 6 comments
Open

CHAPTER 2 명세 기반 테스트 #2

moto6 opened this issue Aug 15, 2023 · 6 comments

Comments

@moto6
Copy link
Owner

moto6 commented Aug 15, 2023

범위

  • CHAPTER 2 명세 기반 테스트 37p~76p

기간

  • ~ 2023/09/02 까지 댓글로 남겨주세요
@moto6
Copy link
Owner Author

moto6 commented Aug 31, 2023

to @meteora09 @gurioh @AntBean94 @banggeunho @parkcoldroad @Lightieey @D0ri123 @70825

  • 벌써 금요일이네요..! 이번주는 특히나 비도오고 그래서 더 정신이 없었던거같네요..! 이번주 스터디 범위는 챕터 2입니다 가능하면 9/2일까지 책을 읽고 내용을 정리해서 댓글 달아주시면 감사하겠습니다!
  • 불가피한 사정이 있으신 분들은 늦어도 다음주(9/4~9/8) 까지라도 꼭 챕터2를 학습하고 댓글 남겨주시면 감사하겠습니다!

@70825
Copy link

70825 commented Sep 3, 2023

https://70825.notion.site/Ch-2-bab67922d1c54f19aa1c02606e60b1a6?pvs=4

책을 읽으면서 최대한 실용적으로 경우의 수를 모두 테스트한다는 내용이 좋았습니다.
이러면 기능이 바뀔 때마다 무조건 테스트 코드도 고쳐야하는 귀찮음이 올 것 같은데, 그만큼 프로덕트가 견고하다는 뜻일 것 같다는 생각이 들어요

이거 여기에 댓글 달아도 잔디가 안남네요 😭

@parkmuhyeun
Copy link

명세 기반 테스트

명세 기반 테스트 기법 적용하여 테스트 케이스 작성하는 방법

1단계: 요구사항과 입출력에 대해 이해하기

요구사항을 어떻게 작성했든 세부분으로 이루어져있다.

  1. 프로그램 또는 메서드는 무엇을 수행하는가(비즈니스 규칙)
  2. 프로그램은 데이터를 입력으로 받는다.
  3. 출력에 대한 추론은 프로그램이 무엇을 수행하고 입력이 어떻게 기대하는 출력으로 변환되는지 더 잘 이해하도록 해준다.

2단계: 여러 입력값에 대해 프로그램이 수행하는 바를 탐색하기

메서드가 수행하는 작업을 탐색하면 메서드를 잘 이해할 수 있다. 직접 가지고 놀며 프로그램을 더 잘이해하도록 해보자.

3단계: 테스트 가능한 입출력과 구획을 탐색하기

프로그램의 정확성을 충분히 확신할 수 있도록 입출력에 대해 우선순위를 매기고 그 일부를 선택하는 방법을 찾아야 한다. 프로그램에 대해 가능한 입력과 출력의 수는 거의 무한대이지만, 프로그램은 일부 입력 집합에 대해 특정 입력값에 관계없이 동일한 방식으로 동작

How? 다음과 같이 생각

  1. 각각의 입력에 대해: '내가 전달하는 입력이 어떤 부류에 해당하는가?'
  2. 각 입력과 다른 입력의 조합에 대해 'open 태그와 close 태그 사이에 어떤 조합을 만들 수 있을까?
  3. 이 프로그램에서 기대하는 여러 부류 출력에 대해 '배열을 리턴하는가? 빈 배열을 리턴하는가? 널을 리턴하는가?

입력 변수 자세히 분석하고 변수들의 가능한 조합을 살펴보고 출력에 대해 생각

4단계: 경계 분석하기

버그는 입력 도메인의 경계에서 흔하게 발생한다. 구획을 설계 할 때 한 구획은 다른 구획과 가까운 경계를 지닌다. 경계를 발견할 때마다 입력을 한 경계에서 다른 경계로 이동시키며 프로그램에 어떤 일이 일어나는지 테스트하자.

5단계: 테스트 케이스 고안하기

앞에서 입력, 출력, 경계를 적절하게 해부했으니 이제 구체적인 테스트 케이스를 만들자. 앞에서 찾은 모든 구획을 결합할 수 있을 것이다. 하지만 모두다 작성하는 건 노력에 비해 성과가 크지 않기 때문에 구획을 어떻게 조합할지 실용적으로 결정해야 한다.

즉, 구획을 무작정 조합하지 말고 어떤 구획을 확장해서 테스트해야하는지 어떤 구획은 그렇게 하지 않을지 명확히 파악하고 나면, 조합을 통해 여러 ㅌ스트 케이스를 도출한다. 먼저 예외 케이스를 고려

6단계: 테스트 케이스 자동화하기

테스트를 자동화하는 일은 거의 기계적인 작업이다. 창의성이 필요한 부분은 특정 구획에 입력을 넣고 그 구획에 대한 프로그램의 출력을 이해하는 일이다. JUnit을 이용해 다음과 같이 테스트 할 수 있음

@Test
void noSubstringBetweenOpenAndCloseTags() {
  assertThat(substringsBetween("aabb", "aa", "bb")).isEqualTo(new String[] {""});
}

또한, 여러개의 값들을 주입할 수 있는 ParameterizedTest를 이용해 좀 더 편리하게 테스트를 해볼 수도 있다.

7단계: 창의성고 경험을 발휘해서 테스트 스위트를 강화하기

체계적인 것도 좋지만 우리의 경험을 무시해서는 안된다. 이번 단계에서는 우리가 고안한 구획을 살펴보고 흥미로운 변형을 찾아 낼 수 있는지 살펴보자.


현업에서의 명세 테스트

프로세스는 연속적이 아니라 반복적이어야 한다.

위에서 본 7단계는 순차적인 프로세스가 아니라 전체 프로세스는 반복적이다. 즉, 다른 단계들 사이를 왔다 갔다 해야한다. -> 테스트 케이스를 작성할 때 구획이나 경계를 놓쳤다는 것을 알게 되면 다시 돌아가서 테스트 스위트를 개선

명세 테스트는 어느 정도로 수행해야 하는가?

실패의 위험성을 이해해야 한다. 프로그램이 어떤 부분에서 실패하면 비용이 얼마나 들까? 비용이 높은 경우라면 테스트에 더 투자를 하고, 더 많은 코너 케이스를 탐색하고 품질을 보장하기 위해 다양한 기술을 시도하는 것이 현명할 수 있다. 이 책의 저자는 모든 단계를 몇번 거쳤는데도 테스트하지 않는 경우를 찾을 수 없을 때 테스트를 중단한다고 한다.

이해를 높이기 위해 입력을 변경해서 사용하자

모든 테스트에 동일한 입력 시드를 사용함으로써 다양한 테스트 케이스에 대한 이해를 단순화할 수 있다. 하지만 이 방법은 가능한 다양한 입력을 테스트하는 일반적인 테스트 개념에 위배되어 주의해야 한다. 입력 공간을 살펴보고 코너 케이스를 식별할 수 있기 때문에 다양한 입력은 필수다.

조합의 수가 폭발적으로 증가한다면 실용적이어야 한다.

첫째, 가능한 한 조합의 수를 줄이도록 하자. 다른 동작과 동떨어진 예외적인 동작을 테스트하는 것도 한 가지 방법이다. 또한 도메인 지식을 활용하여 조합의 수를 더 줄일 수 있다.

둘째, 메서드 수준에서 많은 조합이 발생한다면 메서드를 두 개로 나누는 것을 고려하자. 두 개의 작은 메서드는 테스트 할 대상이 적기 때문에 테스트할 조합이 적다. 메서드의 계약과 그 계약이 정보를 전달하는 방법을 잘 만들면 된다.

무엇을 입력할지 모르겠다면 간단한 입력을 넣어보자

테스트 케이스에 무엇을 입력할지 선택하는 것은 까다로운 일이다. 복잡한 입력을 사용해야 하는 충분한 이유가 없다면 이를 선택하지 않는게 좋다. 단순함이 중요하다.

관심 없는 입력에 대해 합리적인 값을 선택하자

떄로는 기능의 특정 부분을 수행하는 것을 목표로 할 수 있따. 그 부분은 입력값 중 하나를 사용하지 않을 수 있는데 이 '쓸데없는' 입력변수에 아무 값이나 전달할 수 있다. 이때 입력으로는 현실적인 값을 전달하자.

널과 예외 케이스는 의미가 있을 때만 사용하자

널과 예외적인 경우에 대한 테스트는 개발자가 종종 코드에서 이러한 경우를 처리하는 것을 깜박하기 때문에 항상 중요하다. 테스트 중인 코드가 UI와 매우 관련이 있는 경우라면 널, 빈 문자열, 일반적이지 않은 정숫값 등과 같은 코너 케이스를 더 많이 수행하자. 코드가 UI와 별로 관련이 없고 데이터가 테스트 대상 구성요소에 도달하기 전에 검사된 것이 확실하다면 이러한 테스트를 건너 뛸 수 있다.
-> 즉 버그를 잡을 수 있는 테스트만 작성하자.

테스트가 동일한 스켈레톤을 갖는 경우 매개변수화 테스트를 사용하자

약간의 중복은 결코 문제가 되지 않지만 많은 중복은 문제가 된다. 그것이 결국 관리 포인트가 되기 때문이다. 중복이 너무 많을 때 매개변수 테스트를 사용해 좀 더 편리하게 테스트를 해볼 수 있다.

다음과 같이 사용해볼 수 있다.

// 사용한 경우
@ParameterizedTest
@CsvSource(value = {"test,TEST", "tEst,TEST", "Java,JAVA"})
void toUpperCase(String actual, String expected) {
    assertEquals(actual.toUpperCase(), expected);
}
//사용하지 않은 경우
@Test
void toUpperCase1(String actual, String expected) {
    String expected = "TEST"
    String actual = "test"

    assertEquals(actual.toUpperCase(), expected);
}

@Test
void toUpperCase2() {
    String expected = "tEst"
    String actual = "TEST"

    assertEquals(actual.toUpperCase(), expected);
}

@Test
void toUpperCase3() {
    String expected = "Java"
    String actual = "JAVA"

    assertEquals(actual.toUpperCase(), expected);
}

Chapter2 한 줄 평

그 동안은 감으로 테스트를 몇개 작성했었는데 좀 더 체계적인 방법을 배운 거 같아 너무 좋은 것 같습니다~ 어떻게 테스트를 작성하면 좋을지에 대해 많이 배웠습니다. 이를 토대로 체계적인 방법 + 경험적인 측면을 이용해 테스트를 잘 짜도록 해보겠습니다.

@D0ri123
Copy link

D0ri123 commented Sep 9, 2023

급한 사정이 생겨서,, 이제서야 챕터2 스터디 내용 공유드립니다..😓
테스트 코드는 단순히 요구사항만 보고 머릿속으로 떠오르는 테스트 케이스를 작성하는 게 일반적인 줄 알았습니다.
테스트 케이스를 경계와 구획을 기준으로 조합을 생각해보고, 거기서 또 실용적인 케이스들만 필터링해내가는 체계적인 과정이 있다는 걸 배워서 테스트 코드 연습을 해당 과정을 토대로 많이 해보고 싶다는 생각이 들었습니다!

Ch2. 명세 기반 테스트

요구사항은 소프트웨어 테스트와 관련하여 가장 중요한 요소다.
특히나 이번 명세 기반 테스트에서는 요구사항을 테스트의 입력으로 사용하기 때문에 요구사항을 광범위하게 수행하는 테스트를 체계적으로 도출할 수 있는 방법에 대해 이번 챕터에서 알아본다.

Ch2 목표: 주어진 요구사항을 광범위하게 수행하는 체계적인 테스트 케이스 도출방법

2-1) 요구사항이 모든 걸 말한다

1단계: 요구사항과 입출력에 대해 이해하기

p.60 중, '요구사항에 대한 깊은 이해는 좋은 테스트 케이스를 고안하기 위한 핵심이다.' 라고 적혀있습니다.
1단계에서는 요구사항 이해를 위한 핵심 키워드 3가지를 제시했는데요. '목표, 매개변수, 반환' 입니다.
목표 는 해당 메서드가 뭘 수행하는지, 매개변수 는 어떤 데이터를 입력받는지, 반환 은 어떤 출력으로 반환하는지
해당 키워드들에 집중하여 요구사항을 바라보면 좋은 테스트 케이스를 고안할 수 있습니다.

2단계: 여러 입력값에 대해 프로그램이 수행하는 바를 탐색하기

여러 입력값들을 넣어보며 테스트하려는 메서드가 어떻게 동작하는지에 대한 명확한 그림이 그려진다면, 탐색 단계를 중단해도 좋습니다.
이 단계에서는 프로그램을 이해하는 데에만 집중하는 것이 좋습니다.

3단계: 테스트 가능한 입출력과 구획을 탐색하기

프로그램은 일부 입력 집합에 속한 값이 어떻든 동일한 방식으로 동작합니다.
이를 테스트 용어로 말하자면 해당 집합에 속한 입력들은 서로 동등하다라고 말할 수 있습니다.

촘촘한 테스트 케이스를 위해서

  1. 도출 가능한 입력에 대해 어떤 부류인지 분석하고,
  2. 개별 입력들의 조합을 어떻게 만들 수 있을지,
  3. 출력에는 어떤 부류가 있는지에 대해 탐색하는 단계입니다.
    +) null과 빈 입력값은 어떤 테스트에서나 항상 좋은 예외 케이스입니다.

4단계: 경계 분석하기

개발자가 경계 근처에서 발생하는 버그를 만들 확률은 다른 입력값에 비해 더 크다고 합니다.
그렇기 때문에 이 단계에서는 가능한 입력값들 중 경계를 식별하는 법을 배웁니다.

  • 접점과 거점
    접점은 경계 위에 있는 점을 말하고, 거점은 경계가 속해있지 않은 구획(구간)에 있으면서 경계에 가장 가까운 점을 말합니다.
  • 내점과 외점
    내점은 조건이 참인 점을 말하고, 외점은 조건이 거짓인 점을 말합니다.

5단계: 테스트 케이스 고안하기(어떻게 실용적인 조합을 고안할 수 있을까?)

위의 경계 분석을 통해서 생성된 구획에 대한 조합을 생성할 수 있습니다.
그러나 입력 개수에 따라 케이스가 급격히 증가할 수 있고, 이걸 모두 테스트 하기에는 성과가 크지 않을 것입니다.
그래서 무작정 구획을 조합하는 것이 아니라, 어떻게 실용적인 조합을 고안할 수 있을지에 대해 알아야 합니다.

  • null과 빈 입력값은 테스트 케이스를 하나씩만 수행해도 충분합니다.
  • 케이스를 살펴보며, 중복되는 케이스가 있는지 살펴보는 과정이 필요합니다.

6단계: 테스트 케이스 자동화하기

모든 테스트를 연관된 한 메서드에 넣을지, 테스트 한개 당 메서드를 따로 구현할지에 대해서는 사용하는 상황에 따라 선택하자.

7단계: 창의성과 경험을 발휘해서 테스트 스위트를 강화(보강)하기

2-2) 간략히 살펴보는 명세 기반 테스트

위의 7단계는 순차적으로 진행하는 것이 아니라, 반복적인 프로세스(왔다갔다)로 해야 한다.

2-4) 현업에서의 명세 테스트

  • 프로세스는 연속적이 아니라 반복적이어야 한다.
  • 기능이 실패했을 때의 비용이 많이 든다면, 테스트에 투자를 많이 하는 것이 좋다.
  • 구획을 식별하여 테스트 케이스를 고안할 때, 경계를 구획 사이의 경계가 아닌 또 다른 하나의 구획으로 이해할 수도 있다. 경계와 구획 구분 짓는 거에 너무 명확히 하려고 하지 않아도 된다.
  • 접점과 거점으로도 충분하지만, 내점과 외점을 추가한다면 프로그램을 더 잘 이해하고 실제 입력을 더 잘 나타낼 수 있다.
  • 조합의 수는 가능한 줄이도록 노력해야 한다.
  • 메서드 수준에서 많은 조합이 발생하며 메서드를 두 개로 나누는 것을 고려해야 한다.
  • 복잡한 입력을 사용해야 하는 충분한 이유가 없다면 단순한 입력으로 가자(정수 입력값 -> 작은 정수 / 문자열 -> 짧은 길이의 문자열)
  • 널과 예외 케이스는 의미가 있을 때만 사용하자(UI와 매우 관련 있는 경우라면 null, 빈 값에 대한 테스트를 수행해도 좋다)
  • 테스트가 동일한 스켈레톤을 가진다면 매개변수화 테스트(@ParameterizedTest)를 사용하자

@Lightieey
Copy link

엄청나게 늦어서 정말 죄송합니다,, 😣

https://norgb.tistory.com/21
이번 장에서는 테스트 케이스를 도출하는 방식을 좀 더 체계적으로 정리할 수 있었던 것 같아요!
가장 인상깊었던 부분은 실용적인 테스트 케이스 조합하기매개변수화 테스트였습니다 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants