Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.

유닛테스트에 대한 생각

요즘 SNS에서 왠지 TDD 혹은 테스트에 대한 글을 많이 봤다. 나름 테스트를 열심히 작성하는 처지에서 다른 개발자의 테스트 글을 보다 보니 나도 한번 정리해봐야겠다고 생각했다.

한 6~8년 정도 전에는 TDD가 한창 뜨겁던 시절이었는데 가장 유명한 책은 역시 켄트 벡의 TDDBE인데 여기서도 꽤 많은 통찰력을 얻었지만, 실용적으로 나에게 다가오기 시작한 것은 채수원 님의 테스트 주도 개발을 읽고 나서부터였던 것 같다. 이때까지만 해도 업무환경에서 TDD는커녕 테스트 작성도 쉽지 않았던 터였다가 내가 알아서 TDD를 할 수 있는 환경에 오고 나서는 열심히 TDD를 적용하려고 노력했고 한 달 정도 노력한 뒤에 TDD를 한 달여 정도 해보고 나서....라는 글을 남겼다.

그전까지는 책이나 글로 배운 이론상의 TDD가 머릿속에 있었으면 이때부터 내가 실제로 적용해 보면서 내가 편한 부분이나 책에서는 좋다고 했는데 나는 잘 모르겠는 부분 등이 구분되기 시작했던 것 같다. 이때의 경험을 TDD를 한 달여 정도 해보고 나서....에 정리해 놓았는데 오랜만에 읽어보니 지금 읽어도 고민의 수준이 크게 달라지진 않은듯한 기분이 들어서 약간 좌절 중이다. 그리고 1년 정도 뒤에 TDD에 대한 생각을 좀 더 적은 글도 있다.

예전에는 고민했던 내용


테스트를 정말 작성해야 하는가?

이 부분은 지금은 완전히 종결된 질문이다. 이젠 테스트의 유용성 같은 건 고민하지 않고 오히려 테스트가 없으면 코드를 작성할 수 없는 지경이 이르렀다. 아주 간단한 코드가 아니라면 테스트 없이는 코드를 작성하지 않는다. 단 몇십 줄의 코드를 작성하더라도 내 코드가 의도대로 동작하는지 확인하려면 REPL에서 돌려보던 main() 메서드를 쓰던 HTTP로 찔러보든 간에 테스트가 필요한데 어차피 테스트할 거 유닛테스트로 만들어서 계속 확인하지 않을 이유가 없다.

내가 작성한 코드가 수정 후에도 계속 동작하고 이번 릴리스가 배포된 후에도 문제가 생기지 않을 거라는 보장을 하는 방법에서 테스트 코드 이상의 방법을 알지 못한다. (잘 작성되었다는 하에) 테스트코드가 있으면 CI에서 계속 돌려보면서 코드의 버그를 파악할 수 있고 배포 전에도 최소한의 안정성을 기대할 수 있다. 그러다 보니 테스트를 항상 같이 작성하게 되었고 특히 테스트가 없으면 리팩토링은 전혀 못 하는 상황까지 왔다.

내가 TDD를 잘하고 있나?

처음 TDD로 접해서 그런지 예전에는 TDD에 집착을 많이 했다.(TDD와 유닛테스트를 혼용해서 쓰기도 하는데 여기선 구분해서 쓰고 내 입장은 이젠 이 둘을 구분하는 것 자체가 큰 의미 없다고 보는 편이다) 예를 들면 테스트를 먼저 작성한다는 게 어느 정도인지? 테스트를 먼저 작성해서 Fail을 보고 코드를 작성해서 Pass를 보라는데 항상 그래야 하나? 간단한 로직이라도? 이런 걸 고민하면서 TDD를 하려고 노력하면서 내가 하는 게 TDD가 맞나? 방금 한 것은 TDD 규칙을 어긴 건가? 하는 고민을 했다.

