Claude Code를 사용하여 WPF 업무 애플리케이션에 단위 테스트를 작성한 경험 - 염두에 둔 세 가지 사항
요약
WPF 업무 애플리케이션에 Claude Code를 활용하여 MSTest 단위 테스트 작성 경험을 공유합니다. 원래 테스트가 어려웠던 구조였으나, AI에게 계획 수립부터 맡기고 검증 규칙을 명확히 함으로써 커버리지를 63%에서 92%까지 향상시켰습니다.
핵심 포인트
- AI에게 코드를 요청하기 전, '계획 수립' 단계를 먼저 거쳐 방향성을 사람이 검토해야 합니다.
- 테스트 목표(예: 커버리지)를 숫자로 제시하면 AI가 스스로 다음 개선 단계를 판단합니다.
- 헬퍼 메서드 남용은 가독성을 떨어뜨리므로, 테스트에 직접 작성하는 것이 좋습니다.
- '하지 않을 것'과 같은 제약 사항을 명시하여 AI의 과도한 리팩토링을 방지해야 합니다.
.NET Framework / WPF 기반의 업무 애플리케이션에 Claude Code를 활용하여 MSTest 단위 테스트를 추가했습니다.
대상은 마치 빵집의 '작업 실적 입력 앱(매장 태블릿 버전)'과 같은 화면이라고 생각하시면 됩니다.
원래 앱은 테스트가 쉽도록 설계되지 않았습니다.
- ViewModel 내부에서 바로
new DbContext()를 호출하는 경우 - 정적인 유틸리티(로그인 정보, 로그 출력)를 직접 호출하는 경우
- 화면에 라이선스가 있는 서드파티 컨트롤이 나열되어 있는 경우
그 결과, 커버리지 63%에서 92%로 향상되었고, 284개의 테스트가 통과할 수 있었습니다.
본문에서는 그때 염두에 두었던 사항을 세 가지로 좁혀 소개합니다.
- 지켜야 할 규칙을 먼저 정하고 AI에게 '계획 수립'부터 맡기기
- 헬퍼 메서드를 너무 많이 만들지 않기
- 검증(Verification)을 '강하게' 하기
① 팀 공통 절차서 (AGENTS.md) 준비하기
② Claude에게 '테스트를 작성하기 위한 프롬프트'를 만들게 하기
③ 사람이 프롬프트를 리뷰하고 수정하기
...
핵심은 바로 '테스트를 써줘'라고 요청하지 않는 것입니다.
처음에는 다음과 같이 '프롬프트를 만들어 줘'라고 요청합니다.
〇〇 화면에 MSTest용 테스트 코드를 작성하기 위한 프롬프트를 생성해 주세요.
코드 블록으로 출력해 주세요.
― 대상 및 참고 자료
...
Claude는 대상 코드와 자료를 읽고 '무엇을, 어떤 순서로, 무엇을 지키면서 할 것인지'를 정리하여 보여줍니다.
사람은 그것을 읽고 수정하기만 하면 되므로, 실행하기 전에 방향성을 검토할 수 있습니다.
## 제약 사항 (반드시 지켜야 함)
1. 본체의 프로덕션 코드는 변경하지 않는다.
테스트하기 쉽도록 수정이 필요하다면, 실행하지 않고 제안으로 보고한다.
...
'하지 않을 것'을 명시해 두지 않으면, AI는 배려심이 넘쳐서 본체를 리팩토링하거나 라이선스 체크를 우회할 수도 있습니다.
네 번째 사항도 효과적이었습니다.
AI가 무리하게 숫자를 맞추려고 하지 않고, 생성할 수 없는 화면은 이유를 적어 Inconclusive로 처리해 주는 등 성실한 방식으로 응답합니다.
첫 세션이 끝났을 때 커버리지는 63%였습니다.
다음 세션에서 요청한 것은 이것뿐이었습니다.
현재 전체 커버리지가 63%이니, 커버리지를 좀 더 높여 주세요.
Claude는 커버리지 리포트에서 도달하지 못한 분기(branch)를 찾아내고, 필요한 구조를 스스로 추가하여 92%까지 올려주었습니다.
목표가 숫자로 제시되면, AI는 다음에 무엇을 해야 할지 스스로 판단할 수 있습니다.
도중에 알게 된 함정은 Claude의 메모리에 남겨두었습니다.
- DbContext의 인자 없는 생성자는 내부적으로 연결 확인을 수행함
- 라이선스가 등록되지 않은 컨트롤을 생성하면, 테스트 호스트가 다운됨
덕분에 다음 화면(사무실 PC 버전)은 그 부분을 처음부터 피해서 시작할 수 있었습니다.
AI에게 테스트 작성을 맡기면, 공통화하려는 경향이 있습니다.
ArrangeDefaultData()
AssertResultIsValid()
CreateModelWithStandardSetup()
와 같은 헬퍼(helper) 메서드가 계속 늘어납니다.
보기에는 깔끔하지만, 리뷰하는 입장에서는 힘듭니다.
- 테스트 하나를 읽기 위해 헬퍼를 여러 단계를 추적해야 함
- '이 테스트가 무엇을 준비하고, 무엇을 확인하는지'가 테스트 메서드만으로는 알 수 없음
- 헬퍼에 버그가 있으면, 모든 테스트가 함께 잘못된 방식으로 통과해버림
AI가 작성한 코드를 사람이 리뷰하려면, 가독성은 리뷰 비용에 직결됩니다.
| 공통화한 (헬퍼로 만든 부분) | 공통화하지 않은 (테스트에 직접 작성한 부분) |
|---|---|
| 테스트 대상 DLL 로딩 | 테스트 데이터 준비 |
| ... | |
| '어떤 테스트에서도 같고, 테스트의 의미와 관계없는 부분'만 헬퍼로 만들었습니다. |
준비-실행-검증은 모두 테스트 메서드 안에 보이도록 작성하는 것을 우선했습니다.
[STATestMethod]
public void 焼成中か判定_該当する作業中データが無い_falseを返す()
{
...
다소 장황하고 중복되더라도, 테스트 하나를 위에서부터 읽으면 의미가 파악되는 것을 우선했습니다.
AI에 맡기면 이런 테스트가 나오기 쉽습니다.
// 약한 검증의 예시
Assert.IsNotNull(requester); // 생성됨
Assert.IsNotNull(showMethod, "Show 메서드가 존재함"); // 메서드가 있음
...
이것만으로도 커버리지는 올라갑니다.
하지만 처리 과정에서 잘못된 값을 반환해도 통과해 버리기 때문에 테스트로서의 가치는 거의 없습니다.
그래서 '에러가 발생하지 않음', '값이 존재함'을 확인하는 것을 넘어, '에러 없이, 〇〇 값이 설정되어 있음'까지 확인하도록 변경했습니다.
[STATestMethod]
public void 굽는 중인지 판별_DB 예외_false를 반환하고 에러 로그를 출력함()
{
...
}
| 약한 검증 | 강한 검증 |
|---|---|
| 예외가 발생하지 않음 | 예외 없이, 반환값이 false이고, 로그에 특정 문구가 출력됨 |
IsNotNull(result) | AreEqual(기대값, result) |
| 리스트가 비어있지 않음 | 개수와 내용물이 기대한 대로 (CollectionAssert.AreEqual) |
| 알림이 왔음 | 어떤 속성이, 어떤 순서로, 몇 번 알림이 왔는지 |
| 메시지 박스가 뜸 | 어떤 문구로 떴는지 |
이러한 방침은 프롬프트에도 전달했고, 리뷰 시에는 '이 Assert로는 잘못된 구현을 놓치지 않을까?'를 반드시 확인했습니다.
로그나 메시지 박스의 문구까지 확인할 수 있는 이유는, 그것들을 단순히 '기록만 하는' 가짜 객체로 대체했기 때문입니다.
대체할 때 '무엇이 전달되었는지'를 기록해 두면, 검증을 훨씬 강력하게 할 수 있습니다.
AI에 맡기는 부분이 클수록, 마지막 확인은 인간의 몫입니다.
실제 DB와 연결되어 있지 않은지: '연결 문자열 자체가 없음'을 테스트 항목으로 만듭니다.
본체 파일이 변경되지 않았는지: 빌드 과정에서 결과물 폴더의 DLL이 덮어쓰여진 적이 있었습니다. 커밋 전에 git status
로 확인합니다.
: '테스트할 수 없다'고 하는 부분이 정말 불가능한지, 읽는 사람이 Inconclusive
의 이유가 타당한지
Assert가 약하지 않은지: 위의 표에 대입하여 확인합니다.
Claude Code에게 테스트 작성을 맡길 때 의식했던 점은 다음 3가지였습니다.
지켜야 할 규칙과 종료 조건을 처음에 프롬프트로 전달하기
바로 작성하게 하기보다, 먼저 프롬프트를 만들게 하고 리뷰합니다.
- 헬퍼(Helper)를 너무 많이 만들지 않기
공통화는 '테스트의 의미와 관계없는 기반'에만 한정합니다. 테스트 하나만 읽어도 의미가 파악되는 것을 우선시합니다. - 검증을 강화하기
'에러가 발생하지 않음', '값이 있음'이 아니라, '에러 없이, 〇〇 값이 되어 있음'까지 확인합니다.
AI는 테스트를 빠르고 대량으로 작성해 줄 수 있습니다.
그렇기 때문에, 사람이 읽을 수 있고, 오류를 정말로 찾아낼 수 있는 테스트를 만드는 것이 중요하다고 느꼈습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기