본문 바로가기

개발

유니코드와 variant form, variant selector

어느 때처럼 기능 테스트를 하고 있었다.

그런데 당연히 결과가 똑같아야 할 아래의 두 테스트의 결과가 다르게 나왔다.

 

    @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의 문자열이 똑같아 보이는데... 왜 테스트는 실패할까...

 

혹시 몰라서 ShipShipFactory의 코드를 다시 살펴 보았다.

 

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