티스토리 뷰

Junit5의 기본적인 메소드들을 학습하는데, assertIterableEquals과 assertLinesMatch의 차이점이 궁금하였다.

먼저 Junit5 공식 문서를 통해 assertIterableEquals과  assertLinesMatch은 다음과 같음을 확인할 수 있었다.

 

(1) assertIterableEquals()

assertIterableEquals(Iterable<?> expected, Iterable<?> actual)
Asserts that expected and actual iterables are deeply equal.

 두 객체 Iterable(Iterable 인터페이스를 구현한 클래스)가 deeply equal한지 (내용 비교) 비교하는 것이다.

이때 두 iterable은 같은 순서로 동일한 element를 반환해야 하지만 두 iterable이 같은 타입을 반환해야하는 것은 아니다.

@DisplayName("Iterable equals")
@Test
void testIterableEquals() {
    ArrayList<String> expectedList = new ArrayList<>(List.of("luv", "2", "code"));
    LinkedList<String> actualList =  new LinkedList<>(List.of("luv", "2", "code"));

    assertIterableEquals(expectedList, actualList, "Expected list should be same as actual list");
}

이렇게 타입이 달라도 동일한 순서로 내용만 일치하는지를 판별하는 것이다.

(2) assertLinesMatch()

assertLinesMatch(List<String> expectedLines, List<String> actualLines)
Asserts that expected list of Strings matches actual list.

공식 문서에 따르면, assertLinesMatch는 list의 string값을 비교하는 것이다. 

여기서 나는 assertIterableEquals와 차이점이 무엇인지 혼동이 오기 시작했다. 

하지만 정리한 값은 다음과 같다.

1) assertLinesMatch()의 parameter로 List<String>과 Stream<String>이 올 수 있다.

2) 두 parameter를 비교하는 알고리즘은 다음과 같다.

① expected 라인과 actual 라인을 equals()로 한 pair씩 비교하고 일치하면 다음 pair로 넘어간다. 이까지는 assertIterableEquals()와 동일하다.

② 여기부터 다른데, 만약 내용이 완전히 동일하지 않다면 String.matches(String)을 통해 정규식으로 비교 한다. 그리고 일치하면 다음 pair로 넘어간다.

③ 그렇지 않으면 expected 라인이 fast-forward marker인지 확인하고  fast-forward marker를 적용하여 ①로 넘어간다.

 

많은 블로그를 찾아 보았지만 ast-forward marker가 무엇인지 정확히 이해할 수 없었지만, 유데미 강의 질문를 통해 완전한 이해를 할 수 있었다.

 

코드를 보면 다음과 같다.

@DisplayName("Lines Match")
@Test
void testLinesMatchExpectLineEquals() {
    List<String> expectedList = List.of("luv", "2", "code");
    List<String> actualList = List.of("luv", "2", "code");

    assertLinesMatch(expectedList, actualList, "Expected lines should match actual lines");
}

① expected 라인과 actual 라인을 equals()로 한 pair씩 비교하고 일치하면 다음 pair로 넘어간다.

-> 위의 케이스는 ① 항목에서 바로 테스트가 통과가 된다.

@DisplayName("Regex for any number")
@Test
void testLinesMatchRegularExpressionMatchAnyNumber() {
    List<String> expectedList = List.of("luv", "\\d+", "code");
    List<String> actualList = List.of("luv", "9", "code");

    assertLinesMatch(expectedList, actualList, "Expected lines should match actual lines");
}

②의 경우를 테스트 하기 위해 다음과 같이 테스트 코드를 작성해 볼 수 있는데, ①을 통해 동일하지 않는 항목이 발견되면 String.match(String)을 통해 정규식 일치 여부를 확인하고 테스트가 통과된다. \\d+가 9와 일치되기 때문이다.

@DisplayName("Regex for numbers 2 through 5")
@Test
void testLinesMatchRegularExpressionMatchNumbers2Through7() {
    List<String> expectedList = List.of("luv", "[2-7]+", "code");
    List<String> actualList = List.of("luv", "5", "code");

    assertLinesMatch(expectedList, actualList, "Expected lines should match actual lines");
}

 

위의 코드도 마찬가지로 [2-7]+ 정규식에 5가 부합하기 때문에 테스트 통과가 된다.

 

Fast-forward marker란 공식문서에 따르면 다음과 같다.

A valid fast-forward marker is an expected line that starts and ends with the literal >> and contains at least 4 characters. Examples:

-   >>>>

     >> stacktrace >>

     >> single line, non Integer.parse()-able comment >>

    Skip arbitrary number of actual lines, until first matching subsequent expected line is found. Any character between the fast-forward literals are discarded.

-   ">> 21 >>"

    Skip strictly 21 lines. If they can't be skipped for any reason, an assertion error is raised.

즉 >> 와 >> 사이에 적어도 4자 이상을 포함하고 있다면, 다음 일치하는 expected line이 발견될때 까지 건너 뛰는 것이다.

@DisplayName("Fast-forward marker, skip expected lines with markers")
@Test
void testLinesMatchFastForward() {
    List<String> expectedList = List.of("luv", "2", ">> just ignore this line >>", "code");
    List<String> actualList = List.of("luv", "2", "", "random","string", "code");

    assertLinesMatch(expectedList, actualList, "Expected lines should match actual lines");
}

위 코드에서 >>로 시작하고 끝나는 fast-forward marker가 있다면  다음 line과 일치하는 것이 발견될때 까지 expected line을 건너 뛴다. 

