Can we test it? Yes, was can! - Mitchell Hashimoto - https://www.youtube.com/watch?v=MqC3tudPH6w 안녕하세요, 여러분. 와주셔서 감사합니다. 긍정적으로 말해보겠습니다. 낙관적이라고 생각하지만, 바로 본론으로 들어가죠. 이것은 우리 모두가 전에 들어본 적 있는 내용입니다. PR이 열렸을 때, 사소할 수도 있고 아닐 수도 있죠. 작성자가 선제적으로 말했을 수도 있고 아닐 수도 있습니다. 테스트가 없는 이유를 물어볼 수도 있지만, 보통은 이렇게 들립니다. "이건 테스트할 수 없어요"라는 말을 듣게 되죠. 그 뒤에 보통 "하지만 괜찮아요. 수동으로 확인했거든요"라는 말이 따라옵니다. 저도 이런 말을 한 적 있고, 여러분도 그랬을 겁니다. 이 방에 있는 누군가는 지난 24~48시간 동안 이런 말을 했을 거예요. 괜찮습니다. 하지만 사실은 보통 이런 겁니다. "아직" 테스트할 수 없거나... 또는 "아직"을 "쉽게"로 바꿔 말할 수 있죠. "쉽게"는 괜찮습니다. 때로는 뭔가를 테스트할 수 있다고 주장할 수 있지만, 테스트하는 데 필요한 작업이 가치가 없을 수도 있습니다. 그런 경우가 많이 있죠. 치명적인 버그가 아니라면 문제가 되지 않을 수 있고, 재현 가능한 환경에서만 실행한다면 큰 문제가 아닐 수 있습니다. 그건 다 괜찮습니다. 그런 상황은 많이 있어요. 이 강연은 언제 테스트해야 하는지에 대한 것이 전혀 아닙니다. 그런 가치 판단은 여러분이 직접 하시기 바랍니다. 제가 말하고 싶은 건 어떻게 테스트할 수 있는지이번 강연의 목표는 다양한 상황에 적용할 수 있는 개념과 전략들을 소개하는 것입니다. 여러분에게 패턴 매칭 능력과 적용 방법을 알려드리고자 합니다. 이는 매우 유사한 것 같습니다. 우리 모두가 동의할 수 있듯이 가치가 있습니다. 매일 하나의 언어만 사용하더라도 여러 프로그래밍 언어를 배우는 것은 가치가 있습니다. 여러 언어를 배우면 더 나은 프로그래머가 되는 경향이 있습니다. 마찬가지로, 다양한 테스팅 과제를 접하는 것만으로도, 제가 보여드릴 예시를 직접 마주치지 않더라도, 모두가 더 나은 테스터가 될 수 있다고 생각합니다. 그것이 오늘 제 목표입니다. 자, 강연의 목표를 이해하셨죠. 왜 제 말을 들어야 할까요? 제 경험은 무엇일까요? 간단히 설명해 드리겠습니다. 저는 Hashicorp라는 회사를 시작했습니다. 우리는 많은 도구를 만들었고, 저는 모든 초기 엔지니어링 팀의 일원이었습니다. 죄송하다고 할지 아니면 감사하다고 할지 모르겠네요. 좋아하신다면 말씀해 주세요. 싫어하신다면 제가 한 게 아니라 다른 사람이 한 겁니다. 네, 약 12년 동안 그 일을 했습니다. 중요한 점은, 이 소프트웨어에 대한 초기 테스팅 전략을 수립하는 데 많은 부분 참여했다는 것입니다. 대부분 네트워크 시스템이었습니다.일반적으로 노드 수나 그런 종류의 규모, 차원, 축의 관점에서 대규모였습니다. 그리고 보안에 민감했습니다. 우리가 만든 것 중 하나가 볼트였기 때문입니다. 최근에 저는 2023년 말에 Hashicorp를 떠났습니다. 혹시 모르는 분들을 위해 말씀드립니다. 하지만 최근 몇 년 동안 Go See라는 프로젝트에 참여해왔습니다. 이는 터미널 에뮬레이터입니다. 회사가 아니라 그저 취미 프로젝트로, 재미로 하는 것입니다. 하지만 이는 매우 다른 특성을 가지고 있습니다. 이것은 데스크톱 소프트웨어입니다. Mac OS와 Linux를 아우르는 크로스 플랫폼입니다. GPU 렌더링을 포함하고 있습니다. 그리고 이것이 흥미롭다고 생각하는 이유는 이 각각의 경험들이 제게 매우 다른 테스팅 환경을 제공했기 때문입니다. 저는 테스팅을 좋아하는 사람입니다. 그래서 이것을 파악하고 싶었고 지난 15년 동안 제가 알아낸 것을 공유하고 싶습니다. 좋습니다. 그럼 2017년으로 돌아가보겠습니다. 저는 Go for Con에서 "Advanced Testing with Go"라는 제목의 강연을 했습니다. 제목과 행사에서 알 수 있듯이, 이는 상당히 GO에 특화되어 있었습니다. 많은 예시들이 GO와 관련된 것이었죠. 하지만 수년에 걸쳐 많은 사람들이 다가왔습니다. 이는 제가 한 강연 중 가장 많이 본 강연 중 하나입니다. 많은 사람들이 GO를 쓰지 않더라도 이 강연을 보고 가치를 찾았다고 말했습니다. 거의 10년이 지났고, 저는 이 강연의 후속편을 만드는 것이 저는 더 많이 배웠고, 더 복잡한 상황을 경험했습니다 그리고 이 부분에서 짧게 끝났는데 더 말하고 싶었습니다 하지만 중요한 점은 이 강연과 겹치지 않으려고 노력한다는 것입니다 그래서 한 가지 개념만 겹치는데, 이는 앞으로 사용할 것이기 때문입니다 제가 말씀드리는 것들이 조금 고급스러워 보일 수 있습니다 하지만 이 강연은 더 많은 내용을 다룹니다. 제목에 고급이라고 되어 있지만 이 강연은 더 기초적인 개념들을 많이 다룹니다. 여러분이 GO를 그렇게 작성하지 않더라도, 이 강연을 좋아하신다면 저 강연도 보시면 좋아하실 것 같습니다. 자, 이제 본론으로 들어가겠습니다. 우리가 배운 것은 이렇습니다. 학교나 프로그래밍 책의 테스트 챕터에서 배운 것처럼요 어떤 연산이 있고, 출력이 있고 실행하면 그 출력을 예상하거나 단언합니다 일부는 이런 식입니다. 그리고 만약 여러분이 하는 일이 이와 같다면, 좋습니다. 쉽고 우리 모두 즐겁게 테스트를 작성할 수 있죠. 하지만 문제는 제가 제대로 말씀드리고 있는지 확인해 보겠습니다. 현실은 많은 실제 상황에서 여러분은 다음과 같은 경우를 만날 수 있습니다 add가 실제로는 셸에 서브프로세스를 실행하여 BC를 실행하고 어떤 이유로 Windows에서도 작동하길 원했지만 작동하지 않습니다 이건 말도 안 되는 얘기죠. 현실이 아닙니다. 유일한 이것은 어리석은 예시입니다. 현실과는 거리가 멉니다. 유이를 가능하게 할 생태계는 아마도 JavaScript일 것입니다. 하지만 핵심은 실제로는 상황이 복잡하다는 것입니다. 대부분의 테스트는 단순히 무언가를 실행하고, 결과를 얻고, 만족하는 상황으로 끝나지 않습니다. 적어도 제 경험상 그렇습니다. 제가 실제로 신경 쓰는 많은 것들이 그런 범주에 속하지 않습니다. 오히려 다음과 같은 범주에 속합니다. 부작용이 있거나 실행하기 위해 복잡한 세계 상태를 먼저 만들어야 하는 경우입니다. 네트워킹, GPU와 같은 다른 장치들, 동시성 등 목록은 계속됩니다. 이에 대해 자세히 설명하지는 않겠습니다. 다만 여러분도 동의하고 자신의 코드에서 이를 경험했기를 바랍니다. 이런 것들 때문에 이런 범주에서 작업하다 보면 "이건 테스트할 수 없지만, 아마도 작동할 거야"라고 말하게 됩니다. 좋습니다, 마지막으로 얘기하고 싶은 것은, 본격적으로 들어가기 전 마지막 준비 슬라이드입니다. 테스트의 두 가지 측면입니다. 테스트에는 두 부분이 있고, 둘 다 똑같이 중요합니다. 이것이 슬라이드 형식이 될 것입니다. 슬라이드 제목이 시험관으로 시작하면 테스트 전략에 대해 이야기할 것입니다. 만약 비누와 거품으로 시작하면 테스트 가능성에 대해 이야기할 것입니다. 테스트 전략은 설명이 필요 없습니다. 그것은 무언가를 테스트하는 방법입니다. 이것 없이는 테스트가 존재하지 않습니다. 모두가이것은 쉽습니다. 두 번째도 마찬가지로 중요합니다. 하지만 제가 보기에 엔지니어링 경험과 무관하게 더 자주 무시되는 것은 테스트 가능성입니다. 소프트웨어가 테스트 가능하지 않으면, 실제로 이것은 테스트할 수 없다고 말할 수 있는 경우가 있고 그것이 사실일 수 있습니다. 그래서 모든 것을 테스트할 수 있으려면 테스트 가능성과 실제 테스트 전략을 함께 고려해야 합니다. 테스트 가능성을 달성하는 방법은 일반적으로 적절한 코드 구조, 올바른 API 제공, 자동화에 친화적인 설계 등입니다. 그래서 이 두 가지를 혼합해서 이야기하겠습니다. 슬라이드 디자인을 통해 더 많은 것을 테스트할 수 있도록 안내하겠습니다. 이를 통해 더 많은 것을 테스트할 수 있게 될 것입니다. 자, 첫 번째 테스트 전략부터 시작해보겠습니다. 아, 한 가지 중요한 점은 슬라이드가 대략 가장 간단한 것부터 더 복잡하거나 고급스러운 순서로 되어 있다는 것입니다. 이 내용이 너무 뻔하다고 생각하시거나 전체 강연이 뻔할 것 같다고 생각하실 수 있습니다. 그렇지 않길 바랍니다. 앞선 강연을 보면 이 그룹은 상당히 고급 수준이니까요. 하지만 시작해보겠습니다. 첫 번째는 스냅샷 테스팅입니다. 때로는 골든 파일 테스팅, 그라운드 트루스 테스팅 등으로도 불립니다. 저는 예전에 그런 용어들을 사용했지만, 요즘은 스냅샷 테스팅이 더 일반적인 것 같아서 여기서는 그렇게 부르겠습니다.08:일반적으로 복잡한 출력 형식이 있어서 프로그래밍 방식으로 비교 코딩이 어려운 경우입니다. 스냅샷 테스팅에 익숙하다면, 하나의 덜 논의되는 시나리오에 익숙할 것입니다. 하지만 제 생각에 스냅샷 테스팅에서 더 강력할 수 있는 점은 종종 뭔가가 실패한 이유를 이해하는 데 더 나은 차이점을 제공한다는 것입니다. 이에 대한 예시를 보여드리겠습니다. 여기 있습니다. 오, 예상보다 훨씬 작게 나왔네요. 여기 제 터미널 에뮬레이터인 Ghosty에서 직접 가져온 예시가 있습니다. 로컬에서 미리보기했을 때 이미지가 정말 작았고 코드는 정말 컸어요. 그래서 모르겠네요. 하지만 코드는 읽을 필요 없습니다. 코드는 Zig입니다. 저는 이 발표에서 Zig 예제를 사용할 겁니다. 이 발표는 완전히 언어에 구애받지 않습니다. 상관없으니 무시하세요. 하지만 기본적으로 여기서 하는 일은 Ghosty가 터미널 에뮬레이터 내에 내장된 스프라이트 폰트를 가지고 있다는 것입니다. 터미널이 렌더링하는 많은 글리프들이 완벽한 그리드 크기에 의존합니다. 여러분이 가장 익숙한 것은 아마도 PowerShell 유형의 글리프들, 예를 들어 화살표일 것입니다. 폰트를 사용하면, 지금 터미널에서 이것을 보고 있다면, 화살표가 그리드 크기와 완벽하게 일치하지 않는 폰트를 사용하고 있는 겁니다. 그러면 터미널이 이 작업을 하고 있지 않은 겁니다. Ghosty가 하는 일은 이러한 글리프 코드 포인트를 현재 그흔하지 않습니다. 일부 터미널에서 이렇게 하지만 문제는 어떻게 테스트하냐는 것입니다. 어떻게 작동하는지 확인할 수 있을까요? 특히 우리가 처음 발견한 것 중 하나는 그리드 크기가 한 차원에서 홀수일 때 1픽셀 오차가 있었다는 것입니다. 그래서 방법 중 하나는... 또한 새로운 글리프가 계속 추가됩니다. 우리는 래스터화 방식의 라이브러리를 변경했고 그런 것들을 바꿨습니다. 이런 것들이 퇴보하지 않도록 어떻게 확인할 수 있을까요? 이는 스냅샷 테스팅의 완벽한 사례입니다. 우리가 하는 일은 우리가 프로그래밍 방식으로 래스터화하는 모든 글리프의 비트맵을 렌더링하고, 그것을 저장소에 직접 커밋한 다음 비교합니다. 매우 간단합니다. 그것이 스냅샷이고 우리가 비교하는 방법입니다. 코드가 보여주려 했던 것은 아래쪽에 기본 단계가 있다는 것입니다. 우리는 이 기준 데이터를 읽고, 실제 래스터화를 실행한 다음 단순히 비교합니다. 거기에는 특별한 것이 없습니다. 하지만 제가 언급했던 것 중 하나는 기준 데이터 테스팅의 유용한 특성 중 하나가 더 나은 차이점 비교라는 것입니다. 스냅샷 테스팅은 더 나은 차이점을 보여줍니다. 이 경우, 우리의 비교 대상이 이미지이기 때문에 표준 이미지 차이 비교 기술을 적용할 수 있습니다. 우리는 실제로 저장소에서 이렇게 합니다. 테스트가 실패하면 차이점도 생성하여 테스트 실행의 일부로 파일 시스템에 덤프합니다. 다시 말하지만, 이것이 조오른쪽에 실패가 발생했습니다. 오른쪽의 차이점은 보기 좀 어렵지만, 거기에 초록색 줄이 있습니다. 이것이 생성된 차이점입니다. 여기서 강조하고 싶은 점은 이것이 더 유용한 차이점이라는 것입니다. 이전에는 일부 테스트가 있었고 단순히 일치하지 않는다거나 이 위치의 이 픽셀이 잘못되었다고 말했습니다. 스냅샷 테스팅이 제공하는 한 가지는 스냅샷은 보통 단일 assertion보다 더 많은 맥락을 가진다는 것입니다. 예를 들어, 이 한 픽셀이 잘못되었지만, 여기 모든 픽셀이 있어서 볼 수 있습니다. 보통 유닛 테스트 환경의 표준 assertion은 더 큰 맥락을 포함하지 않습니다. 스냅샷은 그렇게 하는 경향이 있어서 이 경우, 수직 막대가 문제라는 것을 매우 명확하게 볼 수 있었습니다. 여러 글리프에 걸쳐 있다는 것을 볼 수 있었고, 코드베이스에 대한 제 경험으로, 이런 것을 볼 때 즉시 수직 막대를 그리는 공통 함수가 잘못되었다고 생각합니다. 그리고 그것이 제가 결국 여기서 망가뜨린 것입니다. 이는 이미지에만 적용되는 것이 아닙니다. 이미지는 쉬운 시각적 예시를 제공합니다. 하지만 예를 들어, Terraform의 경우, 약 10년 전에 우리가 마주친 한 가지는 Terraform이 수행하는 첫 번째 단계가 실행될 리소스 그래프를 구축한다는 것이었습니다. 우리는 그 그래프를 구축하는 것에 대한 많은 테스트를 가지고 있었고 이 예상된 노드, 이 예상된 엣지가 존재하지 않는다는 실패를 받곤 했고 매우이런 주장들로 인해 우리에게 차이점 찾기가 매우 어려웠습니다. 그래서 결국 예상 그래프와 실제 얻은 그래프를 덤프하기 시작했고 누락된 엣지는 빨간색, 추가 엣지는 녹색, 정점 등으로 색상을 표시한 dot 형식을 생성했습니다. 그러면 전체 그래프를 로드할 수 있어 이런 것들을 디버깅하는 것이 훨씬 더 쉬워졌습니다. 이는 실제로 텍스트 비교 형식이었습니다. 이미지로 렌더링할 수 있지만 단순히 텍스트 비교였습니다. 하지만 다시 말하지만, 정점 하나가 누락되었지만 모든 정점을 볼 수 있어 주변의 더 큰 맥락을 파악할 수 있었기 때문에 디버깅이 더 쉬워졌습니다. 이것이 실제로 스냅샷 테스팅의 더 큰 이점이라고 생각합니다만, 명심해야 할 점입니다. 좋습니다, 다음 섹션은 여기에 넣으려고 하지 않았으므로 실제로 건너뛰겠습니다. 그것도 GopherCon 발표와 겹치므로 그쪽을 참고하시면 됩니다. 또한 일종의 주관적인 내용이라 사람들이 화를 내는 경향이 있습니다. 그래서 괜찮습니다. 어쨌든 건너뛰는 게 좋겠습니다. 좋습니다, 테스트 가능성에 대해 이야기해 봅시다. 비누와 거품: 부작용 격리하기. 사실 이 발표에서 다른 부분에 주의를 기울이지 않더라도, 이것이 코드를 테스트 가능하게 만드는 가장 중요한 전략입니다. 이는 다양한 맥락에서 반복해서 나타납니다. 시나리오는 이렇습니다: 테스트하고 싶은 동작이 있지만 어떤 종류의외부 입출력이나 복잡한 시스템으로 가득 찬 부작용 유형의 동작이 있습니다. 이는 매우 흔한 경우로 테스트할 수 없습니다. 여기서 목표는 이 입출력 복잡성과 상태 수프 안에서 순수한 기능적 동작을 찾아내어 추출하고 재정렬하여 대부분 테스트할 수 있는 것을 얻는 것입니다. 이 경우 대부분의 복잡성을 테스트할 수 있습니다. 결과적으로 명백히 테스트 가능한 것을 얻게 됩니다. 예를 들어보겠습니다. 명백히 테스트 가능한 것을 얻지만, 이는 쓰레기 입력, 쓰레기 출력 유형의 테스트가 됩니다. 이는 이 경우 외부 부작용을 시뮬레이션할 수 없기 때문입니다. 그래서 인위적으로 입력을 제공해야 합니다. 쓰레기를 입력하고 테스트하면 쓰레기가 나옵니다. 이런 종류의 테스트에 제공하는 입력이 실제로 현실적인지 확인해야 합니다. 이는 개념적인 것입니다. 실제로 살펴보겠습니다. 자, 여기 예시가 있습니다. 시각적으로 시작하고 코드를 볼 것입니다. 하지만 슬라이드 크기로 인해 코드를 보기는 어려울 것 같습니다. 시각적으로 이해해 봅시다. 여기 Go에서 가져온 단순화된 실제 예시가 있습니다. 터미널 에뮬레이터가 해야 할 주요 작업 중 하나는 아마도 가장 중요한 것인데, 터미널에 무언가가 입력되면키보드에서 무언가를 누르면 그것을 어떤 형식으로 인코딩해야 합니다 그리고 그것이 셸이나 실행 중인 프로그램으로 전송됩니다. 그 다음에 기본적인 작업을 수행합니다. 예를 들어 셸 프롬프트에서 Control R을 누르면 보통 역방향 검색이 나타날 것으로 예상합니다. 등등. Ghosty의 초기 버전에는 위 이미지와 같은 키보드 입력 핸들러가 있었습니다. 기본적으로 여기서 일어나는 일은 사용자가 키를 누르면 마우스 상태를 읽습니다. 이상해 보이지만 마우스 상태에 따라 키를 인코딩할지 여부가 결정됩니다. 예를 들어 실제로 무언가를 하이라이트하고 있는 중에 특정 키를 누르면 하이라이트를 이동하거나 변경하고 싶을 수 있습니다. 그래서 먼저 마우스 상태를 확인하고 응답합니다. 그 다음 키보드 상태를 확인합니다. 어떤 키가 눌렸는지, 어떤 수정자가 눌렸는지 알아야 하기 때문입니다. 반복인지, 첫 번째 누름인지 등 그런 것들을 처리합니다. 그 다음 터미널 설정을 읽습니다. 인코딩 방식에 영향을 미치는 다양한 설정이 있습니다. 레거시 인코딩을 사용하는지, 각각에 대해 kitty 인코딩을 사용하는지? 대체 유니코드 코드 포인트를 인코딩하는지? 컨트롤을 다르게 인코딩하는지? 많은 것들이 있습니다. 마지막으로 키를 인코딩하고 실제 PTY에 작성합니다. 이것은 테스트되지 않았습니다 마우스 키보드 상태 등을 설정하는 것이 간단하지 않기 때문입니다. 처음에는 이것을 테스트할 수 없는언젠가는 VM을 실행하거나 입력을 합성하고 무언가를 주장할 것이라고 생각했습니다. 그냥 미뤄두고 나중에 해결하기로 했죠. 하지만 이 코드로 인해 계속 곤란을 겪으면서 지속적인 회귀가 발생했습니다. 복잡성이 너무 높아서 지금 당장 VM 기반 테스트라도 해야 한다는 걸 깨달았습니다. 그래서 앉아서 이걸 테스트 가능하게 만들어야 한다고 집중했죠. 이대로는 안 된다고 생각했거든요. 그리고 깨달은 것이 이 색깔들이 보여주는 것입니다. 색깔을 볼 수 있다면, 노란색은 외부 로직에 의존하는 부분이고 초록색은 그렇지 않은 순수한 부분입니다. 입력을 받아 출력을 내고 외부 시스템을 건드리지 않는 부분이죠. 색상으로 구분되어 있어 쉽게 볼 수 있습니다. 함수 호출을 별도 카테고리로 단순화하고 번갈아 배치했습니다. 하지만 실제로는 이 모든 것이 뒤섞여 있었고 필요할 때마다 상태를 가져와 조건문을 실행했습니다. 적어도 제게는 이 코드를 몇 시간 동안 뚫어지게 쳐다봐야 무언가가 보이기 시작했습니다. 그리고 아래와 같은 모습이 나타났죠. 외부 상태를 가져오는 모든 부분을 맨 위로 옮기고 읽기-처리-쓰기 순서로 바꾸면 그 부분을 분리해서 인위적인 입력을 제공할 수 있고, 그렇게 하면 중간 부분을 격리시킬 수 있습니다.이 순수한 녹색 요소를 테스트해보세요. 입력만 받고 출력만 제공합니다. 그러면 테스팅 101처럼 예상 결과를 확인하는 1+2=3 같은 환경이 됩니다. 이 경우 대부분의 복잡성, 버그, 문제가 이 녹색 요소에 있었습니다. 그래서 우리는 많은 문제를 극적으로 제거할 수 있었습니다. 또한 퍼징 등을 훨씬 쉽게 할 수 있게 되었죠. 퍼징에 대해서는 여기서 말하지 않겠습니다. 충분히 네, 예상한 대로입니다. 이것이 키 인코더의 실제 코드입니다. 아래쪽을 보여드리고 싶네요. 아래쪽이 더 중요하지만, 위쪽만 보이는데 각 줄은 보이지만 텍스트는 안 보이죠. 각 줄은 구조체, 불리언, 정수, 문자 등 키 인코딩에 필요한 상태 정보입니다. 여기서 보여주려는 것은 터미널이 유효한 키 인코딩을 생성하는 데 실제로 필요한 상태의 양입니다. 총 약 15개의 필드가 있습니다. 일부는 운영체제에서, 일부는 터미널의 내부 설정에서 생성됩니다. 아래쪽은 실제 테스트를 그대로 복사한 것으로, 수정 없이 우리가 이제 테스트할 수 있는 회귀 유형을 보여줍니다. 아래쪽 테스트가 하는 일은 믿으셔야 하고, 나중에 슬라이드를 확인하실 수 있습니다. 특정 입력을 어떻게 인코딩하는지 테스트하고 있습니다.러시아어 키보드 레이아웃과 kitty 키보드 설정을 인코딩하여 대체 유니코드 문자도 추가하고 예측합니다 우리가 올바른 결과를 얻을 것으로 예상합니다. 이것이 제가 이걸 테스트해야 했던 이유입니다 왜냐하면 기능을 수정하거나 버그를 고치거나 기능을 구현할 때마다 저에게는 매우 낯선 레이아웃에서 문제가 발생했기 때문입니다 당연히 저는 러시아어를 타이핑하지 않았죠. 그리고 이 특정 kitty 레이아웃을 얻는 것은 매우 어렵습니다. 이는 매우 흔한 일입니다. 러시아어, 일본어, 중국어, 헝가리어 등 모든 종류의 매우 언어 특정적인 테스트 케이스가 있어서 우리가 항상 올바르게 처리하는지 확인합니다. 그리고 이 때문에 Go See는 가장 완벽하고 안정적인 키 인코딩 기능 중 하나를 가지고 있다고 생각합니다 이는 많은 도움이 되었습니다. 이것이 바로 부작용을 분리하는 핵심 포인트입니다. 기본적으로 여기서부터는 계속해서 이런 예시를 보여줄 것입니다 스냅샷 테스팅을 하고, 부작용을 분리하고 이들을 결합하여 점점 더 복잡한 작업을 수행할 것입니다 . 좋습니다. GPU에 대해 이야기해 보겠습니다. GPU에 대해 정말 흥미로운 시기입니다. 다행히도 저는 OpenAI에서 일하지 않기 때문에 이 발표에서 AI에 대해 전혀 언급하지 않을 것입니다 일반적인 GPU 프로그래밍에 대해서만 이야기하겠습니다. 이는 적용될 수 있습니다AI에도 적용되지만, 제 경우엔 렌더링과 일부 연산에 적용됩니다. GPU 프로그래밍과 GPU 테스팅에 대해 이야기하고 싶습니다. 배경 설명을 하자면 Ghosty는 GPU로 렌더링되는 터미널 에뮬레이터입니다. 터미널을 실행할 때 보이는 주요 화면, 커서가 깜빡이는 그 전체가 단순히 GPU를 통해 렌더링하는 이미지입니다. 우리는 GPU에서 약간의 연산도 수행합니다. 개인적으로는 몇 년 전 처음으로 GPU 코드를 작성해보는 시도였습니다. 그 전까지는 순수하게 CPU에서만 작업했죠. 그래서 시작할 때 제가 가장 먼저 물었던 것은 "이걸 어떻게 테스트하지?"였습니다. 처음 든 생각과 반응은 전형적인 "테스트 불가능"이었죠. VM을 띄우고 스크린샷을 찍는 게 매우 명확한 해결책처럼 보였습니다. 결국 렌더링을 하는 거니까요. 그래서 그게 올바른 해결책처럼 보였습니다. 하지만 수년간 우리 렌더러에는 단위 테스트가 없었습니다. 그리고 수년간 키 인코딩과 마찬가지로 렌더러와 GPU 코드에서 계속해서 회귀 오류를 잡느라 고생했습니다. 그래서 마침내 저는 앉아서 이걸 해결해야겠다고 마음먹었습니다. GPU 로직을 테스트하는 방법에 대한 자료는 놀랍게도 부족합니다. GPU 테스트 방법을 웹 검색해보면, 구글 첫 페이지가 완전히 쓸모없는 경우가 드물게 있는데 이게 그 중 하나입니다. 한 응답에 약간 괜찮은 아이디어가 있긴 한데흥미롭지만, 아무도 구현하지 않은 아이디어에 불과했습니다. 그래서 어떤 방향으로 가야 할지 스스로 알아내야 했죠. 그래서 저는 Kronos Group, Apple, Microsoft로 가서 DirectX, Direct3D, Metal, OpenGL, Vulkan의 레퍼런스 자료와 문서들을 큰 텍스트 형식으로 제공되는 모든 것을 다운로드했습니다. 그리고 Command F 검색과 LLVM 지원 검색을 했습니다. 테스트, 검증, 스냅샷, 버그, 안정성 등 이런 용어들을 모두 찾아봤죠. 놀라운 점은 세 회사의 언어 명세서, 드라이버 사양 등을 포함한 전체 참고 자료가 PDF로 약 4,000페이지에 달하는데도 그 용어들이 한 번도 언급되지 않았다는 겁니다. 단 한 번도 나오지 않았어요. 제가 찾은 바로는, GPU 테스팅 방법에 대한 공식 자료가 전혀 없었고, 이 문제에 관심을 가진 사람도 거의 없었습니다. 적어도 공개적으로 인덱싱된 자료는 없었죠. 그래서 저는 이걸 도전으로 받아들였습니다. 저는 테스팅을 좋아하니까 어떻게든 해결할 수 있을 거라고 생각했죠. 그리고 제게 꽤 잘 맞는 방법을 찾아냈다고 봅니다. 여기서 간단한 면책 조항을 추가하자면, 제 경험에서 알 수 있듯이 저는 아마도 이 분야의 전문가는 아닙니다.GPU에 대해 전문가는 아닙니다. 복잡한 3D 게임을 만들어본 적이 없어요. 제 터미널 에뮬레이터가 어드벤트 오브 코드 예제 같은 장난감 수준을 넘어서 GPU를 사용한 거의 유일한 것입니다. 제 기술이 실제로 잘 일반화되는지는 확실하지 않습니다. 제 변명을 하자면, Ghostly에는 약 15,000줄의 렌더러 코드가 있습니다 Metal과 OpenGL로 나뉘어 있죠. 두 시스템에 대해 별도의 렌더러가 있고, 여기에는 CPU 코드와 GPU용 셰이더가 모두 포함됩니다 셰이더는 약 2,000줄입니다. 그래서 CPU에 13,000줄, GPU에 2,000줄이 있습니다. 이것이 제가 다루던 내용입니다. 이것이 제가 테스트하려던 것입니다. 제가 깨달은 핵심은 GPU 프로그래밍에는 두 가지 측면이 필요하다는 것입니다. 배경 설명을 하겠습니다. 데이터를 준비하고 결과를 처리하는 CPU 측면과 실제로 셰이더를 실행하는 GPU 부분이 있습니다. 우리는 이 둘을 분리해서 테스트할 수 있다고 느꼈고, 특히 GPU는 순수한 함수 평가기라는 점이 명확해서 테스트가 매우 쉽지만, 워크로드를 설정하는 것은 매우 어렵다는 점이 흥미로운 점입니다. 하지만 이제 이것을 시각적으로 더 자세히 살펴보고 GPU 측면부터 시작해보겠습니다. CPU 측면. 우리는 이 기본적인 내용을 실제로 읽을 수 있습니다. GPU 프로그래밍에 익숙하지 않은 분들을 위해 배경 설명을 하자면, CPU도 GPU를 준비하는 데 일부 작업을 수행해야 합니다. CPU는 올바른 데이터와 기본적으로 이런 작은 작업 설명들을 결국에는 GPU에 오프로드하고 제출하여 "여기 처리할 것들이 있으니 실행해"라고 말합니다. "나중에 돌아오거나, 완료되면 알려주세요. 나중에 결과를 확인하겠습니다." 이것이 GPU를 생각하는 매우 일반적인 방식입니다. GPU를 위해 CPU가 수행하는 준비 작업은 대략 이 최상위 함수로 생각할 수 있습니다. 이 함수는 세계의 상태를 입력으로 받습니다. 렌더링을 하는 경우, 이를 장면 상태라고 부를 수 있습니다. 레벨에 어떤 몬스터가 있는지, 그들의 위치, 카메라의 위치, 플레이어의 위치 등입니다. 내가 어느 행성에 있는지. 이것이 존재하는 장면 상태입니다. 장면 상태를 가져와서 결과적으로 세 가지 값 집합을 생성합니다. 그래프(정점과 간선)를 생성하고, 그 그래프에 대한 데이터 첨부를 생성합니다. 어떤 노드가 어떤 데이터에 접근해야 하는지 말이죠. 이것이 GPU입니다. 그래프는 단순히 그래프입니다. 정점과 간선이 있고, 정점은 연산입니다. 정점 셰이딩, 프래그먼트 셰이딩, 컴퓨트 셰이딩 등과 같은 것들이죠. 간선은 단계 간의 데이터 의존성입니다. 정점 셰이더는 프래그먼트 셰이더가 필요로 하는 출력을 생성하거나, 정점 셰이더가 필요로 하는 것, 또는 프래그먼트 셰이더가 특정 텍스처에 접근해야 하는 것 등입니다. 이런 간선들이 그래프에 존재하고, 데이터 첨부는 말 그대로 바이트 버퍼입니다. 역사Vulcan, Metal, 그리고 나중의 Direct3D와 같은 더 현대적인 그래픽 API들은 이제 이것들을 단순히 버퍼라고 부르는 경향이 있습니다. 실제로 이는 바이트의 집합일 뿐이기 때문입니다. 때로는 이 바이트들이 구조를 가집니다. 차원과 보폭을 가진 RGBA 형식으로 텍스처가 됩니다. 하지만 때로는 단순 계산을 위한 바이트일 뿐입니다. 따라서 데이터는 단순히 바이트이며, 원하는 대로 활용할 수 있습니다. CPU 측을 테스트하기 위해서는 부작용을 분리하는 기술을 적용해야 합니다. 이 경우 부작용은 워크로드를 준비하고 제출하기 위해 GPU 자체에 대한 모든 API 호출입니다. 그래서 제가 결국 한 일은 중간 단계를 만드는 것이었습니다. 장면 상태를 가져와 그래프와 데이터를 생성하고, 그 다음 그래프가 올바른 형태를 가지고 있는지 확인합니다. 저는 테라폼에 대한 많은 경험이 있습니다. 데이터에 대해서는 스냅샷 테스팅을 다시 가져왔습니다. 단순히 바이트이기 때문입니다. 따라서 우리는 구조화된 스냅샷 검증을 할 수 있습니다. 그 후에는 간단한 테스트되지 않은 코드가 있는데, 이는 그래프와 데이터 첨부를 GPU API 호출로 변환합니다. 이 부분은 거의 변경되지 않습니다. 엔드투엔드 테스트가 있을 때까지 이를 테스트하지 않아도 괜찮습니다. 이렇게 해서 우리는 특정 장면 상태가 주어졌을 때 GPU에 대해 올바른 워크로드를 생성하고 있는지 테스트할 수 있습니다. GPU가 이를 가지고 올바른 작업을 수행할지는이것은 go see가 장면을 렌더링하기 위해 사용하는 15,000줄의 코드 중 13줄에 불과합니다. 이는 매우 큰 작업입니다. GPU 측면에서는 시각적으로 훨씬 단순합니다. GPU는 디스크에 접근할 수 없고, 네트워킹에 접근할 수 없으며, 다른 주변 장치에 접근할 수 없습니다. 오직 자체 메모리에만 접근할 수 있죠. 따라서 GPU는 정의상 거의 순수한 함수 평가기입니다. 연산과 데이터가 있고 데이터를 출력합니다. 이는 테스트하기에 매우 좋고 쉬운 것입니다. 하지만 재미있는 점은 제가 말씀드렸듯이, 어려운 부분은 실제로 작업량을 제출하는 것입니다. 그래서 GPU 측면의 일반적인 아이디어는 파이프라인이 기대하는 입력 버퍼 세트를 인위적으로 구성하는 것입니다. 출력 버퍼가 CPU에서 읽을 수 있도록 할 것입니다. CPU로 돌아오지 않을 수 있는 프레임 버퍼에 쓰는 대신, 이 화면을 렌더링할 것입니다. 화면에 렌더링하지 말고, 제가 가진 CPU에서 읽을 수 있는 다른 메모리에 렌더링하세요. 그런 다음 실제 GPU 작업을 제출합니다. 우리는 CPU에서 유닛 테스트를 실행합니다. CPU에서는 GPU를 위한 유닛 테스트를 실행하고, GPU에서는 실제 GPU 작업을 제출한 다음 출력 버퍼를 비교합니다. 대략적으로 말하자면, 스냅샷 테스팅, 실제 데이터 파싱, 원하는 방식으로 하지만 어떤 식으로든 출력을 비교합니다. 이것이 일반적인 아이디어입니다. 어렵죠, 그렇죠? 그래서 실제로 제가 발견한 것은29:56.600 출시한 것은 스냅샷 테스팅을 통한 전체 렌더 패스입니다. 제가 하는 일은 인위적으로 장면 상태를 만드는 것인데, 보통 아주 작은 터미널, 2x2 터미널 같은 것입니다. 이를 실제 GPU로 보내고 이미지를 받아 비교합니다. 픽셀 단위로 동일한 이미지를 기대합니다. 이는 일종의 엔드투엔드 테스팅처럼 들립니다. 어떤 면에서는 그렇습니다. 정확히 단위 테스트는 아니고 통합 테스트에 가깝지만, 매우 강력하고 견고한 테스트라고 생각합니다. 두 가지 이유가 있습니다. 첫째, 훨씬 빠릅니다. 창을 만들고, GPU 작업을 제출하고, 스크린샷을 찍어 비교하는 것이 현대 컴퓨터에서는 순식간에 이루어집니다. 정말 빠르고 매우 견고합니다. 이 경우 입력 장면 상태를 엄격히 제어합니다. 한 번의 렌더 패스를 실행하고 정확히 한 프레임의 결과를 얻어 비교합니다. 일반적인 엔드투엔드 테스트에서 걱정해야 할 타이밍, 합성 입력, 윈도우 위치, 크로밍 등 주변의 모든 것들을 신경 쓸 필요가 없습니다. 비교하려는 내용에 대해 완벽하게 잘린 이미지를 정확히 얻고 단일 프레임이므로 매우 견고합니다. 잘 작동합니다. 향후에는 셰이더를 더 고립된 상태에서 테스트하고 싶습니다. 이미 많은 작업을 했고, 개념 증명으로 수행한 몇 가지를 설명해 드리겠습니다. 잘 작동합니다.하지만 모두 만족스럽지 않은 트레이드오프가 있습니다. 그래서 실제로 이것을 배포하지는 않았습니다. 이것이 제 면책 조항입니다. 개념 증명은 모두 작동합니다. 전체 렌더 패스는 당연히 전체 입력을 이미지로 테스트하지만, 개별 셰이더 자체는 꽤 복잡할 수 있거나 매우 복잡할 수 있습니다. 조건문, 루프, 명백한 엣지 케이스들이 있어서 어떤 방식으로든 테스트하고 싶습니다. 그리고 때로는 초기 장면 상태를 통해 이러한 엣지 케이스를 유도하거나, 적어도 결과 출력 상태에서 시각화하기 어렵습니다. 그래서 저는 셰이더로 단위 테스트에 더 가까워지고 싶습니다. 그래서 저는 최선의 방법을 찾기 위해 다양한 기술을 시도해 왔습니다. 다시 말하지만, 이것들은 많은 사람들이 시도하지 않은 것들입니다. 각각에 대해 자세히 설명하지는 않겠습니다. 아직 배우고 있기 때문입니다. 하지만 높은 수준의 개념만 다루겠습니다. 이에 대해 더 자세히 아시는 분들이 있다면 듣고 싶습니다. 예를 들어, 몇 가지 개념만 말씀드리겠습니다. OpenGL. 많은 사람들이 Vulkan 등에 대해 열광하지만, OpenGL도 여전히 작동합니다. OpenGL에는 트랜스폼 피드백이라는 기능이 있는데, 이것은 기본적으로 일부 셰이더의 출력을 CPU가 읽을 수 있는 버퍼로 캡처할 수 있게 해줍니다. 모든 셰이더는 아니지만 일부 유형의 셰이더에 대해서요. 실제로 매우 좋은 API입니다. 문자열을 추가하고 원하는32:48.220출력 버퍼로 만들어집니다. 그리고 그게 완벽해요. 저는 이게 무엇을 위해 만들어졌는지 모르겠어요. 본 적이 없거든요. 제가 소스 그래프 검색 등을 해봤는데, 거의 사용되지 않는 것 같아요. 매우 적은 API 호출만 있고, Vulkan에는 실제로 존재하지 않아요. 명확히 충분한 가치가 없어서 발전시키지 않은 것 같아요. 하지만 일종의 셰이더 테스트에는 완벽해요. 그게 문제죠. 이건 Metal 쪽에서만 적용돼요. Metal은 변환 셰이더가 없고, Direct3D도 마찬가지예요. 그래서 제가 발견한 해결책은 공유 로직을 컴퓨트 셰이더로 추출하는 거예요. 렌더링이 아닌 순수 컴퓨트 셰이더로, 각 측면에서 이 공유 라이브러리를 호출하고 컴퓨트 셰이더로 실행해 제 나름의 변환 피드백 메커니즘을 만드는 거죠. 이는 더 많은 코드가 필요해요. GPU 코드의 구조 변경이 필요한데, 테스트에는 표준이지만 그리 좋지는 않아요. 그래도 작동해요. 좋은 점은 모든 종류의 셰이더에 적용된다는 거예요. 하지만 컴퓨트 셰이더로 추출할 수 있어야 하는데, 불가능한 경우를 본 적이 없어요. 많은 가능성이 있죠. 안타깝게도 미래 측면의 제품 상태까지는 도달하지 못했어요. 언젠가 블로그나 강연에서 모든 것을 정리해서 이야기할 수 있길 바라요. 하지만 이 결과로 우리가 렌더러를 테스트할 수 있다고 확신해요. 제가 가진 전체 렌더 패스 방식은 매우 견고하고 빠릅니다. 실제로 GPU 같은 드라이버가 작동한다고 가정하고 드라이버를 검증하려는 게 아니라면, 소프트웨어 드라이버로만 실행하면 됩니다. 그리고 한 프레임만 실행하고 있습니다. 그래서 매우 빠릅니다. 그리고 흥미롭고 스냅샷과 부작용 격리를 잘 보여준다고 생각합니다. 좋습니다, 마지막으로 이야기하고 싶은 주제는 VM 테스팅입니다. 특히 이전에 언급한 노란색 박스를 테스트하려면 이것이 필요합니다. 실제로 테스트하는 유일한 방법은 실제로 그것을 발생시키는 것입니다. 키보드와 마우스 및 다른 유형의 이벤트를 시뮬레이션하는 유일한 방법은 VM을 통해서입니다. 키보드나 마우스뿐만 아니라 네트워크 장애 같은 것도 안티테시스가 정말 잘합니다. 디스크 장애, 이런 것들도요. 하이퍼바이저 계층을 통해 가장 잘 수행됩니다. 여기서 사과드릴 게 있는데 닉스를 언급할 거라서요. 닉스에 대한 열정이 많은 사람들을 지치게 한다는 걸 알고 있고 그것에 대해 듣고 싶어 하지 않는다는 것도 압니다. 그래서 죄송합니다. 제 변명을 하자면, 저는 개발 테스트 환경, 가상화, 컨테이너화에 대해 잘 알고 있다고 자신합니다. 15년 동안 제 경력의 전부였거든요. 그리고 제가 곧 보여드릴 것을 잘 구현할 수 있는 다른 기술은 모르겠습니다. 그래서 닉스를 사용할 것이고할 겁니다. 미안하지만 동시에 미안하지 않습니다. 좋습니다. 먼저 Nix 없이 VM 테스팅에 대해 이야기해 봅시다. 감정적인 반응이 있다면, 여기서부터 시작하겠습니다. 좋습니다. 말씀드렸듯이 엔드 투 엔드 테스트가 필요한 것들이 있습니다. 그리고 VM이 그것에 가장 적합합니다. 컨테이너로도 가능할 수 있지만, 원하신다면 서로 바꿔 사용하셔도 됩니다. 하지만 이 경우에는 계속 VM이라는 단어를 사용하겠습니다. VM 테스팅은 정말 복잡하고 거의 임의로 복잡한 세계의 상태를 모델링할 수 있게 해줍니다. 특정 커널 소프트웨어 버전, 특히 그들 간의 상호작용을 포함해서요. 데스크톱 측면에서는 다양한 로케일, 다양한 키보드 레이아웃 등을 시뮬레이션할 수 있게 해줍니다. 이런 것들이 때때로 필요합니다. 그래서 전체 VM을 실행하고, 실제로 소프트웨어를 실행하고, 이벤트를 합성한 다음 원하는 일이 일어났는지 어떻게든 확인하는 것이 아이디어입니다. 보통 스크린샷이나 SSH 명령을 통해 이루어집니다. SSH 명령은 이 프로세스가 실행 중이다, 이 파일이 존재한다, 이 파일에 이런 내용이 있다 등을 확인합니다. 주로 이 두 가지 방식으로 수행됩니다. 좋습니다. 이제 Nix를 도입해 봅시다. 왜 Nix를 도입해야 할까요? 실제로 이것을 읽을 수 있어 다행입니다. Nix를 도입하는 이유는 Nix가 Nix에 접근할 수 있는 완전한 자체 테스팅 프레임워크를 제실제로 Nix 자체를 테스트하기 위해 일차적으로 사용되기 때문입니다. 그래서 없어지지 않을 겁니다. 매일 실행되고 있죠. 지금도 실행 중입니다. 이런 종류의 테스트를 실행하기 위해 Nix 프로젝트에 의해 지금 수천 개의 작업이 대기 중입니다. 둘째, 이것은 실제 테스트 프레임워크입니다. VM을 시작하는 방법만 정의하는 게 아닙니다. 테스트를 작성하고 통과를 확인하는 전체 API가 있습니다. 이것이 중요한 이유는 단순히 Docker 이미지를 실행하는 것과 같지 않기 때문입니다. Docker는 컨테이너의 런타임을 제공하지만, 실제로 테스트할 수 있는 도구는 제공하지 않습니다. 이것은 양쪽 모두를 제공합니다. 그리고 셋째, 죄송합니다, 하나 돌아가겠습니다. Nix로 구동되기 때문에 이는 이점입니다. 완전한 접근이 가능하니까요. 솔직히 말해서 언어는 아마 단점일 겁니다. 하지만 Nix 패키지에 대한 접근은 이점입니다. 기본적으로 지난 수십 년간 존재했던 모든 준인기 소프트웨어의 모든 버전에 접근할 수 있기 때문입니다. 이는 매우 중요합니다. 커널, libc 등 모든 것의 특정 버전을 고정할 수 있기 때문입니다. 이것이 제가 찾은 유일한 방법입니다. 가장 성가신 데스크톱 사용자인 Debian 사용자들이 오래된 장기 지원 소프트웨어 버전을 가져와서 이게 작동하지 않는다고 말할 때, 이것이 실제로 작동한다는 것을 확인할 수 있는 유일한 방법이었습니다. 그럼 이것이 어떻게 생겼는지 살펴보겠습저는 선택적 복수형을 사용했습니다. Nix를 사용하면 여러 머신을 정의하고 그들 간의 네트워킹을 할 수 있기 때문입니다. 하지만 그건 다루지 않을 겁니다. 그것만으로도 전체 강연 주제가 될 수 있죠. 아마 머신 구성에 대한 전체 학위 과정이 될 수도 있을 겁니다. 이건 그저... 여기서 인용 부호가 많은 의미를 담고 있습니다. 이것은 단지 Nix OS 구성일 뿐입니다. 전체 NIX 설치를 구성할 때 넣을 수 있는 모든 것을 여기에 넣을 수 있습니다. 즉, 말씀드렸듯이 모든 것을 의미합니다. 커널, 드라이버, 사용자, 패키지, 모든 것이죠. 두 번째 단계는 실제로 테스트를 정의하는 것입니다. 테스트는 Python으로 작성되며, nix로 작성되지 않습니다. 여기에 문자열을 넣거나 테스트가 포함된 파일을 포함시킵니다. Nix는 완전한 Python API를 제공하여 systemd 유닛 대기와 같은 좋은 고수준 기능을 제공합니다. Nix OS가 systemd를 사용하기 때문이죠. 심지어 OCR도 할 수 있습니다. 화면에 특정 텍스트가 나타날 때까지 기다릴 수 있고 이를 자동으로 처리합니다. 그리고 이건 그저 Python일 뿐입니다. 이를 통해 얻을 수 있는 것 중 하나는 REPL에 접근할 수 있다는 것입니다. VM을 실행하고 REPL을 사용하여 테스트를 실험해볼 수 있습니다. 그래서 좋죠. 이 경우 우리는 ALICE 사용자에 대한 테스트를 정의하고 있습니다. 앞서 언급하지 않았지만, 이전에 Alice를 위해 Firefox를 설치했습니다. ALICE 사용자는 Firefox를 가지고 있고 Nix에 대해 이야기할 때마다 해야 하듯이 말이죠. 하지만 기본적으로 중요한 점은 프레임워크 런타임에 내장된 메커니즘이 있다는 것입니다 테스트를 실행하고, REPL을 얻고, 테스트를 디버깅하고, 개발하고, 단일 테스트를 실행하는 등의 모든 것이 일종의 단일 명령으로 가능합니다. 그래서 이것은 VM 테스트를 처리하기 위한 완전한 엔드투엔드 스레드입니다. 그리고 제가 말씀드렸듯이, 이 정도 수준의 유연성을 제공하는 다른 것을 찾지 못했습니다 테스팅에 초점을 맞춘 것 말이죠. 맞아요. 수많은 프레임워크가 있고, 제가 일부를 만들기도 했습니다 머신을 스핀업하고 VM을 스핀업하는 것들이요. 하지만 실제로 엔드투엔드 부분을 완성하는 것은 아니었죠. 제가 실제로 NixOS VM을 어디에 사용하고 있나요? 다시 말하지만, 완전한 샌드박스 환경 없이는 테스트할 수 없는 것들입니다. 제가 이것을 하게 된 실제 계기는 무엇이었을까요? 그것은 바로 입력 방식이었습니다. 저는 꽤 차분한 사람이지만, 리눅스의 입력 방식 때문에 몇 번 테이블을 내리쳤습니다. 모르시는 분들을 위해 설명하자면, 입력 방식은 기본적으로 특정 아시아 언어를 입력하는 방법입니다. 이모지 키보드도 일종의 입력 방식이죠. 물리적 키보드에 없는 문자를 입력하는 능력이 바로 입력 방식입니다. 필기 입력도 입력 방식의 일종이죠. 리눅스에서는 입력 방식, 입력 방식 프레임워크, 윈도우 시스템/컴포지터가 모두 다른 사람들에 의해 개발됩니다.41:26.이는 리눅스의 어려움이 정말 두드러지는 부분이라고 생각합니다. 매우 특정한 버전의 다양한 것들이 완전히 다르게 작동하기 때문입니다. 이는 저를 미치게 만들었죠. 그래서 입력 방식을 테스트하기 위해 VM 테스팅이 필요했습니다. 저는 리눅스를 좋아하고 NEXT OS를 사용하지만, 애플과는 대조적입니다. 애플에서는 모든 것들이 함께 동기화되어 작동해야 한다는 명확한 수직적 지시가 있습니다. 앱 개발자로서는 훨씬 쉽지만, 아시다시피, 제가 다뤄야 할 것들이죠. 그리고 데스크톱 통합 같은 다른 것들도 있습니다. Open과 Ghostly가 우클릭 시 나타나는지 확인하는 것 등이요. 실제로 스크린샷을 찍고 우클릭하고 다시 스크린샷을 찍지 않고 어떻게 테스트할 수 있겠습니까? 이것들도 리눅스에서 다양한 버전 업그레이드로 계속 문제가 생깁니다. 그래서 VM에 완벽합니다. 여기서 리눅스에 대해 많이 불평했지만, 제 의견으로는 데스크톱 리눅스에서 안정적인 데스크톱 앱 경험을 만들기 위해 해야 하는 작업들입니다. 나중을 위해 여기 두겠습니다. 사진을 찍거나 나중에 슬라이드를 다운로드하세요. VM 테스팅에 대해 더 많이 배울 수 있는 자료들입니다. 매우 강력하지만, Nix의 모든 것처럼 학습 곡선이 수직 절벽 같습니다. 그래서 '왕좌의 게임'에 나오는 벽을 오르고 싶다면, 이 자료들이 필요할 겁니다. 제 생각에 이를 수행함으로써 얻는 이점은 큽니다. 적절한 필요한 테스트를 위해서는 이에 비할 만한 것이 없습니다. 그래서 이게 전부입니다. 감사합니다. 우리가 5가지 주제만 다뤘다는 걸 알지만, 내용이 많았다고 생각합니다. 가장 중요한 것은, 다시 말하지만, 다른 것은 몰라도 부작용을 분리하는 것이 매우 강력한 기술이라는 점이며, 저는 여러 예시를 보여드리고 싶었습니다. 그래서 그렇게 하려고 노력했습니다. 더 많은 내용을 원하시면 2017년 Go 컨퍼런스 자료를 참조하세요. 감사합니다.