728x90
1주차의 Test코드를 조금 분석했었는데, 2주차 테스트코드를 작성할 때 도움을 많이 받았다.
그래서 2주차의 Test코드도 분석해보려고 한다!
1. ApplicationTest분석
class ApplicationTest extends NsTest {
private static final int MOVING_FORWARD = 4;
private static final int STOP = 3;
@Test
void 전진_정지() {
assertRandomNumberInRangeTest(
() -> {
run("pobi,woni", "1");
assertThat(output()).contains("pobi : -", "woni : ", "최종 우승자 : pobi");
},
MOVING_FORWARD, STOP
);
}
@Test
void 이름에_대한_예외_처리() {
assertSimpleTest(() ->
assertThatThrownBy(() -> runException("pobi,javaji", "1"))
.isInstanceOf(IllegalArgumentException.class)
);
}
@Override
public void runMain() {
Application.main(new String[]{});
}
}
해당 코드에 대해 분석을 해보겠다!
private static final int MOVING_FORWARD = 4;
private static final int STOP = 3;
- 처음에는 MOVING_FORWARD, STOP에 대한 상수를 정의해줬다
- 전진해야하는 상황에 대한 상수값과, 멈춰야할 상황에 대한 상수값을 가독성이 좋게 정의해둔 것 같다
(1) 전진_정지
@Test
void 전진_정지() {
assertRandomNumberInRangeTest(
() -> {
run("pobi,woni", "1");
assertThat(output()).contains("pobi : -", "woni : ", "최종 우승자 : pobi");
},
MOVING_FORWARD, STOP
);
}
- 테스트 이름을 보니 전진과 정지에 대한 테스트인 것 같다
- RandomNumberInRange에 대한 테스트를 한다
- 첫 인자는 Executable 인터페이스를 구현한 클래스의 객체이다
- execute()라는 메서드를 필수적으로 구현해야한다
- 이를 화살표 함수로 구현하여 일회용 객체를 전달하였다
- "pobi,woni"와 "1"을 Console의 입력값으로 세팅하고, 나온결과값에 "pobi :-", "woni : ", "최종 우승자 : pobi"가 포함되어있는지 확인한다
- 나머지 인자는 MOVING_FORWARD와 STOP 상수를 넣어줬다. (각각 4와 3을 값으로 가짐)
- 이는 Randoms 클래스의 pickNumberInRange의 반환값을 지정해주는 것이다
Assertions.assertRandomNumberInRangeTest
public static void assertRandomNumberInRangeTest(
final Executable executable,
final Integer value,
final Integer... values
) {
assertRandomTest(
() -> Randoms.pickNumberInRange(anyInt(), anyInt()),
executable,
value,
values
);
}
- assertRandomNumberInRangeTest 내부에서는 assertRandomTest를 호출한다
- 첫 번째 인자는 Verification을 구현한 클래스의 객체이다
- Verification을 구현하기 위해 apply() 메서드를 오버라이드 해야한다
- 이를 위해 화살표함수를 사용하여 apply를 구현하며 일회용 객체를 만든다
- 내부에서는 Randoms의 pickNumberInRange를 anyInt(), anyInt()로 호출한다
- 나머지 인자는 받은 걸 그대로 넣어주면 된다
Assertions.assertRandomTest
private static final Duration SIMPLE_TEST_TIMEOUT = Duration.ofSeconds(1L);
private static final Duration RANDOM_TEST_TIMEOUT = Duration.ofSeconds(10L);
private static <T> void assertRandomTest(
final Verification verification,
final Executable executable,
final T value,
final T... values
) {
assertTimeoutPreemptively(RANDOM_TEST_TIMEOUT, () -> {
try (final MockedStatic<Randoms> mock = mockStatic(Randoms.class)) {
mock.when(verification).thenReturn(value, Arrays.stream(values).toArray());
executable.execute();
}
});
}
- 내부에서는 assertTimeoutPreemptively를 호출한다
- assertTimeoutPreemptively의 설명은 다음과 같다 -> 제한시간 내에 실행파일이 실행되는지 확인
- 주어진 시간 제한이 초과되기 전에 제공된 실행 파일의 실행이 완료되는지 확인합니다.
참고: 실행 파일은 호출 코드와 다른 스레드에서 실행됩니다. 또한 시간 초과가 초과되면 실행 파일 실행이 사전에 중단됩니다. 가능한 바람직하지 않은 부작용에 대한 논의는 클래스 수준 Javadoc의 선점형 시간 초과 섹션을 참조하세요.
- 첫 번째 인자로 RANDOM_TEST_TIMEOUT을 넣어준다
- 10초로 설정한 Duration객체이다
- 10초 안에 주어지 코드가 실행되는지 확인한다
- 두 번째 인자는 Executable인터페이스의 구현체의 객체이다
- execute() 를 필수로 구현하기 위해 익명클래스를 구현한 모습이다
- 내부에서는 try-resource문을 이용하여 Randoms클래스에 대한 mock객체를 생성하고 mock의 동작을 설정한다
- verification코드를 수행하면 value와 values들을 연속으로 return하도록 mock의 동작을 설정한다.
- thenReturn에 대한 설명은 다음과 같다
- 메서드 호출 시 반환할 연속 반환 값을 설정합니다.
예: when(mock.someMethod()).thenReturn(1, 2, 3); 시퀀스의 마지막 반환 값(예: 3)에 따라 추가 연속 호출의 동작이 결정됩니다. Mockito.when에 대한 javadoc의 예를 참조하세요.
매개변수: value – 첫 번째 반환 값
값 – 다음 반환 값 반환: 스텁 연속 호출을 허용하는 객체
- 그리고 executable의 execute()를 호출한다
중간정리
@Test
void 전진_정지() {
assertRandomNumberInRangeTest(
() -> {
run("pobi,woni", "1");
assertThat(output()).contains("pobi : -", "woni : ", "최종 우승자 : pobi");
},
MOVING_FORWARD, STOP
);
}
- assertRandomNumberInRangeTest의 동작은
- 첫 번째 인자로 수행할 동작을 지정한다.
- 두 번째 이후 인자는 Randoms의 pickNumberInRange()가 연속으로 반환할 인자를 지정한다
- 동작을 수행하면서 중간에 Randoms.pickNumberInRange(anyInt(), anyInt())를 호출하면 해당 인자에 해당하는 값이 반환된다
- 10초 제한시간 내에 수행할 동작이 수행되는지도 확인된다
NsTest.run()
protected final void run(final String... args) {
try {
command(args);
runMain();
} finally {
Console.close();
}
}
private void command(final String... args) {
final byte[] buf = String.join("\n", args).getBytes();
System.setIn(new ByteArrayInputStream(buf));
}
protected abstract void runMain();
- command함수를 입력받은 인자들을 넣어 호출한다
- command함수는 인자들을 \n을 기준으로 하나의 String으로 만든 후, System의 In 값에 넣어준다(입력값에 넣어주는 것)
- 그리고 runMain()을 수행한다
- runMain()은 ApplicationTest에 구현이되어있으며, Application의 main함수를 실행하는 동작이 지정되어 있다
- 모든 동작이 끝난 후, 예외가 발생하든 발생하지 않든 Console을 close해준다
- 요약하면: 입력받은 인자들을 Console의 입력값으로 세팅하고, Application의 main함수를 호출
NsTest.output()
private PrintStream standardOut;
private OutputStream captor;
@BeforeEach
protected final void init() {
standardOut = System.out;
captor = new ByteArrayOutputStream();
System.setOut(new PrintStream(captor));
}
@AfterEach
protected final void printOutput() {
System.setOut(standardOut);
System.out.println(output());
}
protected final String output() {
return captor.toString().trim();
}
- output()을 수행하기 전 @BeforeEach가 달린 메서드인 init()이 먼저 수행된다
- init에서는 standartOut에 현재 System.out을 담아두고, captor에는 ByteArrayOutputStream 객체를 대입한다. 그리고 System.setOut메서드로 captor를 생성자로 넣은 PrintStream 객체를 세팅해준다
- output()에서는 captor를 String으로 바꾸고, 양 옆 공백을 제거한 값을 String으로 return한다
- output() 수행이 끝난 후에는 @AfterEach가 달린 메서드인 printOutput()을 호출한다
- System.setOut메서드로 standartOut을 넣어 다시 원래대로 돌리고
- System.out.println으로 콘솔에 값을 출력한다
- 요약: System.out의 값을 새로운 PrintStream에 담은 뒤, 해당 값을 trim()한 String값을 반환함
assertThat(output()).contains()
public static AbstractStringAssert<?> assertThat(String actual) {
return AssertionsForClassTypes.assertThat(actual);
}
//----------------------------다른 라이브러리------------------------------------
public SELF contains(CharSequence... values) {
strings.assertContains(info, actual, values);
return myself;
}
- assertThat은 새로운 StringAssert객체를 만들어 반환한다
- 설명은 다음과 같다 -> Creates a new instance of StringAssert from a String.
- contains는
- 설명은 다음과 같다
- Verifies that the actual CharSequence contains all the given values.
You can use one or several CharSequences as in this example:
assertThat("Gandalf the grey").contains("alf");
assertThat("Gandalf the grey").contains("alf", "grey");
Params:
values – the values to look for.
Returns:
this assertion object.
Throws:
NullPointerException – if the given list of values is null.
IllegalArgumentException – if the list of given values is empty.
AssertionError – if the actual CharSequence does not contain all the given values. - 실제 CharSequence에 지정된 값이 모두 포함되어 있는지 확인합니다.
다음 예에서와 같이 하나 이상의 CharSequence를 사용할 수 있습니다.
- 요약하면 output()에 지정한 값이 모두 포함되어있는지 확인한다
- "pobi : -", "woni : ", "최종 우승자 : pobi" -> 3개의 String값이 모두 포함되어 있어야 AsserttionError가 발생하지 않음
최종요약
@Test
void 전진_정지() {
assertRandomNumberInRangeTest(
() -> {
run("pobi,woni", "1");
assertThat(output()).contains("pobi : -", "woni : ", "최종 우승자 : pobi");
},
MOVING_FORWARD, STOP
);
}
- 첫 번째 인자로 수행하는 코드에서 Randoms.pickNumberInRange(anyInt(), anyInt())를 호출하면 두 번째 이후 인자의 값들이 연속적으로 반환된다
- "pobi,woni", "1" 값을 콘솔의 입력으로 세팅하여 Application의 main메서드를 호출하고, output()으로 main메서드가 System.out 에 보낸 값을 받아온다
- System.out의 값이 "pobi : -", "woni : ", "최종 우승자 : pobi" 를 포함하고 있는지 테스트한다
(2) 이름에_대한_예외_처리
@Test
void 이름에_대한_예외_처리() {
assertSimpleTest(() ->
assertThatThrownBy(() -> runException("pobi,javaji", "1"))
.isInstanceOf(IllegalArgumentException.class)
);
}
//Assertions----------------------------------------------------------
public static void assertSimpleTest(final Executable executable) {
assertTimeoutPreemptively(SIMPLE_TEST_TIMEOUT, executable);
}
- assertSimpleTest를 호출한다
- assertSimpleTest는 assertTimeoutPreemptively를 내부에서 호출한다
- 1초 안에 해당 함수가 수행이 되는지 테스트된다
- 인자로 Executable 인터페이스를 상속받은 클래스 객체를 넣어야한다
- 여기서도 화살표함수로 execute() 메서드를 구현해줬다
- assertThatThrownBy는 첫 번째 인자로 들어온 코드에서 예외가 발생하는지 테스트한다
- isInstanceOf로 발생한 예외가 IllegalArgumentException.class 타입인지 확인한다
NsTest.runException
protected final void runException(final String... args) {
try {
run(args);
} catch (final NoSuchElementException ignore) {
}
}
- 여기서는 run을 호출하고, NoSuchElementException은 무시한다 (왜 무시하는지는 모르겠다)
- run은 위에서 설명했듯이 args를 Console 입력값으로 넣어 Application의 main을 실행하는 함수다
요약
@Test
void 이름에_대한_예외_처리() {
assertSimpleTest(() ->
assertThatThrownBy(() -> runException("pobi,javaji", "1"))
.isInstanceOf(IllegalArgumentException.class)
);
}
- 1초안에 코드가 실행되는지 테스트함
- "pobi,javaji", "1" 를 인자로 넣어 Main 함수를 실행할 때 예외가 발생하는지 테스트함
- NoSuchElementException 예외는 무시함
- 해당 예외가 IllegalArgumentException 타입인지도 확인한다
2. StringTest: contains, containsExactly, isEqualTo, assertThatThrownBy
(1) split_메서드로_주어진_값을_구분()
@Test
void split_메서드로_주어진_값을_구분() {
String input = "1,2";
String[] result = input.split(",");
assertThat(result).contains("2", "1");
assertThat(result).containsExactly("1", "2");
}
- 쉼표를 기준으로 split하고 result배열에 받는다
- result 배열에 "2"와 "1"이 모두 포함되어있는지 테스트한다
- contains의 설명은 다음과 같다
- 실제 배열에 주어진 값이 순서에 관계없이 포함되어 있는지 확인합니다.
예 : String[] abc = {"a", "b", "c"};
// assertions는 패스한다
AssertThat(abc).contains("b", "a");
AssertThat(abc).contains("b", "a", "b");
// assertions는 실패한다
assertThat(abc).contains("d");
assertThat(abc).contains("c", "d");
Params: 값 – 주어진 값.
반환: 이 assertion 객체.
오류: NullPointerException – 주어진 인수가 null인 경우.
IllegalArgumentException - 주어진 인수가 빈 배열인 경우.
AssertionError – 실제 배열에 지정된 값이 포함되어 있지 않은 경우
- result 배열에 "1", "2"가 순서대로 포함되어있는지 테스트한다
- containsExactly의 설명은 다음과 같다
- 실제 배열에 주어진 값만 순서대로 포함되어 있는지 확인합니다.
예 : Ring[] elvesRings = {vilya, nenya, narya};
// assertions는 패스한다
assertThat(elvesRings).containsExactly(vilya, nenya, narya)
// 실제 순서와 예상 순서가 다르기 때문에 어설션은 실패합니다.
assertThat(elvesRings).containsExactly(nenya, vilya, narya);
Params: 값 – 주어진 값.
반환: 이 assertion 객체.
오류: NullPointerException – 주어진 인수가 null인 경우.
AssertionError – 실제 배열에 동일한 순서의 지정된 값이 포함되어 있지 않은 경우, 즉 실제 배열에 지정된 값이 일부 또는 전혀 포함되어 있지 않거나 실제 배열에 지정된 값보다 많은 값이 포함되어 있거나 값은 동일하지만 순서가 다른 경우 .
(2) split_메서드_사용시_구분자가_포함되지_않은_경우_값을_그대로_반환()
@Test
void split_메서드_사용시_구분자가_포함되지_않은_경우_값을_그대로_반환() {
String input = "1";
String[] result = input.split(",");
assertThat(result).contains("1");
}
- 위에서 설명한 contains로 해당 배열에 1이 포함되어있는지 확인한다
(3) charAt_메서드로_특정_위치의_문자_찾기
@Test
void charAt_메서드로_특정_위치의_문자_찾기() {
String input = "abc";
char charAtElement = input.charAt(0);
assertThat(charAtElement).isEqualTo('a');
}
- charAtElement가 'a' 와 같은지 확인한다
- isEqualTo의 설명은 다음과 같다
- 실제 값이 주어진 값과 같은지 확인합니다.
예:
// assertion will pass
assertThat('a').isEqualTo('a');
// assertions will fail
assertThat('a').isEqualTo('b');
assertThat('a').isEqualTo('A');
매개변수: 예상 – 실제 값과 비교할 주어진 값입니다.
반환: 이 주장 객체.
오류: AssertionError – 실제 값이 주어진 값과 같지 않은 경우.
- 실제 값이 주어진 값과 같은지 확인합니다.
(4) charAt_메서드_사용시_문자열의_길이보다_큰_숫자_위치의_문자를_찾을_때_예외_발생
@Test
void charAt_메서드_사용시_문자열의_길이보다_큰_숫자_위치의_문자를_찾을_때_예외_발생() {
String input = "abc";
assertThatThrownBy(() -> input.charAt(5))
.isInstanceOf(StringIndexOutOfBoundsException.class)
.hasMessageContaining("String index out of range: 5");
}
- charAt(5)로 주어진 문자열 길이보다 큰 인덱스를 찾을 때 예외가 발생하는지 확인한다
- 예외의 타입은 StringIndexOutOfBoundException인지 확인하고
- 예외의 메시지에 "String index out of range: 5"가 포함되는지 확인한다
728x90
'프로그래밍 언어 > Java' 카테고리의 다른 글
[JAVA] 단위테스트 공부3: 활용하기 (0) | 2023.11.01 |
---|---|
[JAVA] 클래스 내부 속성으로 가지고 있는 객체를 mock으로 설정하는 방법: @InjectMock이 동작하지 않는 이유 (0) | 2023.11.01 |
[JAVA] 문자열 양쪽 공백을 제거하는 trim과 strip 비교 (0) | 2023.10.29 |
[JAVA] 단위테스트 공부2: 테코톡 보고 정리 (0) | 2023.10.29 |
[JAVA] 단위테스트 공부1: Mock 알아보기 (1) | 2023.10.29 |