AI와 함께하는 테스트 자동화: 플러그인 개발기

6 days ago 5

프롤로그

저희 팀에서 관리하는 레포지토리는 7개입니다. 빠른 비즈니스 과제를 해내가며 필수 테스트코드만 작성하다 보니 커버리지가 부족한 레포지토리가 생겼습니다. 특히 엣지케이스나 예외 상황에 대한 테스트가 부족했고, 이는 운영 안정성을 떨어뜨리는 원인이 되었습니다.


30분 만에 100개 테스트 자동 생성, 어떻게 했을까?

100개의 단위 테스트를 작성하고, 실제로 동작하는지 검증까지 완료하는 데 30분이 걸렸습니다.

BUILD SUCCESSFUL 총 테스트: 100개 ✅ 통과: 97개 (97%) ⏭️ 스킵: 3개 (3%) - 통합 테스트 필요 ❌ 실패: 0개 (0%) 생성된 파일: - ProductSortServiceTest.kt (25/25 TC 통과) - ProductFilterServiceTest.kt (24/24 TC 통과) - BadgePriorityServiceTest.kt (23/23 TC 통과) - DiscountCalculatorTest.kt (25/25 TC 통과)

비결은 간단합니다.

IntelliJ 플러그인이 컴파일 보장 템플릿을 생성하고, Amazon Q가 구현을 채워넣는 방식입니다.

플러그인 (1분) → 컴파일 보장 템플릿 → Amazon Q (2분) → 완성된 테스트

1. AI 에이전트로 테스트 코드 작성 시도

1.1 AI 에이전트 사용 경험

ChatGPT, Claude, Gemini 같은 AI 에이전트들이 테스트 코드 생성에 도움을 줍니다. 특히 IDE에 통합된 AI 도구들은 프로젝트 컨텍스트를 이해하기 때문에 더 정확한 코드를 생성합니다.

여러 IDE 통합 AI 도구들을 실제로 비교 테스트해본 결과, Amazon Q를 선택했습니다.

IDE 통합 AI 도구 비교

도구 IDE 환경 비용 구조 의존성 인식 컨텍스트 이해
GitHub Copilot VS Code 기반 월 $10 고정 제한적 파일 단위
Amazon Q IntelliJ 통합 월 $19 (Pro tier) 우수 프로젝트 전체
Cursor 독립 IDE 사용량 기반 (예측 어려움) 우수 프로젝트 전체

Amazon Q 선택 이유

가장 큰 이유는 기존 개발 환경을 그대로 유지할 수 있다는 점입니다. 우리 팀은 이미 IntelliJ IDEA를 사용하고 있어 단축키, 환경설정, 플러그인 생태계에 익숙합니다. Cursor로 전환하면 새로운 IDE에 적응하는 학습 비용이 발생하지만, Amazon Q는 IntelliJ에 플러그인 형태로 통합되어 익숙한 환경에서 바로 AI 기능을 사용할 수 있습니다.

실제 사용 과정

나: "ProductDisplayService 클래스의 테스트 코드 작성해줘" Amazon Q: [테스트 코드 생성] 나: "우리 팀은 Kotest FunSpec을 사용해. Context 구조로 작성해줘" Amazon Q: [수정된 코드 생성] 나: "MockK를 사용하고, given-when-then 주석을 명시해줘" Amazon Q: [다시 수정된 코드 생성]

발견한 문제

10개 클래스에 테스트를 작성하면서 패턴이 보였습니다.

  • 매번 같은 컨벤션 설명 반복
  • 어떤 메서드를 테스트해야 할지 판단하기
  • 생성된 코드의 빌드 오류 수정 (15% 정도)
  • 클래스마다 10분씩 소요

실제 소요 시간 측정

  • ProductDisplayService: 11분 (빌드 오류 1회 수정)
  • MinOrderService: 9분 (빌드 오류 없음)
  • DeliveryFeeService: 12분 (빌드 오류 2회 수정)
  • 평균: 약 10분/클래스

이 반복 작업을 자동화하면 어떨까?

1.2 해결 방법 모색

세 가지 방법을 고민했습니다.

방법 1: 프롬프트 템플릿

  • 팀 컨벤션을 문서화해서 매번 복사
  • 한계: 의존성은 여전히 수동, 클래스마다 다른 정보 필요