지금은 이런 규칙을 거의 신경 쓰지 않는다. 난 내가 하는 걸 TDD라고 부르지는 않고 그냥 유닛테스트라고 부르는데 TDD의 규칙에서 벗어나기 위해서인지도 모르겠다. 내가 테스트를 작성할 때의 느낌을 생각해 보면 코드와 같이 테스트를 작성한다는 말이 정확할 것 같다. 그냥 코드를 작성하는 과정에서 테스트 코드를 작성하는 게 포함되어 있으므로 테스트가 먼저인지 나중인지는 크게 신경 쓰지 않지만, 코드를 작성하면 테스트도 다 작성되어 있다. 코드만 먼저 작성하고 나중에 테스트를 작성하는 식으로 분리되어 있지 않다.

via GIPHY



테스트를 작성해서 좋은 점


기술부채를 갚기 위한 기반

테스트를 열심히 작성하는 이유는 실제로 도움이 되기 때문이다. 위에서 말한 대로 코드를 돌려보지 않고 직접 버튼을 눌러보던 호출해 보던 테스트를 해보지 않을 수는 없다. 이런 테스트는 반복해서 하게 되는데 이를 유닛테스트로 작성해 놓으면 쉽게 코드를 돌려볼 수 있다. 코드를 작성하다 보면 자연히 기술부채가 쌓이게 마련이다. 설계를 잘못했을 수도 있고 일정에 쫓기다 보니 어설픈 코드가 들어갈 수도 있다. 아니 잘못한 게 없다고 하더라도 빠르게 기술도 달라지게 마련이고 비즈니스에 맞추다 보면 요구사항도 많이 달라져서 기술부채가 쌓이기도 한다.

이런 기술부채를 쌓지 않으려고 노력해야 하지만 노력한다고 완전히 없어지진 않는다. 완전히 없앨 수 있다면 나중에 기술부채를 쉽게 갚을 수 있는 기반을 마련해 두는 게 좋다는 게 내 생각이다. 그리고 기술부채를 쉽게 갚을 수 있는 가장 좋은 방법이 유닛테스트를 잘 작성해 놓는 것이다. 더 좋은 방법이 있다면 사용하겠지만, 아직 찾지 못했다. 테스트를 잘 작성해 놓으면 기능 추가할 때도 큰 걱정 없이 할 수 있고 리팩토링할 때도 맘 놓고 할 수 있다. 다른 곳에 영향이 없을 거라고 생각하고 수정했는데 테스트가 깨져서 다른 부분에 영향을 준 것을 발견하게 되면 정말 테스트 작성해 놓길 잘했다는 생각이 든다.

코드의 사용자 입장

테스트를 작성하면 내가 작성한 메서드나 클래스의 사용자 입장이 되는 기분이 든다. 예전에는 테스트를 어떻게 작성해야 하는지 고민하기 바빠서 이런 생각을 못 했는데 점점 익숙해질수록 코드의 사용자 입장이 되어 본다는 것은 중요하게 느껴진다. 여기서 사용자 입장이라는 것은 라이브러리를 만들어서 다른 사람에서 제공하기 때문에 필요하다기보다는 내가 혼자 작성하더라도 그 코드를 다른 코드에서 다시 불러서 사용해야 하므로 항상 사용자와 작성자 입장이 반복되게 된다. 코드를 작성할 때 필요한 기능을 고민하고 코드를 어느 정도 머릿속에서 그리면서 코드를 작성하게 되는데 뜻밖에 실제로 사용하려고 하다 보면 인터페이스 등이 사용패턴에 잘 맞지 않는 경우가 있다. 머릿속에서 한 번에 잘 그려지는 사람은 상관없겠지만 나 같은 경우에는 테스트코드를 작성하면서 이런 경험을 많이 하게 된다. 이렇게 파라미터를 받고 값을 반환하면 될 거라고 생각했는데 테스트를 작성하다 보니 생각한 형태로 사용하기 어렵다거나 다른 값이 더 필요하다거나 하는 것을 발견하게 된다.

