티스토리 뷰


소개 & 배경

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 유틸리티 클래스의 세 가지 정적 메서드에 대한 포괄적인 테스트를 추가했습니다:

  1. newMockSafeHashSet(Iterable<Object>) - 4개 테스트
  2. newMockSafeHashSet(Object...) - 3개 테스트
  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));
    }
}

 

이 클래스는 두 가지 주요 기능을 제공합니다:

  1. newMockSafeHashSet: Mock 객체를 안전하게 저장할 수 있는 Set을 생성합니다. 일반 HashSet은 mock 객체의 hashCode()나 equals() 메서드가 제대로 동작하지 않을 수 있어, HashCodeAndEqualsSafeSet을 사용합니다.
  2. 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 프로젝트에 기여하는 방식은 다음과 같습니다:

  1. 테스트 커버리지 향상
    • Sets 유틸리티 클래스의 모든 메서드와 엣지 케이스를 검증
    • 향후 리팩토링 시 회귀 버그를 방지하는 안전망 제공
  2. 코드 신뢰성 증대
    • 17개의 테스트 케이스로 다양한 시나리오 검증
    • Mock 객체, null 값, 중복 요소 등 실제 사용 시나리오 커버
  3. 문서화 효과
    • 테스트 코드가 Sets 클래스의 사용법을 보여주는 살아있는 문서
    • 새로운 기여자가 코드를 이해하는 데 도움

향후 개발자/사용자에게 주는 이점

  1. 리팩토링 안전성
    • Sets 클래스를 수정할 때 테스트가 자동으로 회귀 버그를 감지
    • CI/CD 파이프라인에서 자동으로 검증
  2. 버그 예방
    • 엣지 케이스( null 처리, 중복 요소 등)를 명시적으로 테스트
    • 실제 버그 발생 전에 문제를 발견할 수 있음
  3. 학습 자료
    • Mockito 내부 구현을 이해하려는 개발자들에게 좋은 참고 자료
    • Mock 객체를 Set으로 관리하는 방법을 보여줌

이번 오픈소스 기여의 가치

이 PR은 기능 추가나 버그 수정이 아닌 테스트 추가라는 비교적 작은 기여입니다. 하지만 오픈소스 프로젝트에서 이런 기여도 매우 중요합니다: 테스트 커버리지는 프로젝트의 건강 지표입니다. 작은 기여로 시작하여 프로젝트에 익숙해질 수 있는 좋은 기회입니다. 또한, 코드 리뷰를 통해 다른 개발자들과 소통할 기회가 되기도 합니다. 


맺음말

이번 PR을 통해 Mockito 오픈소스 프로젝트에 작지만 의미 있는 기여를 할 수 있었습니다. 테스트 코드 작성, 코드 리뷰 피드백 반영, 그리고 오픈소스 기여 프로세스를 경험하는 소중한 기회였습니다. 이번 경험으로 제가 얻은 것은 다음과 같습니다. 

  • 코드 리뷰 피드백을 받고 개선하는 과정 
  • 작은 기여라도 프로젝트에 도움이 된다는 것을 확인한 것 

오픈소스 기여 경험을 통해 많은 것을 느꼈습니다. 작은 테스트 추가부터 시작하여 점진적으로 프로젝트에 기여할 수 있습니다. 관심 있는 오픈소스 프로젝트를 찾아 작은 기여부터 시작해보는 것은 개발자로서 좋은 도전인 것 같습니다. 다양한 오픈소스에 기여하는 경험으로 제품의 지속적 발전의 책임이 있는 개발자로서의 소양을 함양하고 가치있는 배움들을 얻을 수 있으리라 생각합니다. 


관련 참고 자료