방법 2: 스크립트 자동화

  • 간단한 스크립트로 클래스 정보 추출
  • 한계: IDE 통합 부족, 사용성 떨어짐

방법 3: IntelliJ 플러그인

  • IDE에 완전히 통합
  • PSI 트리로 정확한 코드 분석
  • 원클릭으로 모든 정보 수집

세 번째 방법을 선택했습니다. 테스트 코드 작성 시간을 단축하기 위해 반복 작업을 자동화하는 IntelliJ 플러그인을 개발하기로 했습니다.


2. 첫 시도: 한계가 보였다

2.1 플러그인 첫 버전

목표

  • 팀 테스트 패턴 자동 학습
  • 클래스 의존성 자동 수집
  • 커버리지 분석으로 우선순위 제시
  • AI에게 최적화된 프롬프트 전달

2.2 플러그인 첫 버전 사용 결과

첫 버전은 간단한 구조였습니다. 클래스 코드와 의존성을 수집하고, Gemini API를 호출하여 완성된 테스트 코드를 생성하는 방식이었습니다.

10개 클래스로 파일럿 테스트

결과 건수 비율
테스트 생성 성공 10/10 100%
컴파일 성공 1/10 10%
컴파일 실패 9/10 90%
수정 없이 바로 실행 가능 0/10 0%

거의 모든 케이스에서 컴파일이 깨지는 형태로 코드가 생성되었습니다. 생성된 코드를 수정하는 데 추가 시간이 필요했고, 이는 처음부터 AI 어시스턴트를 사용하는 것보다 두 배의 작업을 야기했습니다.

발견한 주요 문제들

문제 1: 기존 테스트가 사라진다

가장 심각한 문제였습니다. 테스트를 추가하려고 했더니 기존 테스트가 완전히 대체되어 버렸습니다. 10개 클래스 모두에서 이 문제가 발생했습니다.

// 기존 테스트 (163줄) class ProductServiceTest : FunSpec({ context("상품 조회") { test("정상 케이스") { ... } test("상품 없음") { ... } } }) // AI 생성 결과 - 기존 테스트 완전 교체 class ProductServiceTest : FunSpec({ context("새로운 테스트") { // 기존 테스트는 사라짐... } })

문제 2: Import 오류 (23%)

import comor.example.domain.Product // com → comor import ioio.kotest.matchers.shouldBe // io → ioio

문제 3: 존재하지 않는 클래스/필드 (18%)

import com.example.domain.ProductValidator // 실제로는 없는 클래스 every { product.category } returns "FOOD" // category 필드는 존재하지 않음

문제 4: 타입 불일치 (15%)

every { location.latitude } returns "37.5" // Double인데 String

문제 5: 유효하지 않은 Mock 데이터 (12%)

enum class ProductStatus { ACTIVE, INACTIVE, DELETED } every { status.name } returns "PENDING" // 존재하지 않는 값

완전 자동화 방식은 한계가 명확했습니다. AI가 생성한 코드는 "그럴듯하지만" 실제로는 동작하지 않는 경우가 많았습니다.


3. 팀 공유: 함께 발견한 개선점

3.1 팀원들의 사용 경험

플러그인이 팀에 많은 도움이 될 거라 기대했지만, 현실은 녹록지 않았습니다.

사용성 문제

  • 플러그인 설치 복잡성
  • Amazon Q와의 차별성에 대한 의문
  • 사용법을 알기 어려움

기술적 문제

  • 컴파일 오류 빈번 발생 (Import 오타, 타입 불일치)
  • 전체 메서드 커버리지 부족 (일부 메서드 누락)

플러그인을 설치하고 사용법을 익히는 것 자체가 허들이었습니다. 새로운 도구를 도입하려면 그만큼 명확한 가치를 보여줘야 하는데, 컴파일 오류와 기존 테스트 손실 같은 문제들이 그 가치를 희석시켰습니다.

3.2 발견한 문제 패턴

팀원들과 함께 사용하면서 구체적인 문제 패턴이 보였습니다.

문제 1: AI 할루시네이션

import comor.example.domain.Product // com → comor (오타) import com.example.domain.ProductValidator // 없는 클래스

문제 2: 타입 불일치

every { location.latitude } returns "37.5" // Double인데 String