하지만 >>로 시작하고 끝나는 fast-forward marker안에 특정한 숫자를 넣으면 특정 숫자 만큼의 line을 건너 뛴다.

@DisplayName("Fast-forward marker, skip 4 lines")
@Test
void testLinesMatchFastForwardSkipLines() {
    List<String> expectedList = List.of("luv", "my", ">> 4 >>", "code");
    List<String> actualList = List.of("luv", "my", "one", "two", "three", "four", "code");

    assertLinesMatch(expectedList, actualList, "Expected lines should match actual lines");
}

 

위 코드에서 fast-forward 안의 숫자 4가 아닌 다른 숫자를 입력하면 테스트가 통과되지 못한다. 정확히 4개의 라인만 건너 뛰기 때문이다.

이제 assertLinesMatch() 메소드가 값을 비교하는 알고리즘을 완벽히 이해했다.

하지만 이것을 알고 난 후, 나는 이걸 어디다 쓰며 왜 이런 메소드를 만들었는지에 대한 의문이 들었고 인터넷에 물음을 통해 해답을 찾을 수 있었다.

 

(3) assertLinesMatch 사용하는 경우

assertLinesMatch() 메소드를 사용하는 경우는 다음과 같다.

- 텍스트를 match할 정규식을 적용하고 싶을때

- fast-forward line marker를 사용해 text의 line이나 text를 skip하고 싶을때.

 

이것의 예시로 파일에서 정보를 읽을때 fast-forward marker를 통해 text의 라인을 skip 하는 것을 들 수 있다.

src/test/resources/ 경로 안에 actual-data-file.txt와 expected-data-file.txt를 다음과 같이 생성한다.

expected-data-file.txt
expected-data-file.txt
actual-data-file.txt

파일을 위와 같이 생성하고, actual-data-file.txt에 skip하고 싶은 header가 있다고 가정하자.

( FIRST_NAME, LAST_NAME, EMAIL_ADDRESS, COMPANY_NAME)

이 header들을 무시하고 테스트 시 skip하고 싶을때 사용 할 수 있다.

예시가 너무 억지 같지만, spreadsheet나 database 비교 시 skip 하고 싶은 부분이 있을때 이 assertLinesMatch() 메소드를 사용할 수 있다.

@DisplayName("Fast-forward marker, skip 4 lines - using files")
@Test
void testLinesMatchFastForwardSkipLinesUsingFiles() throws IOException {
    List<String> expectedList = Files.readAllLines(Paths.get("src/test/resources/expected-data-file.txt"));
    List<String> actualList = Files.readAllLines(Paths.get("src/test/resources/actual-data-file.txt"));

    assertLinesMatch(expectedList, actualList, "Expected lines should match actual lines");
}

위의 예시를 코드로 구현해 보면 다음과 같이 테스트가 통과됨을 확인할 수 있다.

 

@DisplayName("Fast-forward marker, skip 4 lines - using files streams")
@Test
void testLinesMatchFastForwardSkipLinesUsingFilesStreams() throws IOException {

    Stream<String> expectedList = Files.lines(Paths.get("src/test/resources/expected-data-file.txt"));
    Stream<String> actualList = Files.lines(Paths.get("src/test/resources/actual-data-file.txt"));

    assertLinesMatch(expectedList, actualList, "Expected lines should match actual lines");
}

Stream<String>을 사용할 수도 있는데, Files.readAllLines()와 Files.lines()의 차이다.

그럼 이 둘 메소드의 차이점은 무엇일까?

(3)  Files.readAllLines()와 Files.lines()의 차이

m<String>을 사용할 수도 있는데, Files.readAllLines()와 Files.lines()의 차이다.

그럼 이 둘 메소드의 차이점은 무엇일까?

차이점은 메모리에 로딩하는 것을 Eager vs Lazy의 차이이다.

Files.readAllLines() 메소드는 전체 파일을 읽어 모든 내용을 메모리에 로드한다.

반면 Files.line() 메소드는 전체 파일을 한번에 메모리에 로드하지 않고, Stream<String>을 반환해 데이터가 필요할때 lazy하게 로딩한다.

따라서 매우 용량이 큰 파일을 읽을때에는 Files.line()을 통해 메모리 사용의 효율성을 제공하여 준다

 

Reference :

https://junit.org/junit5/docs/5.0.1/api/org/junit/jupiter/api/Assertions.html

 

Assertions (JUnit 5.0.1 API)

Asserts that all supplied executables do not throw exceptions. If any supplied Executable throws an exception (i.e., a Throwable or any subclass thereof), all remaining executables will still be executed, and all exceptions will be aggregated and reported

junit.org

https://www.baeldung.com/junit-assertions

 

Assertions in JUnit 4 and JUnit 5 | Baeldung

A look at assertions in both JUnit 4 and 5.

www.baeldung.com

https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/Assertions.html#assertLinesMatch(java.util.List,java.util.List) 

 

Assertions (JUnit 5.9.1 API)

Assert that all supplied executables do not throw exceptions. If any supplied Executable throws an exception (i.e., a Throwable or any subclass thereof), all remaining executables will still be executed, and all exceptions will be aggregated and reported i

junit.org

Udemy 강의 : Spring Boot Unit Testing with JUnit, Mockito and MockMvc

 

 

Spring Boot Unit Testing with JUnit, Mockito and MockMvc

Develop Real-Time Spring Boot Unit Tests: JUnit 5, Mockito, MockMvc, TDD, JsonPath, Hamcrest, H2 Embedded DB, MySQL

www.udemy.com

 

 

 
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday