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

#22_4 [ParameterizedTest 진행하기] #24

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.example.testcode.parameterized;

public enum Constants {

MONDAY(1),
TUESDAY(2),
WEDNESDAY(3),
THURSDAY(4),
FRIDAY(5),
SATURDAY(6),
SUNDAY(7);

private final int day;


Constants(int day) {
this.day = day;
}

public int getDay() {
return day;
}

}
24 changes: 24 additions & 0 deletions TestCode/src/main/java/com/example/testcode/parameterized/Day.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.example.testcode.parameterized;

public enum Day {

Monday("2024-09-30"),
Tuesday("2024-10-01"),
Wednesday("2024-10-02"),
Thursday("2024-10-03"),
Friday("2024-10-04"),
Saturday("2024-10-05"),
Sunday("2024-10-06");

private final String date;

Day(String date) {
this.date = date;
}

public String getDate() {
return date;
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.example.testcode.parameterized;

import java.time.LocalDate;
import java.util.stream.Stream;


public class FindSunday {


//Constants.SUNDAY.getDay()
/*
'int com.example.testcode.parameterized.Constants.getDay()'
java.lang.NoSuchMethodError: 'int com.example.testcode.parameterized.Constants.getDay()'
테스트코드에러 ecum을 왜 못찾지?...
*/
public static LocalDate findFirstSunday(LocalDate date) {
if (date.getDayOfWeek().getValue() == 7) { // 날짜가 일요일인지 확인
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

주석으로 날짜가 일요일인지 확인 을 작성하는 것 보다 코드를 보았을 때 직관적으로 볼 수 있다면 읽기 쉬운 코드를 작성하게 만들어주는게 좋습니다.
대표적으로 모든 "매직넘버"를 상수로 만들거나, 변수로서 이름을 지어주는 것입니다.

이런식으로 작성하면 어떨까요?

Suggested change
if (date.getDayOfWeek().getValue() == 7) { // 날짜가 일요일인지 확인
if (date.getDayOfWeek() == SUNDAY) { // 날짜가 일요일인지 확인

return date; // 만약 해당 날짜가 일요일이면 그 날짜를 반환
}
return findFirstSunday(date.plusDays(1)); // 일요일이 아니면 하루를 더하고 다시 함수 호출
}
Comment on lines +16 to +21
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

재귀함수 사용을 처음 사용하셨네요~ 직접 작성하신건가요?
재귀함수는 잘 사용하면 굉장히 간결한 코드를 만들 수 있어서 이런 시도는 꽤 좋은 방식입니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사실 재귀함수를 사용할수있으면 계속사용해보곳싶었는데
재귀함수가 참 잘 사용하기 어려운 것 같아서 지금까지 사용할 상황을 잘 캐치하지못했었습니다
요번에 반복적인 행위테스트를 진행하다 보니 재귀를 사용할수있을거란 생각을 하고 시도를 하다가 실패하여서..
슬프게도..코드자체는 GPT도움이 있었습니다..
GPT의존도는 많이 줄이긴했는데..완전히 떼지는 못했네요..

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

가능하면 직접 구현해보려고 노력해보세요~ 로직을 짜고 직접 구현하는 과정을 통해서 시간이 걸릴 수 있겠지만, 구현능력을 키우려면 직접 짜보셔야 하기 때문에 연습이 필요합니다 :)


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.example.testcode.parameterized;

public enum Constants {

SUNDAY(7);

private long day;

Constants(long day) {
this.day = day;
}

public long getDate() {
return day;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.example.testcode.parameterized;

import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;

import java.time.LocalDate;
import java.util.stream.Stream;

public class CustomArgumentsProvider implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
return Stream.of(
Arguments.of("2024-09-30"),
Arguments.of("20241001"),
Arguments.of("2024/10/02"),
Arguments.of("2024-10-03")
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package com.example.testcode.parameterized;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.*;

import java.time.DateTimeException;
import java.time.LocalDate;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.*;

class FindSundayTest {


@Test
@DisplayName("LocalDate(yyyy-mm-dd)를 입력했을때 일요일을 찾는다. - success")
public void givenLocalDate_whenFindFirstSunday_thenFirstSunday() {
//given
LocalDate date = LocalDate.of(2024, 9, 29);
//when
LocalDate result = FindSunday.findFirstSunday(date);
//then
assertEquals(LocalDate.of(2024, 9, 29), result);

}

@Test
@DisplayName("날짜 포맷이 맞지않게(yyyy-mm-dd형식이아닌) 12345 입력했을때 실패한다.")
public void givenWrongLocalDateForMat_whenFindFirstSunday_thenFirstSunday() {
/* 기존 코드
//given
LocalDate date = LocalDate.of(12345, 12345, 12345);
//when
LocalDate result = FindSunday.findFirstSunday(date);

// assertThrows 메서드가 예외를 발생시키는 코드를 람다 표현식으로 감싸야 한다
//then
assertThrows(DateTimeException.class, () -> FindSunday.findFirstSunday(date));
*/

/*
에초에 테스트작성시나리오가 잘못되었다 given에서 이미 예외가 발생하기 때문
현재 상황에선 아래와 같이 테스트를 작성해야한다.
혹은 테스트할 메소드에 LocalDate로 변환작업이 있어야지 기존 시나리오 검증이 된다
*/

//then
assertThrows(DateTimeException.class, () -> {
//given
LocalDate date = LocalDate.of(12345, 12345, 12345);
//when
FindSunday.findFirstSunday(date);
});
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분도 많이 어색합니다
시나리오가 잘못된건가 싶기도하고...뭔가 이부분은 아직 모르는것겉아서
멘토님이 뭔가 이부분에서 잘못된곳이있는지 어디가 이상한지 확인해주시면 감사드리겠습니다!!!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이런 경우 given when then을 아예 작성하지 않고, 아래 코드만 작성해도 됩니다.

assertThrows(DateTimeException.class, () -> {
            LocalDate date = LocalDate.of(12345, 12345, 12345);
        });


}

@Test
@DisplayName("경계테스트 - -999999999년을 입력해도 정상적으로 반환한다 - success!")
public void givenMinLocalDate_whenFindFirstSunday_thenFirstSunday() {
//given
LocalDate date = LocalDate.MIN;
//when
LocalDate result = FindSunday.findFirstSunday(date);
//then
assertEquals(LocalDate.of(-999999999, 01, 7), result);

}


@Test
@DisplayName("경계테스트 - 999999999년을 입력하면 정상적으로 반환이 되지않는다")
public void givenMaxLocalDate_whenFindFirstSunday_thenFirstSunday() {
//given
LocalDate date = LocalDate.MAX;
//when
//LocalDate result = FindSunday.findFirstSunday(date);
//then
//assertThrows는 람다식으로 표현을 하게되면 give-when-then형식이 깨지게된다?
assertThrows(DateTimeException.class, () -> FindSunday.findFirstSunday(date));

}


/*
@ValueSource 어노테이션을 사용하여 여러개의 값을 테스트에 주입할 수 있다.
특징은 기본형타입만 지원되며 배열형태로 설정이된다 그로고 하나씩 반복실행하게된다
*/
@ParameterizedTest //@Test 대신 @ParameterizedTest를 사용한다.
@DisplayName("@ValueSource 어노테이션을 사용하여 여러개의 값을 테스트에 주입할 수 있다. - success!")
@ValueSource(strings = {"2024-09-30", "2024-10-01", "2024-10-02", "2024-10-03", "2024-10-04", "2024-10-05"})
Comment on lines +66 to +68
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

명확하게 학습을 위한 테스트로 이름을 지어주셨네요~ 훨씬 테스트의 목적이 명확해보입니다 💯

public void valueSourceTest(String date) {
//given
LocalDate localDate = LocalDate.parse(date);
//when
LocalDate result = FindSunday.findFirstSunday(localDate);
//then
assertEquals(LocalDate.of(2024, 10, 6), result);
}



//@EnumSource 어노테이션은 Enum 타입의 값을 테스트에 주입할 수 있다.
//특정 Enum 타입을 지정하면 해당 Enum의 모든 값을 테스트에 주입한다.
@ParameterizedTest //@Test 대신 @ParameterizedTest를 사용한다.
@DisplayName("@EnumSource 어노테이션을 사용하여 여러개의 값을 테스트에 주입할 수 있다. - success!")
@EnumSource(Day.class)
//@EnumSource(value = Day.class, names = {"Monday", "Tuesday"}) // Day enum의 Monday, Tuesday만 테스트에 주입
public void enumSourceTest(Day day) {
//given
LocalDate localDate = LocalDate.parse(day.getDate());
//when
LocalDate result = FindSunday.findFirstSunday(localDate);
//then
assertEquals(LocalDate.of(2024, 10, 6), result);

}

/*
@valueSource어노테이션은 기본형만 지원하였으나
@MethodSource어노테이션을 사용하면 복잡한객체나 여러가지 다양한타입의 데이터를 전달가능
데이터를 제공할 메소드는 static으로 선언되어야하며
!!Collection!!을 반환해야한다.
가장 많이사용하는 Stream<T>, Iterable<T>, Iterator<T>, Object[]를 반환해야한다.
그리고 해당 메소드는 테스트클래스나 외부클래스에존재해도된다
*/
@ParameterizedTest
@DisplayName("MethodSource 이용한 테스트")
@MethodSource("testMethodSource")
public void methodSourceTest(LocalDate date) {
//given
//when
LocalDate result = FindSunday.findFirstSunday(date);
//then
assertEquals(LocalDate.of(2024, 10, 6), result);


}

// test테스트에 사용될 데이터 스트림으로 반환
private static Stream<LocalDate> testMethodSource() {
return Stream.of(LocalDate.of(2024, 9, 30));
}


/*
Csv형태는 String으로 반환하지만
Junit5에서는 자동으로 타입변환을 해준다. 즉 LocalDate로 받아도 된다.
*/
@ParameterizedTest
@DisplayName("CsvSource 이용한 테스트")
@CsvSource({
"20241004",
"2024/10/05"
})
public void csvSourceTest(LocalDate date) {
//given
//when
LocalDate result = FindSunday.findFirstSunday(date);
//then
assertThrows(ParameterResolutionException.class, () -> FindSunday.findFirstSunday(date));
}
Copy link
Collaborator Author

@rlawltjd8547 rlawltjd8547 Oct 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

csv메소드와 ArgumentsSource 메소드 작성시에
생각을 해보면 두 상황은 성공하는 케이스와 실패케이스가 같이있습니다
근데 생각해보면 성공케이스와 실패케이스는 무조건적인 분리가 필요할것같아서
애초에 시나리오가 잘못생각하고 만든거 아닌가라는 생각이듭니다
즉 테스트 given 에서 성공과 실패를할 데이터는 공존하면 안되는건가요? 각각 나누어야하나요?

또한 Csv쪽에서 작성한내용이 테스트코드의 데이터 타입에 맞추어서 형변환을 지원해주는데
여기서 에러가 나버리면 이건 어떻게 처리를 해야할지 잘모르겠습니다..

그리고
LocalDate형태로 받게 되어있는데 만약 상황이 String으로 받고 localDate로 파싱해서 메소드를 진행하게된다면
이것은 별개의 시나리오가 되는것이고 동작도 파싱이 추가되니 해당 메소드의 테스트코드를 다시 만들어줘야하는게 맞는건가요?...
개인적으로는 그렇다곤 생각하는데 아 이거 짜보니깐 실패케이스도 Exception종류별로생각하면
진짜 하나의 메소드에 테스트코드만 10개도 나올수있을것같아요 지금 간단한 메소드에서도요

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q. 즉 테스트 given 에서 성공과 실패를할 데이터는 공존하면 안되는건가요? 각각 나누어야하나요?
A. 네 분리해야 합니다. 테스트 데이터는 성공하는 조건, 실패하는 조건 등으로 모두 분리해주셔야 합니다. 같은 성질의 데이터끼리 모아두어야 동일한 테스트가 가능하겠죠? 성공/실패 조건의 데이터를 모두 모아서 테스트한다면 결국은 파라미터를 받은 후 또 조건문을 작성해야 하는 경우가 생기기 때문에, 비슷한 조건의 테스트 파라미터를 모아 작성해주시면 되겠습니다.

Q. 또한 Csv쪽에서 작성한내용이 테스트코드의 데이터 타입에 맞추어서 형변환을 지원해주는데
여기서 에러가 나버리면 이건 어떻게 처리를 해야할지 잘모르겠습니다..
A. CSV타입의 데이터를 변환할 때에는 String으로 받아서, 테스트코드 내부에서 형변환을 해주세요. 타입 변환을 테스트 프레임워크에 의존하지 말고 파라미터 테스트는 말 그대로 파라미터 여러개를 제공만 해주는 역할이라고 생각해주세면 됩니다.
특정 타입의 주입이 필요한 경우 CSV가 아닌 다른 파라미터 테스트를 진행해주세요.

Q. LocalDate형태로 받게 되어있는데 만약 상황이 String으로 받고 localDate로 파싱해서 메소드를 진행하게된다면
이것은 별개의 시나리오가 되는것이고 동작도 파싱이 추가되니 해당 메소드의 테스트코드를 다시 만들어줘야하는게 맞는건가요?...
A. 이건 테스트 하고자 하는 로직이 무엇인지에 따라 달라집니다. 위 테스트코드는 findFirstSunday 메서드의 테스트로 첫번째 일요일을 찾을 수 있는지가 주요 관심사이며, LocalDate가 파싱이 잘 되는지를 테스트하는게 아닙니다. 따라서 해당 메서드 테스트는 파싱이 잘되는지를 테스트하지 말고 정확하게 해당 로직에 대해서만 테스트하는게 맞습니다. 만약 파싱에 대한 테스트를 하고싶다면 LocalDate와 관련된 별도 테스트를 구축하시면 됩니다.

Q. 개인적으로는 그렇다곤 생각하는데 아 이거 짜보니깐 실패케이스도 Exception종류별로생각하면
진짜 하나의 메소드에 테스트코드만 10개도 나올수있을것같아요 지금 간단한 메소드에서도요
A. 말씀하신대로 메서드 파라미터를 형변환하는것에 대한 테스트까지 모두 작성하려고하면 하나의 메서드에 수십 수백개의 테스트가 나올 수 있습니다. 따라서 가성비있게 테스트를 작성하려면 내가 원하는 부분을 정확하게 타겟하여 테스트코드를 작성하고, 그 이외에 대한 테스트는 하지 않는 것이 중요합니다.
예를 들어, "2024-10-01" 문자열을 LocalDate로 파싱했을 때 정상적으로 파싱되는지 여부는 이 테스트의 관심사가 아닙니다. 테스트는 findFirstSunday 메서드의 정상 동작이 최대 관심사라서 이 부분을 타겟하여 작성해주시면 됩니다. 파싱이 잘 되는지도 확인하고싶다면 위에 작성한것처럼 LocalDate 관련 학습용 테스트를 따로 작성하면 이후에는 다른 테스트에서 파싱 관련된 테스트를 하지 않아도 되겠죠?




/*
@MethodSource와 다르게 ArgumentsProvider 라는 커스텀데이터 제공 클래스를 통해 데이터를 제공한다
리턴타입은 ArgumentProvider의 ArgumentsStream이다. (Stream<? extends Arguments>)
항상 외부클래스로 구현해야하고 ArgumentsProvider 인터페이스를 반드시 구현해야한다.
이것역시도 자동으로 형변환을 지원해준다
*/
@ParameterizedTest
@DisplayName("@ArgumentSource 이용한 테스트")
@ArgumentsSource(CustomArgumentsProvider.class)
public void argumentSourceTest(LocalDate date) {
//given
//when
LocalDate result = FindSunday.findFirstSunday(date);
//then
assertEquals(LocalDate.of(2024, 10, 6), result);
}

}
Loading