문제 3: 기존 테스트 손실

  • 추가 테스트 생성 시 기존 테스트 덮어쓰기
  • Git diff: 163줄 → 30줄로 축소

문제 4: 불완전한 커버리지

  • 15개 메서드 중 10개만 테스트 생성
  • 5개 메서드 누락

문제 5: 복잡한 프로젝트 구조에서의 혼란

실제 운영 중인 프로젝트는 멀티모듈 구조에 외부 의존성이 많고, 같은 이름의 클래스가 다른 패키지에 존재하는 경우가 빈번합니다. AI는 이런 복잡한 환경에서 어떤 클래스를 import 해야 할지 혼란스러워합니다.

// 실제로는 3개의 다른 Product 클래스가 존재 import com.example.api.dto.Product // API 모듈 import com.example.domain.Product // Domain 모듈 import com.example.external.Product // 외부 라이브러리 // AI가 잘못된 것을 선택하거나, 존재하지 않는 패키지를 만들어냄 import com.example.service.Product // 없는 클래스

이런 문제는 단순한 예제 코드에서는 발생하지 않습니다. 이 글의 예시 코드처럼 간단한 구조의 프로젝트라면 플러그인만으로도 테스트 코드가 잘 생성됩니다. 하지만 실제 멀티모듈 프로젝트에서는 이런 복잡성 때문에 AI가 빈번하게 오류를 만들어냈고, 이것이 플러그인 단독 사용을 포기하게 된 결정적 이유였습니다.

왜 실제 사례를 보여주지 않나요?
실제 멀티모듈 환경의 문제를 재현하려면 사내 코드를 공개하거나, 복잡한 멀티모듈 프로젝트를 별도로 구성해야 합니다. 이는 공수가 매우 크고 보안상 문제가 있어 이 글에서는 단순화된 예시만 다룹니다.


4. 개선: 왜 문제가 생겼을까?

4.1 근본 원인 분석

팀원들과 함께 원인을 분석했습니다.

플러그인이 해결한 것

  • 팀 컨벤션 자동 학습
  • 의존성 자동 수집
  • 우선순위 자동 제시

플러그인이 해결하지 못한 것

  • AI 생성 코드의 정확성
  • 완전한 커버리지
  • 컴파일 성공 보장
  • 기존 테스트 보존

핵심 문제

플러그인은 "AI에게 더 좋은 정보를 주는 도구"일 뿐입니다. AI 자체의 한계는 플러그인으로 극복할 수 없었습니다.

특히 멀티모듈 환경에서는 플러그인이 수집한 정보만으로는 부족했습니다. 같은 이름의 클래스가 여러 모듈에 존재하거나, 외부 라이브러리의 DTO와 내부 도메인 객체가 혼재된 상황에서 AI는 정확한 판단을 내리지 못했습니다.

4.2 자동 수정의 한계

"AI가 오류를 자동으로 고치면 되지 않나요?"

이 방법도 시도해봤습니다.

실험 결과

시도 성공률 평균 시간 비용
1회 30% 40초 1x
2회 55% 80초 2x
3회 65% 120초 3x

35%는 3번 시도해도 실패했고, 시간과 비용만 증가했습니다.

왜 실패했을까?

IDE 통합 AI와 외부 API의 차이 때문입니다.

IDE 통합 AI (Amazon Q)

  • IDE와 완전히 통합
  • 실시간 컴파일러 접근
  • 정확한 타입 정보와 컨텍스트
  • 구조화된 오류 정보

외부 API (Gemini)

  • IDE와 분리되어 정보 손실
  • 텍스트 오류 메시지만 전달
  • 컨텍스트 없이 추측으로 수정
  • 같은 오류 반복 가능

5. 전환: 템플릿 + Amazon Q 방식

5.1 새로운 접근

기존 방식

플러그인 → AI → 완성된 테스트 (오류 가능)

새로운 방식

플러그인 → 컴파일 보장 템플릿 → Amazon Q → 완성된 테스트

핵심 아이디어

Amazon Q가 어려워하는 부분은 플러그인이 해결하고, Amazon Q가 잘하는 부분은 Amazon Q에게 맡기자. 이를 위해 역할을 다음과 같이 나눴습니다.

플러그인 역할

  • 정확한 템플릿 생성 (컴파일 보장)
  • 모든 메서드 커버리지 보장
  • 기존 테스트 보존
  • 팀 컨벤션 적용