코드 사용법의 문서화

테스트를 잘 갖춰놓으면 문서를 따로 만들지 않더라도 자연히 코드 사용법을 알려주는 문서가 된다. 오픈소스를 볼 때 문서를 보기도 하지만 문서는 최신화가 안 된 경우도 많으므로 어떻게 사용하는지 확인할 때는 테스트 코드를 보는 경우가 많다. 마찬가지로 내가 작성한 코드에서도 이 코드를 어떻게 사용하고 예외는 어떤 식으로 다루길 원하는지가 테스트코드에 자연스럽게 나타난다. 일일이 사용법 설명을 굳이 적지 않아도 해당 메서드를 사용할 때 테스트코드를 참고할 수 있다.

코드 작성 방식의 변환

정확히 예전엔 어떻게 코드를 작성했는지 다 기억나진 않지만, 예전에는 코드를 테스트(유닛테스트 말고)해봐야 하니 위에서부터 아래로 만들었다면 지금은 밑에서부터 쌓아 올리는 느낌이다. API를 구현한다거나 화면에 무슨 기능을 추가하면 API 엔트리포인트나 화면에 버튼 등을 만들고 이를 테스트해보면서 구현했던 것 같다. 코드를 작성하면서 계속 테스트해보려고... 지금은 유닛테스트로 어떤 단위의 코드든 실행해 볼 수 있으므로 모델에서 기능을 구현하면서 필요한 유틸리티 함수를 만들면서 올라가서 마지막에 API 엔트리포인트나 화면 등은 붙이는 식으로 작업할 때가 많다. 어느 쪽이 더 좋은지는 모르겠지만 나는 지금의 방식이 더 견고한 느낌이 들어서 좋고 함수의 재사용이나 오류를 어느 단계에서 처리할지가 더 명확해져서 좋다.

유닛 테스트 코드


여전히 고민하는 부분


어떤 테스트가 잘 작성한 테스트인가?

계속해서 테스트를 작성하지만 지금 테스트를 잘 작성하고 있는가는 여전히 고민인 부분이다. 어떤 코드가 좋은 코드인가를 계속 고민하듯이 어떤 테스트가 좋은 테스트인지도 아마 계속 고민해야 할 것 같다. 이건 커버리지를 얘기하는 게 아니다. 경험상 코드를 다 작성하고 나중에 테스트를 추가하는 게 아니라 코드를 작성하면서 같이 유닛테스트를 작성한다면 커버리지는 80% 안팎으로는 나온다. 커버리지는 테스트에서 빠진 부분이 있는지 확인하는 기본 지표이고 최대한 간결한 테스트코드로 문제가 생기면 발견할 수 있도록 다양한 경우를 커버하는 것이다.

예전에 테스트를 어느 정도까지 작성해야 하나요? 라는 질문에 "안심될 때까지"라는 말을 본 적이 있다. 나는 걱정이 많아서인지 안심이 잘 안 돼서 테스트를 많이 짜는 편이라 테스트코드가 좀 장황하다. 대형 오픈소스 프로젝트를 보면 이 정도로 테스트가 충분한가 싶을 정도로 테스트 코드가 간결한 경우를 많이 보게 된다. 익명의 다수가 참여하는 오픈소스를 관리하는 것과 팀에서 개발하는 프로젝트는 좀 관점이 다르긴 하지만 난 오픈소스가 가장 진보된 형태의 개발문화를 가지고 있다고 생각해서 충분히 안심할 수 있으면서 간결한 테스트를 작성하고 싶은데 아직은 그게 잘 안된다.

위에서 코드를 작성할 때 아래부터 작성해서 올라간다고 설명했는데 이럴 때 테스트가 너무 장황한가? 하는 고민을 하게 된다. "컨트롤러" - "서비스" - "모델" 로 구성되어 있다고 하면 모델부터 작성하므로 서비스 단계에서 테스트를 작성할 때는 당연히 모델 쪽의 코드가 다시 테스트가 되고 컨트롤러로 가면 더욱 그렇다. 아래 계층에서 테스트한 건 굳이 테스트하진 않지만 그럼에서 각 계층에 로직이 있으므로 이 부분을 테스트하다 보면 자연히 테스트 대상이 중첩된다. 이럴 때는 이렇게 하는 게 맞을까 하는 고민을 계속 하고 있다.

mocking

테스트를 작성할 때 mocking, stub, spy 등 다양한 기법이 있지만 여기서는 통칭 mocking이라고 하겠다. 처음 테스트를 공부할 때 "인터넷 선을 뽑아도 테스트는 돌아가야 한다"라는 얘기를 들었었는데 나는 심하게 mocking을 하는 편은 아니다. 대표적으로 데이터베이스는 로컬에 데이터베이스를 띄워서 데이터를 직접 넣었다 지웠다 하면서 테스트하고 다른 외부 자원도 가능하다면 직접 사용해서 테스트하는 편이다.

mocking을 많이 하면 테스트는 편하지만(mocking 코드를 작성하는 건 귀찮지만) 실제로는 안 돌아가는데 테스트만 통과할 가능성이 커진다. 프로덕션에 배포하면 mocking 없이 돌아가야 하므로 테스트에서 가능하면 실제와 비슷하게 만들려고 한다. 그런데도 mocking이 필요한 부분이 있는데 가장 많이 하는 것은 소셜로그인이나 외부 자원이 확인이 어려운 경우 정도이다. 소셜로그인이나 외부 API를 사용하는 경우에는 토큰 발급 등 테스트코드에서 자동화하기 쉽지 않은 부분이 있으므로 이 호출 자체를 mocking 해서 내가 작성한 코드만 확인한다. 타 서비스를 사용해서 이메일이나 SMS를 발송하는 때도 발송 자체는 외부 서비스에 의존하고 있고 발송 여부 자체를 내가 따로 확인할 수는 없으므로 mocking 해버린다. 아~ 그리고 외부자원을 사용할 때 과금이 되는 경우도 mocking을 한다. 돈이 나가면 안 되니까... ㅎ

내가 유닛테스트를 작성할 때는 실제로 코드가 어떻게 동작하는지 확인하는 용도이다. 외부 API를 사용한다면 실제로 외부 API가 어떻게 동작하는지 확인해야 하므로 코드를 작성할 때는 외부 API를 사용해서 개발한다. 그래서 난 이 코드를 그대로 남겨두고 코드 작성과 테스트가 완료되면 mocking을 하는 식으로 작성한다. 외부 API 변경이나 버그 등으로 나중에 다시 확인해 보고자 할 때는 이 mocking 코드만 주석 처리하면 큰 노력 없이 실제로 테스트해볼 수 있다.

복잡한 픽스쳐(Fixture)

픽스쳐에 다양한 과정이 포함되지만, 대표적으로는 데이터베이스가 있다. 위에 말한 대로 데이터베이스에 직접 테스트하므로 픽스쳐단계에서 필요한 사용자 생성이나 관련 정보 설정을 해주어야 하고 tear down 단계에서 다시 정리를 해주어야 한다. 테스트를 작성할수록 다양한 픽스쳐가 필요하므로 금세 많이 복잡해진다. 픽스처를 만드는 부분을 공통화하고 보기 좋게 하려고 노력하지만, 테스트에 필요한 준비를 하다 보면 쉽게 정리가 잘 안 된다. 픽스처를 아주 깔끔하게 관리하고 싶은데 이 부분은 여전히 큰 고민 중 하나이다.

I find that writing unit tests actually increases my programming speed. - Martin Fowler



2017/02/25 10:00 2017/02/25 10:00

사이드 프로젝트로 만든 GitHub 번역용 크롬 익스텐션

작년 11월에 구글 번역신경망이 적용되면서 그동안 형편없던 영어-한글 번역에 새로운 세상이 왔음을 알게 되었다. 많은 개발자가 이를 보고 뭔가 이용할 수 있는 게 없을까 하는 생각을 했을 테고 내 주변에도 간단한 도구나 확장으로 번역해 보는 걸 만들었지만, API가 없어서 제대로 뭔가를 만들기는 어려웠다. 나도 그러던 중 뭔가 만들어 볼 수 있지 않을까 해서 Google 클라우드 번역 API를 보다 보니 언제 나왔는지 몰라도 신경망에 기반을 둔 번역이 프리미엄으로 제공되는 것을 알게 되었다.

Standard 번역이 기존의 기계번역이고 신경망 번역이 Premium인데 아직 베타 상태라 신청을 해서 허락을 받아야 사용할 수 있다. 원래는 만들어서 공개할 생각이었지만 일단 신청서는 팀 내부에서만 쓰려고 이런 번역 크롬 익스텐션을 만들려고 한다고 신청서를 작성해 놓고 기대도 안 하고 잊고 있었다.

구글에서 받은 승인 메일

그랬더니 웬걸 갑자기 승인되었다고 메일이 왔다. 앗, 이러면 진짜 만들어 봐야겠는데.... 하고는 만들게 되었다.

GitHub의 댓글 번역

영문 글도 많이 보고 GitHub도 많이 쓰는데 블로그나 기사는 맘먹고 집중해서 읽으면 그나마 읽을 수 있는데 GitHub 이슈나 Pull Request에서 이뤄지는 다양한 논의는 이해하기가 쉽지 않았다. 해커뉴스의 댓글도 마찬가지. 이런 글은 정리해서 쓴 글도 아니고 여러 사람이 다양한 의견을 내놓으므로 내 어설픈 영어로는 문맥을 파악하기가 어려웠고 집중해서 읽기에는 글이 너무 많았다. 나는 한글로 댓글을 보듯이 하나하나 정독하는 게 아니라 훑어보면서 대략적인 문맥만 파악하기를 원했다.

Google 번역 크롬 익스텐션이 있지만 나는 이런 도구로는 내용을 파악하기가 어려웠다. 왜냐하면, 원래 글의 모양 그대로 보여주는 것이 아니라 어떤 문단이 어떻게 번역되었는지 비교하기가 어려웠고 구글 번역의 경우 줄 바꿈이 있으면 별도의 문장으로 번역하는데 GitHub은 마크다운을 많이 쓰다 보니 결과 번역 품질이 좋지 않았다.

내가 원한 건...

  1. GitHub 화면에서 간단한 액션으로 번역 결과를 봐야 한다.
  2. 번역이 이상하면 원문을 봐야 하므로 원문이 같이 나왔으면 좋겠다.
  3. GitHub은 코드나 이미지도 있고 마크다운으로 스타일도 주므로 번역 글을 쉽게 보려면 이러한 스타일이 최대한 유지되어야 한다.

이 정도 목표를 가지고 크롬 익스텐션을 만들기 시작했다.

대충 보면 알겠지만, 처음에는 쉽게 생각했다. 내용을 가져와서 번역하고 그 결과를 화면에 뿌려주면 되는 게 끝이다. 원래는 연초의 일주일 정도 기분전환용 프로젝트로 시작했다. 하지만 1월 내내 이거만 붙잡고 있었지만...

Issue Translator for GitHub

GitHub의 트레이드마크를 침해하지 않으려고 위와 같은 이름을 사용했다. 개발과정은 뒤에 설명하겠지만 일단 결론부터 얘기하면 다음과 같은 크롬 익스텐션을 만들었다. 저장소 참고.

익스텐션의 옵션 화면

먼저 Issue Translator for GitHub를 크롬에 설치하고 API 키와 번역할 언어를 선택한다.(기준은 항상 영어)

데모

이제 GitHub에서 이슈나 풀 리퀘이스트에 가면 댓글마다 오른쪽 위에 지구본 모양의 번역 버튼이 나타난다. 이 버튼을 누르면 구글 번역 API로 번역한 결과를 아래에 보여준다. 이때는 원글의 스타일이나 링크를 최대한 유지한다. 생각보다는 잘 나와서 GitHub을 볼 때 애용하고 있다.

하지만

이 크롬 익스텐션은 Chrome 웹 스토어에 아직 등록하지 않았다. 왜냐하면, 구글 번역 API 프리미엄이 아직 베타 상태이기 때문이다. 그래서 공개하더라도 구글에 신청해서 키를 받지 않는다면 이 플러그인을 쓸 수가 없다. 2월 중에는 베타가 끝나고 공개되지 않을까 해서 기다리고 있었는데 계속 나오지 않아서 그냥 공개하고 나중에 베타가 끝나면 스토어에 올리거나 하기로 했다. 참고로 구글 번역 API는 유료 API이므로 내가 과금을 하지 않으려면 사용자가 구글 클라우드에 가입해서 API를 사용하게 해야 한다.

일단 이 글은 개발 정리용이다.

초기 개발

처음에는 아주 간단히 생각했다. 한꺼번에 전체를 다 번역하려고 하다 보니 API가 유료라서 불필요한 글자 수를 줄이기 위해 댓글마다 버튼을 추가하고 버튼을 누르면 댓글 내용을 구글 API에 던졌다가 받아서 아래에 보여주면 끝날 거라고 생각했다. 그래서 며칠이면 될 것 같았다.

일단 버튼이 필요하므로 GitHub의 Octicons에서 지구본 모양을 골랐다. Octions 모듈에서 API를 잘 제공해서 아이콘의 SVG 패스를 바로 출력해 준다.

$ octicons.globe.path
'<path fill-rule="evenodd" d="M7 1C3.14 1 0 4.14 0 8s3.14 7 7 7c.48 0 .94-.05 1.38-.14-.....'

이를 크롬 익스텐션이 로드될 때 댓글 영역을 찾아서 댓글 상단에 버튼을 넣었더니 원래 있던 듯 자연스럽게 어울렸다.

번역 버튼

크롬 익스텐션을 만들 때 편한 점은 크로스 브라우징을 신경 쓰지 않아도 된다는 거다. 그래서 jQuery나 다른 라이브러리도 사용하지 않고 V8에서 지원하는 JavaScript로만 작성하려고 querySelector등을 열심히 사용하고 API 요청도 fetch API를 사용했다. 하다 보니 API가 초당 10개가 넘으면 오류가 발생해서 요청마다 약간의 딜레이를 주어서 1초에 10개 이상이 나가지 않도록 제어해야 했다.

대충 GitHub의 댓글 DOM 구조를 파악해보니 한 댓글의 각 문단이 <p>태그로 묶이는 걸 알 수 있었다. 댓글 내용 중에 코드 블럭이나 이미지는 블럭할 필요가 없으므로 이는 제외하고 다른 <p>태그만 가져와서 구글 번역을 했더니 다행히도! 번역하면서 HTML 마크업까지 그대로(사실 100%는 아니다) 돌려주었다. 이를 댓글 하단에 보여주니까 제법 그럴듯했다.

그래서 이것저것 테스트하면서 다양한 케이스를 좀 살펴본 다음에 지인 중심으로 플러그인을 뿌렸다. 어차피 Premium API가 비공개이므로 시간이 충분하다는 생각이었다.

익스텐션 개선

피드백 받다 보니 번역 버튼이 나오지 않는다는 얘기가 나왔다. 난 테스트를 하느라고 다양한 댓글을 여러 탭에 띄워놓고 테스트하느라고 몰랐는데 일반적인 패턴에서 사용하다 보니 다른 페이지에서 이슈나 풀 리퀘스트로 넘어가면 버튼이 보이지 않고 페이지를 갱신해야 보였다. 상황을 이해했을 때 대충 짐작이 갔지만, GitHub이 pjax를 쓰고 있어서 메뉴를 바꿀 때 페이지를 새로 로드하는게 아니라 내용 부분만 바꿔친다. 크롬 익스텐션은 페이지 로딩을 방해하지 않으려고 페이지 로딩 후에 익스텐션을 활성화하는데 이게 한 번만 실행되므로 pjax로 메뉴가 바뀌면 댓글에 번역 버튼이 추가되지 않았다. 나는 페이지가 로딩되면 댓글이 있는지 확인 후에 버튼을 넣어주도록 구현해 놓고 있었다.

먼저 GitHub의 동작을 파악하기 위해서 테스트해보니 push state로 조작하는 걸 알 수 있었고 push state의 상태변경을 탐지하기 위해 찾아보니 chrome.webNavigation로 할 수 있는 걸 알게 되었다. manifest.json에서 webNavigation에 대한 권한을 얻은 후 백그라운드 스크립트에서 다음과 같이 히스토리가 변경되는 이벤트를 받았다. 문서에 잘 나와 있지 않아서 꽤 헤맸는데 페이지에서 어떤 스크립트를 동작하게 하는 것을 크롬 익스텐션에서는 콘텐츠 스크립트라고 부르는데 이 이벤트 탐지는 백그라운드 스크립트에서 해야 한다.

if (chrome.webNavigation && chrome.webNavigation.onHistoryStateUpdated) {
  chrome.webNavigation.onHistoryStateUpdated.addListener((details) => {
    chrome.tabs.sendMessage(details.tabId, {action: 'rerun', url: details.url});
  });
}

여기서 받은 이벤트를 chrome.tabs.sendMessage로 해당 탭에 메시지를 보내도록 했다. 콘텐츠 스크립트에서는 다음과 같이 받아서 재실행을 해주었다.

chrome.extension.onMessage.addListener(function(msg) {
  if (msg.action === 'rerun') {
    if (msg.url === location.href) {
      // 버튼 추가 재 실행
    }
  }
});

조건문이 있는 이유는 테스트하다 보니 페이지가 바뀔 때 현재 페이지 URL과 다음 페이지 URL 이렇게 2번씩 이벤트가 발생하므로 1개는 무시하기 위해서 넣은 것이다.

그리고 번역품질이 좋지 않다는 피드백이 있었다. 피드백 받은 댓글로 테스트를 해보니 실제로 번역품질이 많이 안 좋았다. 구글 번역에서 좀 더 테스트를 해보니까 특수기호가 구글 번역결과에 많은 영향을 준다. 아주 간단한 예를 들어 다음과 같은 문장을 보자.

This commit implements the Web IDL USVString conversion, which mandates all unpaired Unicode surrogates be turned into U+FFFD REPLACEMENT CHARACTER.

이는 다음과 같이 번역된다.

이 커밋은 모든 <b> 비 유니 코드 </ b> 대리자가 U + FFFD REPLACEMENT CHARACTER로 변경되도록하는 Web IDL USVString 변환을 구현합니다.

하지만 <b> 태그가 없다면 다음처럼 더 자연스럽게 번역이 된다.

이 커밋은 웹 IDL USVString 변환을 구현합니다.이 변환은 모든 비 유니 코드 사로 게이트를 U + FFFD REPLACEMENT CHARACTER로 변환하도록 요구합니다.

이는 아주 사소한 비교이지만 실제로는 더 다양한 케이스가 있고 태그하나 들어가냐 아니냐에 따라서 번역품질이 많이 달라졌다. ㅠ 결국은 번역을 해주고자 하므로 스타일 등을 버리고 문장만 반역한다면 훨씬 좋은 결과를 얻을 수 있었고 테스트한 지인에게 물어봐도 번역품질이 더 좋은 게 낫다는 얘기를 했다.

