<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>테스트 on 얼쑤 · Team Edo</title><link>https://team-edo.github.io/tags/%ED%85%8C%EC%8A%A4%ED%8A%B8/</link><description>Recent content in 테스트 on 얼쑤 · Team Edo</description><generator>Hugo</generator><language>ko-kr</language><lastBuildDate>Mon, 02 Nov 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://team-edo.github.io/tags/%ED%85%8C%EC%8A%A4%ED%8A%B8/index.xml" rel="self" type="application/rss+xml"/><item><title>고치기 전에 실패해야 증거가 되는 테스트</title><link>https://team-edo.github.io/posts/2026/11/test-fails-first/</link><pubDate>Mon, 02 Nov 2026 00:00:00 +0000</pubDate><guid>https://team-edo.github.io/posts/2026/11/test-fails-first/</guid><description>&lt;p>버그를 고치고 테스트를 추가했더니 통과했습니다. 그럼 버그가 고쳐졌다고 말할 수 있을까요?&lt;/p>
&lt;p>얼쑤의 연주 비교 기능을 고치면서, 답은 &lt;strong>고치기 전에 그 테스트가 실패했는지 봐야 안다&lt;/strong>는 걸 배웠습니다.&lt;/p>
&lt;h2 id="다시-연주한-구간을-비교하는-기능">다시 연주한 구간을 비교하는 기능&lt;/h2>
&lt;p>&lt;strong>얼쑤&lt;/strong>는 국악기 연주를 녹음하면 음표마다 맞게 연주했는지 알려 주는 앱입니다. 틀린 구간만 골라 다시 연주하면, 앱이 &lt;strong>이전 연주와 이번 연주를 비교&lt;/strong>해 어떤 음을 고쳤고 어떤 음이 아직 틀렸는지 보여 줍니다.&lt;/p></description></item><item><title>Jackson 3에서 JSON 배열과 누락 필드 읽기</title><link>https://team-edo.github.io/posts/2026/11/read-stored-json-trees-with-jackson/</link><pubDate>Mon, 02 Nov 2026 00:00:00 +0900</pubDate><guid>https://team-edo.github.io/posts/2026/11/read-stored-json-trees-with-jackson/</guid><description>&lt;p>학습 단계의 설명과 채점에 쓸 악보는 DB에 JSON으로 저장합니다. 학습 단계에서 잘못된 항목을 건너뛰면 화면에 설명이 빠진 채 나타납니다. 악보에서 정수를 잘못 읽으면 음표의 길이나 시작 박을 다르게 해석할 수 있습니다. 두 파서에서 저장값을 읽는 지점에 입력 계약을 드러냈습니다.&lt;/p>
&lt;h2 id="배열-노드에서-원소를-꺼낸-뒤-변환했다">배열 원소를 꺼내 변환&lt;/h2>
&lt;p>학습 단계에는 할 일과 운지 설명처럼 배열로 저장하는 항목이 있습니다. Jackson 3의 &lt;code>JsonNode.map&lt;/code>은 노드 자체에 함수를 적용합니다. 배열의 각 원소를 변환하려면 노드가 배열인지 확인하고 &lt;code>values()&lt;/code>로 원소를 꺼낸 뒤 Kotlin의 &lt;code>map&lt;/code>을 적용합니다.&lt;/p></description></item><item><title>Android SDK 없이 녹음 업로드 검증</title><link>https://team-edo.github.io/posts/2026/10/verify-kmp-without-android-sdk/</link><pubDate>Thu, 29 Oct 2026 00:00:00 +0900</pubDate><guid>https://team-edo.github.io/posts/2026/10/verify-kmp-without-android-sdk/</guid><description>&lt;p>얼쑤의 녹음 업로드에는 두 종류의 코드가 있습니다. 연결이 끊긴 녹음을 다시 보낼지, 언제 보관 기록을 지울지는 공통 로직입니다. 녹음을 앱의 보관 디렉터리로 실제 이동하는 일은 플랫폼마다 다릅니다. 개발 환경에 Android SDK가 없었을 때도 업로드 판단을 검증할 수 있도록 두 부분을 분리했습니다.&lt;/p>
&lt;h2 id="업로드-판단은-브라우저의-공통-테스트에서-확인했다">Wasm에서 공통 업로드 로직 검사&lt;/h2>
&lt;p>공통 코드는 파일의 보관·존재 확인·삭제를 &lt;code>RecordingFiles&lt;/code> 인터페이스로 호출합니다. 전송도 인터페이스로 받습니다. Wasm에서 실행한 공통 테스트는 두 인터페이스의 가짜를 넣어, 연결 실패 뒤 기록이 남는지와 같은 세션의 재전송 결과를 확인합니다. 플랫폼의 파일 구현을 불러오지 않아도 테스트가 실행되는 이유입니다.&lt;/p></description></item><item><title>단독 MockMvc에서 JWT가 비어 500이 난 이유</title><link>https://team-edo.github.io/posts/2026/10/mockmvc-principal-test-boundaries/</link><pubDate>Thu, 29 Oct 2026 00:00:00 +0900</pubDate><guid>https://team-edo.github.io/posts/2026/10/mockmvc-principal-test-boundaries/</guid><description>&lt;p>성장 화면 API의 단독 MockMvc 테스트를 작성하고 실행하니 정상 요청과 400을 기대한 요청이 모두 500으로 실패했습니다. 로그를 따라가 보니 컨트롤러의 &lt;code>@AuthenticationPrincipal jwt: Jwt&lt;/code>에 값이 들어오지 않았습니다. Kotlin은 non-null 매개변수에 &lt;code>null&lt;/code>이 넘어오는 순간 예외를 던졌고, 요청은 정작 검사하려던 코드까지 가지 못했습니다.&lt;/p>
&lt;h2 id="단독-테스트가-빠뜨린-연결을-채웠다">인자 해석기와 예외 처리기 등록&lt;/h2>
&lt;p>&lt;code>standaloneSetup&lt;/code>은 제가 넘긴 컨트롤러를 중심으로 작은 MVC 환경을 만듭니다. 실제 앱에서 쿠키를 읽고 JWT를 검증하는 보안 필터까지 자동으로 붙지는 않습니다. 요청에 인증 후처리기만 붙인 첫 테스트에서는 컨트롤러가 읽을 인증 정보가 비어 있었습니다.&lt;/p></description></item><item><title>상태 조회가 대기 녹음을 지울 수 있던 이유</title><link>https://team-edo.github.io/posts/2026/10/keep-pending-recordings-until-upload/</link><pubDate>Thu, 29 Oct 2026 00:00:00 +0900</pubDate><guid>https://team-edo.github.io/posts/2026/10/keep-pending-recordings-until-upload/</guid><description>&lt;p>연주를 끝낸 직후 연결이 끊기면, 아직 서버에 도착하지 않은 녹음을 다시 보낼 수 있어야 합니다. 얼쑤에서는 녹음 파일을 기기의 보관 위치로 옮긴 다음, 어느 세션에 어떤 파일을 보낼지 기록하고 업로드하도록 만들었습니다. 앱을 다시 열어도 그 기록에서 보낼 녹음을 찾을 수 있습니다.&lt;/p>
&lt;p>리뷰에서 파일을 옮기는 일과 대기 기록을 저장하는 일 사이에 틈이 있다는 걸 발견했습니다. 새 파일은 이미 보관 위치에 있지만, 상태 조회에서 읽을 대기 기록은 아직 없습니다. 기존 조회 함수는 기록이 없으면 보관 폴더에 남은 파일을 정리했습니다. 이 순간에 조회가 끼어들면 방금 옮긴 녹음도 버려도 되는 파일로 보고 지울 수 있었습니다.&lt;/p></description></item><item><title>업무 결과를 HTTP 응답으로 바꾸기</title><link>https://team-edo.github.io/posts/2026/10/map-use-case-results-to-http/</link><pubDate>Thu, 29 Oct 2026 00:00:00 +0900</pubDate><guid>https://team-edo.github.io/posts/2026/10/map-use-case-results-to-http/</guid><description>&lt;p>연주 평가를 조회하는 요청은 평가가 있는지, 다른 회원의 세션인지, 아직 분석 중인지에 따라 답이 달라집니다. 분석을 다시 요청할 때도 아직 작업을 시작할 수 없는 경우가 여러 가지입니다. 두 기능에서 예상되는 업무 결과를 이름 있는 값으로 표현하고, HTTP 상태와 응답 본문은 컨트롤러에서 정했습니다.&lt;/p>
&lt;h2 id="평가-조회는-누가-무엇을-판단하는가">평가 조회의 판단 경계&lt;/h2>
&lt;p>컨트롤러는 인증을 마친 토큰에서 회원 ID를 읽어 세션 ID와 함께 넘깁니다. 유스케이스는 조회 계약을 통해 소유한 세션을 찾고, 완료된 세션에서만 평가와 음표 결과를 읽습니다. 세션과 음표 결과는 같은 읽기 전용 트랜잭션에서 조회합니다. 그 계약을 구현한 MyBatis 어댑터는 조회 행을 업무 코드가 읽을 값으로 바꿉니다.&lt;/p></description></item><item><title>3바이트 업로드가 175바이트가 된 이유</title><link>https://team-edo.github.io/posts/2026/10/signed-chunk-upload-fixture/</link><pubDate>Wed, 28 Oct 2026 00:00:00 +0900</pubDate><guid>https://team-edo.github.io/posts/2026/10/signed-chunk-upload-fixture/</guid><description>&lt;p>녹음 저장소를 붙이면서 작은 파일 하나를 업로드하는 검사를 만들었습니다. SDK를 대역으로 바꾸면 서비스가 SDK를 호출했는지만 볼 수 있습니다. 실제 SDK가 메서드, 경로, 인증 헤더와 본문을 어떤 모양으로 보내는지 보려고 로컬 HTTP 서버를 저장소 주소로 넣었습니다.&lt;/p>
&lt;p>원본은 &lt;strong>3바이트&lt;/strong>였습니다. 대역 서버가 받은 본문은 &lt;strong>175바이트&lt;/strong>였고, 본문을 그대로 원본과 비교하던 검사가 실패했습니다.&lt;/p>
&lt;h2 id="요청-본문에-파일과-서명이-함께-있었다">전송 본문에 붙은 청크 서명&lt;/h2>
&lt;p>대역이 받은 본문에는 청크 길이와 청크별 서명이 들어 있었습니다. 이 요청의 SDK는 파일 데이터를 &lt;code>aws-chunked&lt;/code> 형식으로 감쌌고, 데이터가 끝나는 지점도 크기 0인 청크로 표시했습니다. 그래서 전송 본문의 길이에는 원본 외에 프레이밍과 서명 정보까지 포함됐습니다. AWS 문서에서 &lt;code>x-amz-decoded-content-length&lt;/code>는 이 메타데이터를 제외한 원본 길이입니다. &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/developerguide/sigv4-streaming.html">AWS SigV4 청크 업로드 문서&lt;/a>&lt;/p></description></item><item><title>같은 조건의 연주만 비교한 이유</title><link>https://team-edo.github.io/posts/2026/10/compare-like-for-like-practice-scores/</link><pubDate>Wed, 28 Oct 2026 00:00:00 +0900</pubDate><guid>https://team-edo.github.io/posts/2026/10/compare-like-for-like-practice-scores/</guid><description>&lt;p>얼쑤의 성장 화면에서는 음정, 박자, 길이 점수가 처음보다 어떻게 바뀌었는지 보여줍니다. 같은 곡을 느리게 분 연주와 빠르게 분 연주, 혹은 같은 녹음을 서로 다른 기준으로 채점한 점수를 나란히 두면 점수는 달라질 수 있습니다. 비교에 넣을 연주를 정하는 일이 점수를 빼는 계산보다 먼저였습니다.&lt;/p>
&lt;h2 id="최근의-전체-곡-연주를-기준으로-삼았다">최근 전체 곡 연주가 비교 기준&lt;/h2>
&lt;p>먼저 채점이 끝나 평가가 있는 전체 곡 연주 중 가장 최근 것을 고릅니다. 그 연주와 곡, 악보 버전, 채점 모델 버전, BPM과 BPM 단위가 같은 연주만 비교 대상으로 둡니다. BPM 단위도 같은 이유는 숫자가 같아도 어떤 박을 기준으로 세느냐에 따라 실제 빠르기가 달라지기 때문입니다. 중간부터 끝까지 분 연습 구간은 마지막 음에 도달했어도 전체 곡 연주에서 뺐습니다.&lt;/p></description></item><item><title>정답인데 2~4번째 음이 박자 오류로 찍힌 이유</title><link>https://team-edo.github.io/posts/2026/10/aac-priming-timing-fixture/</link><pubDate>Wed, 28 Oct 2026 00:00:00 +0900</pubDate><guid>https://team-edo.github.io/posts/2026/10/aac-priming-timing-fixture/</guid><description>&lt;p>채점 API의 정상 경로를 검사하려고 악보와 같은 음높이·박자로 합성 연주를 만들었습니다. 합성음을 AAC로 인코딩해 분석 API에 넣자 **2~4번째 음이 &lt;code>TIMING&lt;/code>**으로 판정됐습니다. 악보와 합성기의 박자는 그대로였는데, 파일을 거친 뒤에만 음이 늦어진 것처럼 보였습니다.&lt;/p>
&lt;h2 id="합성음과-채점기-사이에-있던-시간">AAC 준비 샘플과 박자 오차&lt;/h2>
&lt;p>테스트는 FFmpeg의 표준 출력에서 조각 MP4를 받아 사용했습니다. AAC 인코더는 앞에 준비 샘플을 붙일 수 있습니다. 재생 타임라인에는 그 구간을 건너뛰는 정보가 필요한데, 이 테스트의 조각 MP4에는 그 정보가 없었습니다. 앱의 녹음 파일은 일반 MP4였습니다. &lt;a href="https://developer.apple.com/documentation/quicktime-file-format/background_aac_encoding">Apple의 AAC 인코딩 설명&lt;/a>&lt;/p></description></item><item><title>지난달과 연속 연습일 계산하기</title><link>https://team-edo.github.io/posts/2026/10/practice-calendar-previous-month-and-streak/</link><pubDate>Wed, 28 Oct 2026 00:00:00 +0900</pubDate><guid>https://team-edo.github.io/posts/2026/10/practice-calendar-previous-month-and-streak/</guid><description>&lt;p>얼쑤의 성장 화면에는 선택한 기간을 이전 기간과 비교한 수치와 현재 연속 연습일이 함께 나옵니다. 두 숫자 모두 날짜로 계산하지만 기준이 다릅니다. 비교 기간은 사용자가 고른 구간에 따라 바뀝니다. 연속 연습일은 과거 달을 보고 있어도 오늘을 기준으로 유지하기로 했습니다.&lt;/p>
&lt;p>초안에서는 서버가 주간·월간 구간을 정하려고 했습니다. 팀의 API 계약을 대조한 뒤 화면에서 고른 시작일과 종료일을 앱이 보내고, 서버가 그 범위의 요약과 날짜별 집계를 반환하도록 맞췄습니다. 인정 시간 &lt;strong>1분 이상&lt;/strong>을 연습한 날로 보는 규칙도 합의를 거쳐 계약에 반영했습니다. 이때 정한 시작일·종료일 계약을 기준으로 비교 기간과 연속 일수의 계산을 나눴습니다.&lt;/p></description></item><item><title>타임아웃 뒤 ffprobe 종료 확인하기</title><link>https://team-edo.github.io/posts/2026/10/terminate-ffprobe-after-timeout/</link><pubDate>Wed, 28 Oct 2026 00:00:00 +0900</pubDate><guid>https://team-edo.github.io/posts/2026/10/terminate-ffprobe-after-timeout/</guid><description>&lt;p>얼쑤는 녹음을 받으면 ffprobe를 실행해 오디오 형식과 길이를 확인합니다. 리뷰에서 요청을 처리하는 스레드가 대기 중에 중단되면, 서버 코드가 예외로 빠져나가도 자식 프로세스는 남을 수 있다는 점을 발견했습니다. 사용자의 파일 형식이 잘못된 경우와 검사를 끝낼 수 없었던 경우도 앱에서는 다르게 처리해야 합니다.&lt;/p>
&lt;h2 id="예외를-반환하는-경로에서-자식도-정리했다">예외 경로에서 자식 프로세스 종료&lt;/h2>
&lt;p>ffprobe의 표준 출력은 별도 스레드가 읽습니다. 부모가 출력을 읽지 않고 종료만 기다리면, 쓰기 버퍼가 찼을 때 자식도 진행할 수 없기 때문입니다. 부모는 프로세스의 종료와 출력 읽기 완료를 각각 기다립니다. 시간 초과나 읽기 실패는 검사 도구의 오류로 처리합니다. 대기 중 인터럽트를 받으면 현재 스레드에 중단 표시를 복구해 상위 코드도 그 상태를 볼 수 있게 했습니다.&lt;/p></description></item><item><title>트랜잭션 커밋 경계 테스트</title><link>https://team-edo.github.io/posts/2026/10/test-transaction-commit-boundary/</link><pubDate>Wed, 28 Oct 2026 00:00:00 +0900</pubDate><guid>https://team-edo.github.io/posts/2026/10/test-transaction-commit-boundary/</guid><description>&lt;p>연주 분석에는 트랜잭션이 끝나는 시점이 중요한 일이 두 가지 있습니다. 분석 결과를 기록할 때는 세션 상태와 평가를 함께 저장합니다. 녹음이 올라왔을 때는 업로드가 확정된 뒤에만 분석을 시작합니다. 둘 다 오류를 던지는지만 확인하면, 앞서 실행한 쓰기나 바깥에 넘긴 작업이 남았는지 알 수 없습니다.&lt;/p>
&lt;h2 id="db에-실제로-남는-값을-읽었다">롤백 뒤 남은 DB 상태 확인&lt;/h2>
&lt;p>보통 매퍼 테스트는 테스트에 트랜잭션을 열어 두고 끝에서 한꺼번에 되돌립니다. 결과 저장의 원자성 검사에서는 테스트 트랜잭션을 열지 않았습니다. 테스트가 서비스 트랜잭션의 커밋 경계를 대신하면, 서비스를 벗어난 뒤에도 무엇이 남는지 구분하기 어렵기 때문입니다.&lt;/p></description></item><item><title>악보는 29분 30초, 연습은 10분</title><link>https://team-edo.github.io/posts/2026/10/practice-time-idle-checkpoints/</link><pubDate>Tue, 27 Oct 2026 00:00:00 +0900</pubDate><guid>https://team-edo.github.io/posts/2026/10/practice-time-idle-checkpoints/</guid><description>&lt;p>악기를 불면서 악보를 볼 때는 한동안 화면을 건드리지 않습니다. 반대로 악보를 열어 둔 채 자리를 비울 수도 있습니다. 얼쑤의 성장 화면에 연습 시간을 표시하려면 이 두 상황을 구분할 규칙이 필요했습니다. 저는 팀과 집계 기준을 정리하고 서버의 구간 저장 API를 구현했습니다. 앱에서 시간을 재고 기기에 보관해 보내는 부분은 프론트엔드 팀원이 구현했습니다.&lt;/p>
&lt;h2 id="화면이-보인-시간과-연습으로-인정한-시간">화면 표시 시간과 인정 시간&lt;/h2>
&lt;p>악보가 앞에 보이기 시작하면 구간을 하나 엽니다. 화면을 벗어나거나 앱이 뒤로 가면 구간을 닫고, 돌아왔을 때는 새 구간을 엽니다. 구간마다 화면이 보인 시간과 그중 연습으로 인정한 시간을 따로 남깁니다. 성장 화면의 연습 시간은 인정 시간의 합입니다.&lt;/p></description></item></channel></rss>