Amazon Q 역할

  • 템플릿의 TODO 구현
  • IDE 컨텍스트로 정확한 코드 생성
  • 실시간 오류 수정

5.2 실제 비교: 복잡한 레거시 코드로 테스트

실무에서 흔히 볼 수 있는 "빠르게 작성된" 레거시 코드로 테스트했습니다.

테스트 대상 코드

class ProductDisplayProcessor( private val productRepo: ProductRepository, private val categoryService: CategoryService, private val discountCalc: DiscountCalculator, private val badgeService: BadgeService ) { fun process(request: DisplayRequest): DisplayResponse { val products = productRepo.findByIds(request.productIds) val result = mutableListOf<DisplayProduct>() for (p in products) { if (p.stock <= 0) continue if (p.status != "ACTIVE") continue val category = categoryService.getCategory(p.categoryId) ?: continue if (!category.isDisplayable) continue val price = if (p.discountRate > 0) { discountCalc.calculate(p.price, p.discountRate) } else p.price val badges = badgeService.getBadges(p.id) val mainBadge = badges.firstOrNull { it.priority == 1 } result.add(DisplayProduct(p.id, p.name, price, mainBadge?.name)) } return DisplayResponse(result.sortedBy { it.priority }) } }

문제점: 5개 의존성, 복잡한 분기 로직, Null 체크 산재, 중첩 조건문


방식 1: Amazon Q만 사용

프롬프트: "ProductDisplayProcessor 테스트 작성해줘. Kotest, MockK 사용"

생성된 테스트:

context("process") { test("정상 케이스 - 모든 조건을 만족하는 상품") { /* 구현됨 */ } test("재고가 0인 상품은 제외") { /* 구현됨 */ } test("카테고리가 null인 경우") { /* 구현됨 */ } test("할인율이 있는 경우") { /* 구현됨 */ } }

결과: 4개 테스트 생성, 커버리지 60%

누락된 케이스:

  • 상태가 INACTIVE인 경우
  • 카테고리가 노출 불가능한 경우
  • 할인율이 0인 경우
  • 뱃지가 없는 경우
  • 우선순위 정렬 검증

방식 2: 플러그인 + Amazon Q

1단계: 플러그인이 생성한 템플릿

context("상품 필터링") { test("재고가 0인 상품은 제외된다") { /* TODO */ } test("상태가 ACTIVE가 아닌 상품은 제외된다") { /* TODO */ } } context("카테고리 검증") { test("카테고리가 null이면 상품은 제외된다") { /* TODO */ } test("카테고리가 노출 불가능하면 상품은 제외된다") { /* TODO */ } } context("할인 계산") { test("할인율이 0보다 크면 할인가가 계산된다") { /* TODO */ } test("할인율이 0이면 원가가 사용된다") { /* TODO */ } } context("뱃지 처리") { test("우선순위 1인 뱃지가 메인 뱃지로 선택된다") { /* TODO */ } } // ... 총 12개 테스트

2단계: Amazon Q에게 TODO 구현 요청

프롬프트: "위 템플릿의 TODO를 모두 구현해줘"

결과: 12개 테스트 모두 구현, 커버리지 95%, 컴파일 즉시 성공


비교 결과

항목 Amazon Q만 플러그인 + Amazon Q
생성된 테스트 수 4개 12개
커버리지 60% 95%
누락된 엣지케이스 5개 0개
추가 프롬프트 필요 많음 없음
소요 시간 8분 4분

핵심 차이: Amazon Q는 주요 케이스는 잘 생성하지만 엣지케이스를 놓치기 쉽습니다. 개발자가 "뭘 놓쳤지?"를 판단하고 추가 요청해야 합니다. 플러그인은 코드 분석으로 모든 분기를 빠짐없이 추출합니다.

5.3 핵심 개선사항

개선 1: TC 리스트 정제 자동화

대량의 TC 리스트에서 단위 테스트로 검증 가능한 케이스를 자동으로 선별합니다.

TC 리스트 정제 작업: 60~120초 (과제당 1회만 수행) - UI 테스트 제외 - API 통합 테스트 제외 - 단위 테스트 가능 케이스만 추출

플러그인은 Gemini API를 활용하여 TC 리스트를 분석하고, 다음과 같은 형태로 정제된 결과를 반환합니다.