번역 품질 개선

하지만 원래 목표에서 스타일을 유지하는 게 목표였고 실제로 이 마크다운의 스타일을 유지하니까 보기가 편해서 스타일을 버리고 싶지 않았다. 주변 사람들한테 이 문제점을 얘기하다가 갑자기 좋은 생각이 났다. HTML 태그는 아무래도 장황한데 어차피 Markdown으로 입력한 내용이니까 HTML을 Markdown으로 변환한 뒤 번역을 하고 Markdown을 다시 HTML로 바꾸어서 출력하면 결과가 훨씬 나으면서 스타일도 유지할 수 있지 않을까 한 것이다. 마크다운 기호는 아무래도 HTML 태그보다는 더 글쓰기 기호에 가까우니 영향이 더 적을 거라고 생각했다.

HTML을 마크다운으로 바꿀 때는 to-markdown을 쓰고 마크다운을 다시 HTML로 바꿀 때는 markdown-it을 사용했다. 실제로 해보니 번역결과가 더 좋아진 것을 알 수 있었다.

대신 마크다운 기호를 구글 번역이 임의로 막 바꾸는 경우가 많았다. 이건 일정 패턴이 있는 게 아니라 문장에 따라 임의로 바꾸어서 스타일이 깨지므로 이를 최대한 정리해주어야 했다. 예를 들어 > * Chris> *Chris가 된다거나 [text](url)[text] (url)로 바꾼다거나 하는 등이다. 이를 하나씩 찾아서 수정하다 보니 더는 기억으로 제어할 수 없는 수준이 되어서 결국은 경우별 테스트 케이스를 작성해서 관리할 수밖에 없었다. (구글. 부들부들)

특히 이미지랑 링크는 골치 아팠는데 마크다움에서 링크는 [github](https://github.com/)처럼 만들어지는데 이 URL이 깨지는 경우가 많았고 [github](https://github.com/#anchor)와 같이 앵커가 붙은 경우는 # 뒤의 부분만 링크에서 떨어져나와서 번역문이 이상해지는 경우가 많았다. 이미지는 사용자가 업로드를 하므로 링크와 이미지가 섞여서 [![screentshot](img-url)](url)처럼 되는데 이때는 더 심각하게 꼬여버린다. 그래서 이모지로 플레이스 홀더를 만들어 번역 전에 갈아치운 후 번역이 끝나면 다시 복구하는 노가다 방식을 선택했다.

번역된 화면

결국, 다 좋은 결과가 나오는 건 아니지만, 내용을 파악하고 읽기 쉬운 형태 정도로는 나왔다.

마무리

Primium API 오픈 안 한다고 소스를 다듬다 보니 처음에는 익스텐션 코드밖에 없었는데 결국은 babel, webpack 2, karma 테스트까지 모두 사용하게 됐다. 기능은 거의 다 되었으므로 간격 같은 것도 맞추고 로딩 바 등을 넣어서 사용성을 개선하고 무료 아이콘을 찾아다가 넣었다. 처음 승인 메일이 올 때 1월까지만 스탠다드 요금을 받고 2월부터를 Primium 요금을 받는다고 해서 2월에는 열릴 줄 알았는데 영 안 열려서 더 시간 지나기 전에 저장소를 공개로 바꾸고 오픈을 했다.

누군가 구글에 요청해서 승인을 받는다면 사용할 수 있을 것이다. 참고로 구글 클라우드에 가입하면 60일 동안 $300 크레딧을 주는데 덕분에 무료로 잘 썼다. 맘 놓고 주변에도 뿌리고 실컷 테스트했는데 20달러 정도밖에 안 나가서 댓글 읽을 때 드는 시간을 생각하면 나로서는 충분히 지급할 정도의 금액이다.

2017/02/22 09:00 2017/02/22 09:00