어느 때처럼 기능 테스트를 하고 있었다.
그런데 당연히 결과가 똑같아야 할 아래의 두 테스트의 결과가 다르게 나왔다.
@Test
void whiteColorTest() {
String shipName1 = "Whiteship";
Ship whiteship = ShipFactory.orderShip(shipName1, "keesun@mail.com");
assertThat(whiteship.getColor()).isEqualTo("white");
}
@Test
void blackshipColorTest() {
String shipName2 = "Blackship";
Ship blackship = ShipFactory.orderShip(shipName2, "keesun@mail.com");
assertThat(blackship.getColor()).isEqualTo("black");
}
놀라서 테스트 결과를 보니, 더 충격적이었다.
org.opentest4j.AssertionFailedError:
expected: "white"
but was: "white️"
expected, actual의 문자열이 똑같아 보이는데... 왜 테스트는 실패할까...
혹시 몰라서 Ship
과 ShipFactory
의 코드를 다시 살펴 보았다.
Ship
은 String name, email, color 3개의 필드와 @Getter @Setter를 가진 단순한 클래스였고,
ShipFactory
에서 color를 세팅해주는 코드는 아래와 같았다.
public class ShipFactory {
public static Ship orderShip(String name, String email) {
...
// coloring
if (name.equalsIgnoreCase("whiteship")) {
ship.setColor("white️");
} else if (name.equalsIgnoreCase("blackship")) {
ship.setColor("black");
}
...
}
}
여전히 문제가 없는 것처럼 보였다...
왜 그럴까 고민을 하던 나는 whiteship.getColor().getBytes()
와 "white".getBytes()
를 통해 두 문자열을 비교해보기로 했다.
System.out.println("whiteship.getColor() : " + Arrays.toString(whiteship.getColor().getBytes(StandardCharsets.UTF_8)));
System.out.println("white : " + Arrays.toString("white".getBytes(StandardCharsets.UTF_8)));
그리고 그 결과는 아래와 같았다.
whiteship.getColor()
에 알 수 없는 [-1, -72, -113]이 들어가 있었다.
음수가 뜨는 이유는 조금만 생각해보니 알 수 있었다.
byte의 범위를 넘어서는 UTF-8의 문자가 들어있던 탓이다.
보이는 문자는 똑같지만, 뒤에 무언가가 있겠다 싶었고,
ShipFactory
의 코드 ship.setColor("white");
를 한 번 드래그 해보았다.
놀랍게도 공백이 없는 것처럼 보이지만, 무언가가 복사가 되었다.
그리고 해당 공백을 출력해보니 아래와 같은 오류를 맞이할 수 있었다.
64번 라인에 아무것도 없는 것처럼 보이지만...
\ufe0f ...?
우리의 선생님 구글님에게 여쭤보니, 유니코드의 Variation selector 라는 친구였다.
처음 보는 개념이고, 한국어로 된 정보도 없어서 블로그에 정리해보면 좋겠다는 생각이 들었다.
variant selector는 variant form에서 사용되는 문자라고 한다.
https://en.wikipedia.org/wiki/Variation_Selectors_(Unicode_block)
Variation Selectors (Unicode block) - Wikipedia
Unicode character block Variation SelectorsRangeU+FE00..U+FE0F(16 code points)PlaneBMPScriptsInheritedAssigned16 code pointsUnused0 reserved code points3.2 (2002)16 (+16) Code chartNote: [1][2] Variation Selectors is the block name of a Unicode code point
en.wikipedia.org
그럼 variant form이 무엇인지 다시 알아보자.
variant form이란, Unicode에 존재하는 문자를 variation sequence 를 통해서 조금 다르게 표현해주는 상형문자를 뜻한다.
https://en.wikipedia.org/wiki/Variant_form_(Unicode)
Variant form (Unicode) - Wikipedia
From Wikipedia, the free encyclopedia Jump to navigation Jump to search A variant form is a different glyph for a character, encoded in Unicode through the mechanism of variation sequences: sequences in Unicode that consist of a base character followed by
en.wikipedia.org
그럼 variation sequence는 또 무엇이냐?!
variation sequence는 유니코드 + variation selector 로 이뤄진 형태를 말한다고 한다.
variation selector를 알아보기 위해 멀리 왔다.
variation selector이란, 한 유니코드 문자를 특정한 형태의 상형문자로 나타내기 위한 문자 포멧이다.
variation selector는 \ufe00(VS1) 에서 \ufe0f(VS16)까지 총 16개가 존재하고, 현재에는 VS1, VS2, VS3와 VS15, 16 만이 정의되어 사용된다.
VS1, 2, 3은 전각 문자와 반각 문자(Chinese, Japanese and Korean : CJK), Manichaean(이란, 페르시안), 미얀마, 몽골, 수학 문자를 표현하기 위해 사용되고,
VS15(텍스트 형식의 흑백 이모티콘), VS16(컬러 이모티콘)은 이모티콘을 표현하기 위해 출력이 된다.
그 중에서 애타게 찾던 \ufe0f(=U+FE0F)는 VS16를 뜻한다.
궁금증을 다 해결하니 이런 생각이 들었다.
그래서 variant form은 도대체 어떻게 사용하는 거지?
굉장히 간단했다.
❣️(❣) 이모티콘(U+2763)을 예를 들어보자.
만약 U+2763만 출력하면, ❣️ 가 출력된다.
그런데 U+2763 U+FE0E 를 출력하면 ❣ 가 출력된다.
그리고 U+2763 U+FE0F 를 출력하면, ❣️가 출력된다.
그 이유는 variation selector가 없다면, 기본값은 VS16을 사용하도록 되어 있기 때문이다.
https://en.wikipedia.org/wiki/Emoticons_(Unicode_block)
Emoticons (Unicode block) - Wikipedia
Unicode character block EmoticonsRangeU+1F600..U+1F64F(80 code points)PlaneSMPScriptsCommonSymbol setsEmojiEmoticonsAssigned80 code pointsUnused0 reserved code points6.0 (2010)63 (+63)6.1 (2012)76 (+13)7.0 (2014)78 (+2)8.0 (2015)80 (+2) Note: [1][2] Emotic
en.wikipedia.org
결과적으로, 이유는 모르겠지만 U+FE0F가 "white" 문자에 껴있었고, 그래서 발생한 문제였다.
만약 TDD가 아니라면 발견할 수 없었을 문제였을텐데, 유니코드에 대해 새로운 내용도 배우고 유익했다.
😌 (VS16)
+) 추가적으로 아래의 글을 발견하였다. 해당 글을 읽고 나니, Java와 유니코드에 대한 더 깊은 이해가 가능했다.
https://meetup.toast.com/posts/317
Java에서의 Emoji처리에 대해 : NHN Cloud Meetup
Java에서의 Emoji처리에 대해
meetup.toast.com
'개발' 카테고리의 다른 글
[Spring] Spring MVC 구조에 대한 고민과 이해 (0) | 2022.11.10 |
---|---|
Java에서의 문자열 (0) | 2022.11.02 |
[Java] 인터페이스만 다중 상속이 가능한 이유? (0) | 2022.10.09 |
[Spring] java.lang.IllegalArgumentException: id to load is required for loading 오류 (0) | 2022.09.18 |
[Java] UnaryOperator, BinaryOperator 함수형 인터페이스 (0) | 2022.06.27 |