아래는 실제 서비스 테스트 케이스와는 다른 예시입니다.

{ "refinedTCs": [ { "tcNumber": "TC-001", "unitTestGoal": "상품 우선순위에 따른 정렬 순서 검증", "serverLogic": "우선순위 값이 낮을수록 상위 노출", "testableConditions": [ "priority: [1, 3, 2] → sorted: [1, 2, 3]" ] }, { "tcNumber": "TC-002", "unitTestGoal": "품절 상품 필터링 로직 검증", "serverLogic": "재고가 0인 상품 제외", "testableConditions": [ "stock: 0 → filtered out" ] } ] }

이렇게 정제된 TC는 각 클래스의 책임에 맞게 자동으로 분산되어 템플릿으로 생성됩니다.

개선 2: 아키텍처 분석

단순히 모든 TC를 한 클래스에 몰아넣지 않고, 각 클래스의 역할을 분석하여 적절히 분산합니다.

class ArchitectureAnalysisService { fun analyzeArchitecture(targetClass: PsiClass, testCases: List<TCRow>) { // 오케스트레이션 레이어 판단 // TC 책임 소재 분석 // 통합 테스트 식별 } }

예를 들어 DisplayApiService 클래스를 분석하면:

  • 현재 클래스: TC 분기 로직만 테스트
  • ProductSortService: 상품 정렬 TC 분산
  • ProductFilterService: 상품 필터링 TC 분산

6. 결과: 실제 효과는?

6.1 정량적 성과

작업 시간 단축

방식 클래스당 시간 100개 클래스
Amazon Q만 10분 16.7시간
플러그인 + Amazon Q 3분 5시간
단축 효과 70% 11.7시간 절약

코드 품질 향상

지표 Amazon Q만 플러그인 + Amazon Q
컴파일 성공률 85% 95%
메서드 커버리지 67% 95%
빌드 오류 수정 필요 (15%) 거의 없음

6.2 정성적 효과

개발자 경험 개선

  • 반복 작업 자동화로 피로도 감소
  • 일관된 테스트 구조로 가독성 향상
  • 컴파일 보장으로 안정감 증가

팀 협업 강화

  • 팀 컨벤션 자동 적용으로 코드 리뷰 시간 단축
  • 신규 팀원도 쉽게 테스트 작성 가능
  • 레거시 코드 개선 동기 부여

7. 맺음말

"AI가 테스트 코드를 자동으로 작성해준다면?"

이 질문에 대한 우리의 답은 "완벽한 자동화는 불가능하지만, 효율적인 보조는 가능하다"입니다.

처음에는 Amazon Q만으로 충분하다고 생각했습니다. 하지만 반복 작업이 많아 플러그인으로 자동화를 시도했습니다. 첫 버전은 여러 문제가 있었습니다. 컴파일 오류, 기존 테스트 손실, 불완전한 커버리지 등 AI 완전 자동화의 한계가 명확했습니다. 여러 방식을 고민하다가 템플릿 방식으로 전환했고, Amazon Q와 연동하여 실용적인 해법을 찾았습니다.

최종 성과

  • 클래스당 작업 시간 70% 단축 (10분 → 3분)
  • 컴파일 보장 템플릿으로 95% 컴파일 성공
  • 실제 동작하는 테스트 코드 97% 통과
  • 100개 클래스 기준 11.7시간 절약

핵심 교훈

  1. 완벽한 자동화는 환상: AI는 "보조 도구"이지 "대체 도구"가 아닙니다. 사람의 검증과 판단이 반드시 필요합니다.

  2. 도구 간 역할 분담: 플러그인은 반복 작업 제거와 정확한 구조 제공, Amazon Q는 구체적 구현 지원, 개발자는 최종 판단 및 검증을 담당합니다.

  3. 팀과 함께 개선: 혼자 만들지 말고 팀과 함께 사용하며 개선하는 것이 중요합니다.

이 프로젝트는 AI 도구를 실무에 적용하는 과정에서 얻은 실질적인 경험을 담고 있습니다. 완벽한 자동화보다는 개발자의 생산성을 높이는 실용적인 도구를 만드는 것이 목표였고, 그 목표를 달성할 수 있었습니다. 앞으로도 팀원들의 피드백을 반영하며 지속적으로 개선해 나갈 계획입니다.

Read Entire Article