티스토리 뷰
[Open source contribution] Mockito 오픈소스 기여 경험기 : Sets 유틸리티 클래스 테스트 추가
ebson 2025. 12. 25. 11:31소개 & 배경
Mockito란?
Mockito는 Java 개발자들이 가장 널리 사용하는 mocking framework입니다. 단위 테스트를 작성할 때 실제 객체 대신 가짜 객체(mock)를 생성하여 테스트를 격리하고, 의존성을 제어할 수 있게 해줍니다. 간결한 API를 통해 mock 생성, 동작 정의, 호출 검증을 직관적으로 표현할 수 있어 테스트 코드의 가독성과 유지보수성을 높여 줍니다.
// Mockito를 사용한 간단한 예시
List<String> mockedList = mock(List.class);
when(mockedList.get(0)).thenReturn("first");
assertEquals("first", mockedList.get(0));
Mockito는 2007년부터 개발되어 온 오픈소스 프로젝트로, 현재 GitHub에서 15.3k+ stars를 보유하고 있으며, 전 세계 수많은 Java 프로젝트에서 사용되고 있습니다.
왜 이 PR이 필요한가?
Mockito 프로젝트를 분석하던 중, org.mockito.internal.util.collections.Sets 유틸리티 클래스에 대한 테스트가 부족하다는 것을 발견했습니다. 이 클래스는 Mockito 내부에서 Set 컬렉션을 생성하는 핵심 유틸리티인데, 테스트 커버리지가 충분하지 않았습니다.
테스트가 없는 코드는 다음과 같은 문제를 야기할 수 있습니다:
- 리팩토링 시 회귀 버그 발생 위험: 코드를 수정할 때 기존 동작이 깨질 수 있음
- 동작 검증의 어려움: 코드가 의도한 대로 작동하는지 확인할 방법이 없음
- 문서화 부족: 테스트 코드는 코드의 사용법을 보여주는 살아있는 문서 역할
따라서 이 PR은 Mockito 프로젝트의 코드 품질과 유지보수성 향상을 목표로 합니다.
변경 내용 요약
이 PR은 총 2개의 커밋으로 구성되어 있습니다.
커밋 1: Add tests for Sets utility class
첫 번째 커밋에서는 Sets 유틸리티 클래스의 세 가지 정적 메서드에 대한 포괄적인 테스트를 추가했습니다:
- newMockSafeHashSet(Iterable<Object>) - 4개 테스트
- newMockSafeHashSet(Object...) - 3개 테스트
- newSet(T...) - 10개 테스트
총 17개의 테스트 메서드를 작성하여 다음과 같은 시나리오를 검증합니다:
- Mock 객체 처리
- 일반 객체 처리
- 빈 컬렉션 처리
- Null 값 처리
- 중복 요소 처리
- 순서 보존 (LinkedHashSet 동작)
- 에러 케이스 (null 배열 파라미터)
- Set 연산 (add, remove)
커밋 2: Improve comments in SetsTest
두 번째 커밋은 코드 리뷰 피드백을 반영한 개선 작업입니다:
- 한글 주석을 영문으로 번역 (3개 주석)
- 클래스 레벨 Javadoc 추가
- AssertJ null 처리에 대한 설명 주석 추가 (2개)
이 커밋은 Mockito의 코드 스타일 가이드라인을 준수하고, 국제적인 오픈소스 프로젝트의 표준을 맞추기 위한 것입니다.
핵심 코드 설명
Sets 유틸리티 클래스의 역할
먼저 Sets 클래스가 무엇을 하는지 살펴보겠습니다:
/*
* Copyright (c) 2007 Mockito contributors
* This program is made available under the terms of the MIT License.
*/
package org.mockito.internal.util.collections;
import static java.util.Arrays.asList;
import java.util.LinkedHashSet;
import java.util.Set;
public abstract class Sets {
public static Set<Object> newMockSafeHashSet(Iterable<Object> mocks) {
return HashCodeAndEqualsSafeSet.of(mocks);
}
public static Set<Object> newMockSafeHashSet(Object... mocks) {
return HashCodeAndEqualsSafeSet.of(mocks);
}
public static <T> Set<T> newSet(T... elements) {
if (elements == null) {
throw new IllegalArgumentException(
"Expected an array of elements (or empty array) but received a null.");
}
return new LinkedHashSet<T>(asList(elements));
}
}
이 클래스는 두 가지 주요 기능을 제공합니다:
- newMockSafeHashSet: Mock 객체를 안전하게 저장할 수 있는 Set을 생성합니다. 일반 HashSet은 mock 객체의 hashCode()나 equals() 메서드가 제대로 동작하지 않을 수 있어, HashCodeAndEqualsSafeSet을 사용합니다.
- newSet: 순서를 보존하는 LinkedHashSet을 생성합니다. 일반적인 Set 생성보다 편리한 팩토리 메서드입니다.
핵심 테스트 케이스 1: Mock 객체 처리
가장 중요한 테스트 중 하나는 mock 객체가 제대로 처리되는지 확인하는 것입니다:
@Test
public void should_create_mock_safe_hash_set_from_iterable() {
List<Object> mocks = new ArrayList<>();
TestInterface mock1 = mock(TestInterface.class);
TestInterface mock2 = mock(TestInterface.class);
mocks.add(mock1);
mocks.add(mock2);
Set<Object> result = Sets.newMockSafeHashSet(mocks);
assertThat(result).hasSize(2);
assertThat(result).contains(mock1, mock2);
}
테스트 설계 의도:
- Mock 객체들이 HashCodeAndEqualsSafeSet에 안전하게 저장되는지 검증
- Set의 크기와 포함 여부를 확인하여 정상 동작 확인
- Mockito 내부에서 mock 객체들을 Set으로 관리할 때 이 메서드가 사용되므로, 이 동작이 정확해야 함
핵심 테스트 케이스 2: Null 처리와 AssertJ의 특수한 동작
흥미로운 부분은 null 값 처리입니다. AssertJ의 contains() 메서드를 사용할 때 특별한 주의가 필요합니다:
@Test
public void should_handle_null_in_iterable_for_mock_safe_hash_set() {
List<Object> mocks = new ArrayList<>();
mocks.add(null);
mocks.add(mock(TestInterface.class));
Set<Object> result = Sets.newMockSafeHashSet(mocks);
assertThat(result).hasSize(2);
// Explicit type casting is required for AssertJ's contains() method
// when checking for null values to avoid NullPointerException
assertThat(result).contains((Object) null);
}
왜 명시적 타입 캐스팅이 필요한가?
AssertJ의 contains() 메서드는 varargs를 사용합니다. Java에서 varargs 메서드에 null을 직접 전달하면, 컴파일러가 이를 "null 배열"로 해석할 수 있어 NullPointerException이 발생할 수 있습니다. 따라서 (Object) null로 명시적 캐스팅을 해주어야 합니다.
이것은 단순한 테스트 코드 작성의 실수였지만, 실제로 테스트를 실행하면서 발견한 중요한 학습 포인트였습니다.
핵심 테스트 케이스 3: 순서 보존 검증
newSet 메서드는 LinkedHashSet을 반환하므로, 삽입 순서가 보존되어야 합니다:
@Test
public void should_preserve_order_in_new_set() {
Set<Integer> result = Sets.newSet(3, 1, 2);
// LinkedHashSet preserves insertion order
assertThat(result).containsExactly(3, 1, 2);
}
테스트 설계 의도:
- LinkedHashSet의 핵심 특징인 순서 보존을 검증
- Mockito 내부에서 순서가 중요한 경우(예: 메서드 호출 순서 검증) 이 동작이 보장되어야 함
개선된 주석 설명
왜 주석 개선이 필요했나?
두 번째 커밋에서 주석을 개선한 이유는 코드 리뷰 피드백 때문이었습니다. 리뷰어가 다음과 같이 요청했습니다:
"Please either remove these comments or use English"
오픈소스 프로젝트에서는 영문 주석이 표준입니다. 전 세계의 기여자들이 코드를 이해할 수 있어야 하기 때문입니다.
개선된 주석의 예시
Before (한글 주석):
Set<Object> result = Sets.newMockSafeHashSet(mock, mock);
// Mock 객체는 동일한 인스턴스이므로 Set에는 하나만 포함
assertThat(result).hasSize(1);
After (영문 주석):
Set<Object> result = Sets.newMockSafeHashSet(mock, mock);
// Since mock objects are the same instance, only one is included in the Set
assertThat(result).hasSize(1);
클래스 레벨 Javadoc 추가
또한 클래스 레벨 Javadoc을 추가하여 테스트 클래스의 목적을 명확히 했습니다:
/**
* Tests for {@link Sets} utility class which provides factory methods
* for creating mock-safe hash sets and regular sets.
*/
public class SetsTest {
// ...
}
실무적 의미
Mockito 프로젝트 품질 향상
이 PR이 Mockito 프로젝트에 기여하는 방식은 다음과 같습니다:
- 테스트 커버리지 향상
- Sets 유틸리티 클래스의 모든 메서드와 엣지 케이스를 검증
- 향후 리팩토링 시 회귀 버그를 방지하는 안전망 제공
- 코드 신뢰성 증대
- 17개의 테스트 케이스로 다양한 시나리오 검증
- Mock 객체, null 값, 중복 요소 등 실제 사용 시나리오 커버
- 문서화 효과
- 테스트 코드가 Sets 클래스의 사용법을 보여주는 살아있는 문서
- 새로운 기여자가 코드를 이해하는 데 도움
향후 개발자/사용자에게 주는 이점
- 리팩토링 안전성
- Sets 클래스를 수정할 때 테스트가 자동으로 회귀 버그를 감지
- CI/CD 파이프라인에서 자동으로 검증
- 버그 예방
- 엣지 케이스( null 처리, 중복 요소 등)를 명시적으로 테스트
- 실제 버그 발생 전에 문제를 발견할 수 있음
- 학습 자료
- Mockito 내부 구현을 이해하려는 개발자들에게 좋은 참고 자료
- Mock 객체를 Set으로 관리하는 방법을 보여줌
이번 오픈소스 기여의 가치
이 PR은 기능 추가나 버그 수정이 아닌 테스트 추가라는 비교적 작은 기여입니다. 하지만 오픈소스 프로젝트에서 이런 기여도 매우 중요합니다: 테스트 커버리지는 프로젝트의 건강 지표입니다. 작은 기여로 시작하여 프로젝트에 익숙해질 수 있는 좋은 기회입니다. 또한, 코드 리뷰를 통해 다른 개발자들과 소통할 기회가 되기도 합니다.
맺음말
이번 PR을 통해 Mockito 오픈소스 프로젝트에 작지만 의미 있는 기여를 할 수 있었습니다. 테스트 코드 작성, 코드 리뷰 피드백 반영, 그리고 오픈소스 기여 프로세스를 경험하는 소중한 기회였습니다. 이번 경험으로 제가 얻은 것은 다음과 같습니다.
- 코드 리뷰 피드백을 받고 개선하는 과정
- 작은 기여라도 프로젝트에 도움이 된다는 것을 확인한 것
오픈소스 기여 경험을 통해 많은 것을 느꼈습니다. 작은 테스트 추가부터 시작하여 점진적으로 프로젝트에 기여할 수 있습니다. 관심 있는 오픈소스 프로젝트를 찾아 작은 기여부터 시작해보는 것은 개발자로서 좋은 도전인 것 같습니다. 다양한 오픈소스에 기여하는 경험으로 제품의 지속적 발전의 책임이 있는 개발자로서의 소양을 함양하고 가치있는 배움들을 얻을 수 있으리라 생각합니다.
관련 참고 자료
- Mockito 공식 문서: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html
- Mockito GitHub 저장소: https://github.com/mockito/mockito
- Mockito 기여 가이드: https://github.com/mockito/mockito/blob/main/.github/CONTRIBUTING.md
- PR #3771: https://github.com/mockito/mockito/pull/3771
- AssertJ 문서: https://assertj.github.io/doc/
- Total
- Today
- Yesterday
- Cache Avalanche
- 백엔드 아키텍처
- Redis vs DB
- Enum 기반 싱글톤
- Java Performance
- Spring Batch
- 백엔드 성능
- Double-Checked Locking
- 백엔드 성능 튜닝
- spring batch 5
- 트래픽 처리
- Redis 성능 개선
- Initialization-on-Demand Holder Idiom
- 백엔드 성능 설계
- Eager Initialization
- Redis 캐시 전략
- Hot Key 문제
- 캐시 장애
- 스레드 생명주기
- Cache Penetration
- 동시성처리
- DB 인덱스 성능
- InterruptedException
- 캐시 성능 비교
- TTL 설계
- mybatis
- 트랜잭션 관리
- 캐시와 인덱스
- Cache Aside
- DB 트랜잭션
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
| 29 | 30 | 31 |

