Skip to content

Latest commit

Β 

History

History
376 lines (283 loc) Β· 17.8 KB

File metadata and controls

376 lines (283 loc) Β· 17.8 KB

item 39 : λͺ…λͺ… νŒ¨ν„΄λ³΄λ‹€ μ• λ„ˆν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜λΌ

1. λͺ…λͺ… νŒ¨ν„΄μ˜ 문제점

κ³Όκ±°μ—λŠ” ν…ŒμŠ€νŠΈ ν”„λ ˆμž„μ›Œν¬κ°€ λ©”μ„œλ“œμ˜ 이름을 νŠΉμ • νŒ¨ν„΄μœΌλ‘œ λͺ…λͺ…ν•˜μ—¬ νŠΉμ • μž‘μ—…μ„ μˆ˜ν–‰ν•˜λŠ” λ©”μ„œλ“œμž„μ„ κ΅¬λΆ„ν•˜κ³€ ν–ˆλ‹€. 예λ₯Ό λ“€μ–΄ JUnit 3λŠ” ν…ŒμŠ€νŠΈ λ©”μ„œλ“œ 이름이 test둜 μ‹œμž‘ν•΄μ•Όλ§Œ ν…ŒμŠ€νŠΈ ν”„λ ˆμž„μ›Œν¬μ—μ„œ ν•΄λ‹Ή λ©”μ„œλ“œλ₯Ό ν…ŒμŠ€νŠΈ λ©”μ„œλ“œλ‘œ μΈμ‹ν–ˆλ‹€.

κ·ΈλŸ¬λ‚˜ 이 λ°©μ‹μ—λŠ” λͺ‡ κ°€μ§€ λ¬Έμ œκ°€ μžˆλ‹€

  1. μ˜€νƒ€ 문제: tsetSafetyOverride처럼 μ˜€νƒ€κ°€ 있으면 JUnit은 ν…ŒμŠ€νŠΈ λ©”μ„œλ“œλ‘œ μΈμ‹ν•˜μ§€ μ•Šμ•„ λ¬΄μ‹œν•˜λ©°, ν…ŒμŠ€νŠΈκ°€ μ‹€νŒ¨ν•˜μ§€ μ•Šμ•˜μœΌλ―€λ‘œ κ°œλ°œμžλŠ” 이 ν…ŒμŠ€νŠΈκ°€ ν†΅κ³Όν–ˆλ‹€κ³  μ˜€ν•΄ν•  수 μžˆλ‹€.
  2. μ˜¬λ°”λ₯Έ μš”μ†Œμ— λŒ€ν•œ 보증 μ—†μŒ: λͺ…λͺ… νŒ¨ν„΄μœΌλ‘œλŠ” 잘λͺ»λœ μœ„μΉ˜(예: 클래슀 이름)에 μ„€μ •λœ νŒ¨ν„΄μ„ 검사할 수 μ—†λ‹€. 예λ₯Ό λ“€μ–΄ λ©”μ„œλ“œκ°€ μ•„λ‹Œ 클래슀 이름을 TestSafety둜 μ§€μ–΄ JUnitμ—κ²Œ μ€˜λ„, JUnit은 이 클래슀 이름에 관심이 μ—†μœΌλ©°, ν…ŒμŠ€νŠΈλŠ” μˆ˜ν–‰λ˜μ§€ μ•ŠμœΌλ©° Junit은 κ²½κ³  λ©”μ‹œμ§€μ‘°μ°¨ 좜λ ₯ν•˜μ§€ μ•ŠλŠ”λ‹€.
  3. λ§€κ°œλ³€μˆ˜ μ „λ‹¬μ˜ 어렀움: νŠΉμ • μ˜ˆμ™Έκ°€ λ°œμƒν•΄μ•Ό μ„±κ³΅ν•˜λŠ” ν…ŒμŠ€νŠΈλ₯Ό κ΅¬ν˜„ν•˜κ³ μž ν•  λ•Œ μ˜ˆμ™Έ νƒ€μž…μ„ λͺ…μ‹œμ μœΌλ‘œ μ§€μ •ν•˜λŠ” 방법이 μ—†κΈ° λ•Œλ¬Έμ—, ν…ŒμŠ€νŠΈ 이름에 μ˜ˆμ™Έ 이름을 ν¬ν•¨μ‹œν‚€λŠ” λ°©μ‹μœΌλ‘œ μš°νšŒν•˜λŠ” κ²½μš°κ°€ μžˆλ‹€. ν•˜μ§€λ§Œ μ΄λŸ¬ν•œ 방식은 가독성이 λ–¨μ–΄μ§€κ³  κΉ¨μ§€κΈ° μ‰¬μš°λ©°, μ»΄νŒŒμΌλŸ¬κ°€ λ©”μ„œλ“œ 이름에 ν¬ν•¨λœ λ¬Έμžμ—΄μ΄ μ˜ˆμ™Έμ™€ 관련이 μžˆλŠ”μ§€ 확인할 방법이 μ—†λ‹€.

μ˜ˆμ‹œ: JUnit 3의 ν…ŒμŠ€νŠΈ λ©”μ„œλ“œ λͺ…λͺ… νŒ¨ν„΄

public class MyTests {

    // ν…ŒμŠ€νŠΈ λ©”μ„œλ“œλŠ” 이름이 "test"둜 μ‹œμž‘ν•΄μ•Ό 함
    public void testAddition() {
        assert (1 + 1 == 2);
    }

    public void testSubtraction() {
        assert (2 - 1 == 1);
    }

    public void utilityMethod() {
        // 일반적인 μœ ν‹Έλ¦¬ν‹° λ©”μ„œλ“œλ‘œ, ν…ŒμŠ€νŠΈκ°€ μ•„λ‹˜
    }
}

μ• λ„ˆν…Œμ΄μ…˜μ€, μ΄λŸ¬ν•œ λͺ…λͺ… νŒ¨ν„΄μ˜ λ¬Έμ œλ“€μ„ ν•΄κ²°ν•΄μ£ΌλŠ” λ©‹μ§„ λŒ€μ•ˆμ΄λ‹€.

2. λͺ…λͺ…νŒ¨ν„΄μ˜ λŒ€μ•ˆ μ• λ„ˆν…Œμ΄μ…˜μ˜ μž₯점

import org.junit.Test;

public class MyTests {

    @Test
    public void addition() {
        assert (1 + 1 == 2);
    }

    @Test
    public void subtraction() {
        assert (2 - 1 == 1);
    }

    public void utilityMethod() {
        // 일반 λ©”μ„œλ“œ
    }
}

μ• λ„ˆν…Œμ΄μ…˜μ€ λ‹€μŒκ³Ό 같은 μž₯점을 μ œκ³΅ν•œλ‹€.

  • λͺ…ν™•ν•œ μ—­ν•  ꡬ뢄: μ• λ„ˆν…Œμ΄μ…˜μ€ λ©”μ„œλ“œ 이름에 μ˜μ‘΄ν•˜μ§€ μ•Šκ³ λ„ λͺ…ν™•ν•˜κ²Œ νŠΉμ • κΈ°λŠ₯을 μˆ˜ν–‰ν•˜λŠ” λ©”μ„œλ“œλ₯Ό ꡬ뢄할 수 있게 ν•΄μ€€λ‹€. λ©”μ„œλ“œ 이름이 μ•„λ‹Œ μ• λ„ˆν…Œμ΄μ…˜μ„ 톡해 역할을 ν‘œν˜„ν•  수 μžˆλ‹€.
  • μœ μ—°μ„±κ³Ό ν™•μž₯μ„±: 좔가적인 정보λ₯Ό μ• λ„ˆν…Œμ΄μ…˜μœΌλ‘œ μ €μž₯ν•  수 μžˆμ–΄ 더 λ§Žμ€ κΈ°λŠ₯을 μ‰½κ²Œ ν™•μž₯ν•  수 μžˆλ‹€.
  • μ»΄νŒŒμΌλŸ¬μ™€ λ„κ΅¬μ˜ 지원: μ• λ„ˆν…Œμ΄μ…˜μ„ 톡해 μ»΄νŒŒμΌλŸ¬κ°€ μΆ”κ°€ 검증을 μˆ˜ν–‰ν•  수 있으며, λ¦¬ν”Œλ ‰μ…˜μ„ 톡해 λ™μ μœΌλ‘œ λ©”μ„œλ“œλ₯Ό μ œμ–΄ν•  수 μžˆλ‹€.

예λ₯Ό λ“€μ–΄, @TestλΌλŠ” μ• λ„ˆν…Œμ΄μ…˜μ„ λ§Œλ“€κ³  이λ₯Ό νŠΉμ • λ©”μ„œλ“œμ— 뢙이면 κ·Έ λ©”μ„œλ“œκ°€ ν…ŒμŠ€νŠΈ λ©”μ„œλ“œμž„μ„ λͺ…ν™•ν•˜κ²Œ μ•Œ 수 μžˆλ‹€. 이λ₯Ό 톡해 ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό λ”μš± 읽기 μ‰½κ²Œ λ§Œλ“€κ³  μœ μ§€ 보수λ₯Ό μš©μ΄ν•˜κ²Œ ν•  수 μžˆλ‹€.

3. μ• λ„ˆν…Œμ΄μ…˜(Annotation)

TestλΌλŠ” μžλ™μœΌλ‘œ μˆ˜ν–‰λ˜λŠ” κ°„λ‹¨ν•œ ν…ŒμŠ€νŠΈμš© μ• λ„ˆν…Œμ΄μ…˜μœΌλ‘œ, μ˜ˆμ™Έκ°€ λ°œμƒν•˜λ©΄ ν…ŒμŠ€νŠΈλ₯Ό μ‹€νŒ¨λ‘œ μ²˜λ¦¬ν•œλ‹€.

1) 마컀 μ• λ„ˆν…Œμ΄μ…˜ νƒ€μž… μ„ μ–Έ

@Test μ• λ„ˆν…Œμ΄μ…˜μ€ λ©”μ„œλ“œκ°€ ν…ŒμŠ€νŠΈ λ©”μ„œλ“œμž„μ„ ν‘œμ‹œν•˜κΈ° μœ„ν•œ 마컀 μ• λ„ˆν…Œμ΄μ…˜μ΄λ‹€. 이 μ• λ„ˆν…Œμ΄μ…˜μ€ λ§€κ°œλ³€μˆ˜λ₯Ό κ°€μ§€μ§€ μ•ŠμœΌλ©°, 메타 μ• λ„ˆν…Œμ΄μ…˜μ„ 톡해 νŠΉμ • μ‘°κ±΄μ—μ„œλ§Œ μ‚¬μš©ν•  수 μžˆλ„λ‘ μ œν•œν•œλ‹€.

import java.lang.annotation.*;

/**
 * ν…ŒμŠ€νŠΈ λ©”μ„œλ“œμž„μ„ μ„ μ–Έν•˜λŠ” μ• λ„ˆν…Œμ΄μ…˜
 * λ§€κ°œλ³€μˆ˜ μ—†λŠ” 정적 λ©”μ„œλ“œ μ „μš©
 */
@Retention(RetentionPolicy.RUNTIME)  // λŸ°νƒ€μž„κΉŒμ§€ μœ μ§€
@Target(ElementType.METHOD)          // λ©”μ„œλ“œμ—λ§Œ 적용 κ°€λŠ₯
public @interface Test {
}

{% hint style="info" %} @Test μ—λ„ˆν…Œμ΄μ…˜ νƒ€μž… μ„ μ–Έ μžμ²΄μ— 두 κ°€μ§€μ˜ λ‹€λ₯Έ μ• λ„ˆν…Œμ΄μ…˜μ΄ 달렀 μžˆλŠ”λ°, 이λ₯Ό 메타 μ• λ„ˆν…Œμ΄μ…˜(meta-annotation)이라 ν•œλ‹€. {% endhint %}

  • @Retention(RetentionPolicy.RUNTIME): μ• λ„ˆν…Œμ΄μ…˜μ„ λŸ°νƒ€μž„μ—λ„ μœ μ§€ν•˜λ„λ‘ μ„€μ •ν•˜μ—¬ λ¦¬ν”Œλ ‰μ…˜μ„ 톡해 μ ‘κ·Όν•  수 있게 ν•œλ‹€. 메타 μ• λ„ˆν…Œμ΄μ…˜μ€ @Testκ°€ λŸ°νƒ€μž„μ—λ„ μœ μ§€λ˜μ–΄μ•Ό ν•œλ‹€λŠ” 의미이며, λ§Œμ•½ 이λ₯Ό μƒλž΅ν•˜λ©΄ ν…ŒμŠ€νŠΈ λ„κ΅¬λŠ” @Testλ₯Ό 인식할 수 μ—†κ²Œ λœλ‹€.
  • @Target(ElementType.METHOD): μ• λ„ˆν…Œμ΄μ…˜μ„ λ©”μ„œλ“œμ—λ§Œ μ‚¬μš©ν•  수 μžˆλ„λ‘ μ œν•œν•˜μ—¬ 잘λͺ»λœ μœ„μΉ˜μ—μ„œ μ‚¬μš©ν•˜μ§€ λͺ»ν•˜λ„둝 ν•œλ‹€.( 클래슀 μ„ μ–Έ, ν•„λ“œ μ„ μ–Έ λ“± λ‹€λ₯Έ ν”„λ‘œκ·Έλž¨ μš”μ†Œμ—λŠ” 달 수 μ—†μŒ )

이와 같이 λ§€κ°œλ³€μˆ˜κ°€ μ—†λŠ” μ• λ„ˆν…Œμ΄μ…˜μ„ @Test와 같은 μ• λ„ˆν…Œμ΄μ…˜μ„, "아무 λ§€κ°œλ³€μˆ˜ 없이 λ‹¨μˆœν•œ λŒ€μƒμ— λ§ˆν‚Ήν•œλ‹€"λΌλŠ” λœ»μ—μ„œλ§ˆμ»€ μ• λ„ˆν…Œμ΄μ…˜μ΄λΌ λΆ€λ₯΄λ©°, λ©”μ„œλ“œμ—λ§Œ 뢙일 수 있게 μ„€μ •ν•˜μ—¬ μ‚¬μš©μžκ°€ ν΄λž˜μŠ€λ‚˜ ν•„λ“œμ— 잘λͺ» μ μš©ν–ˆμ„ λ•Œ μ»΄νŒŒμΌλŸ¬κ°€ 였λ₯˜λ₯Ό λ°˜ν™˜ν•˜λ„λ‘ ν•  수 μžˆλ‹€.

즉, ν”„λ‘œκ·Έλž˜λ¨Έκ°€ Test이름에 μ˜€νƒ€λ₯Ό λ‚΄κ±°λ‚˜ λ©”μ„œλ“œ μ„ μ–Έ μ™Έμ˜ ν”„λ‘œκ·Έλž¨ μš”μ†Œμ— 달면 컴파일 였λ₯˜λ₯Ό λ‚΄μ€€λ‹€.

2) Sample ν΄λž˜μŠ€μ— @Test μ• λ„ˆν…Œμ΄μ…˜ μ‚¬μš© :마컀 μ• λ„ˆν…Œμ΄μ…˜μ„ μ‚¬μš©ν•œ ν”„λ‘œκ·Έλž¨ μ˜ˆμ‹œ

Sample ν΄λž˜μŠ€μ—μ„œ @Test μ• λ„ˆν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜μ—¬ λ©”μ„œλ“œλ₯Ό ν…ŒμŠ€νŠΈ λ©”μ„œλ“œλ‘œ μ§€μ •ν•  수 μžˆλ‹€.

public class Sample {
    @Test
    public static void m1() { }         // 성곡해야 ν•˜λŠ” ν…ŒμŠ€νŠΈ λ©”μ„œλ“œ
    public static void m2() { }         // ν…ŒμŠ€νŠΈ λ©”μ„œλ“œκ°€ μ•„λ‹˜
    @Test
    public static void m3() {           // μ‹€νŒ¨ν•΄μ•Ό ν•˜λŠ” ν…ŒμŠ€νŠΈ λ©”μ„œλ“œ
        throw new RuntimeException("μ‹€νŒ¨");
    }
    public static void m4() { }         // ν…ŒμŠ€νŠΈ λ©”μ„œλ“œκ°€ μ•„λ‹˜
    @Test
    public void m5() { }                // 잘λͺ»λœ μ‚¬μš© 예: 정적 λ©”μ„œλ“œκ°€ μ•„λ‹˜
}
  • m1, m3, m5λŠ” @Test μ• λ„ˆν…Œμ΄μ…˜μ΄ μ§€μ •λœ ν…ŒμŠ€νŠΈ λ©”μ„œλ“œ
  • m5λŠ” 정적 λ©”μ„œλ“œκ°€ μ•„λ‹ˆκΈ° λ•Œλ¬Έμ— @Test의 μš”κ΅¬μ‚¬ν•­μ— λ§žμ§€ μ•ŠλŠ”λ‹€.

@Test μ• λ„ˆν…Œμ΄μ…˜μ€ Sample 클래슀의 μ˜λ―Έμ— 직접적인 영ν–₯을 μ£Όμ§€ μ•Šκ³ , 단지 관심 μžˆλŠ” ν”„λ‘œκ·Έλž¨μ—κ²Œ μΆ”κ°€ 정보λ₯Ό μ œκ³΅ν•œλ‹€. 즉 λŒ€μƒ μ½”λ“œμ˜ μ˜λ―ΈλŠ” κ·ΈλŒ€λ‘œ λ‘” 채 κ·Έ μ• λ„ˆν…Œμ΄μ…˜μ— 관심 μžˆλŠ” λ„κ΅¬μ—μ„œ νŠΉλ³„ν•œ 처리λ₯Ό ν•  기회λ₯Ό μ£ΌλŠ” 것이닀.

3) μ• λ„ˆν…Œμ΄μ…˜μ„ ν™œμš©ν•œ ν…ŒμŠ€νŠΈ 도ꡬ κ΅¬ν˜„ : 마컀 μ• λ„ˆν…Œμ΄μ…˜μ„ μ²˜λ¦¬ν•˜λŠ” ν”„λ‘œκ·Έλž¨

@Test μ• λ„ˆν…Œμ΄μ…˜μ΄ 뢙은 λ©”μ„œλ“œλ§Œμ„ μ‹€ν–‰ν•˜λŠ” κ°„λ‹¨ν•œ ν…ŒμŠ€νŠΈ 도ꡬ

import java.lang.reflect.*;

public class RunTests {
    public static void main(String[] args) throws Exception {
        int tests = 0;
        int passed = 0;
        Class<?> testClass = Class.forName(args[0]);
        
        for (Method m : testClass.getDeclaredMethods()) {
            if (m.isAnnotationPresent(Test.class)) {
                tests++;
                try {
                    m.invoke(null);
                    passed++;
                } catch (InvocationTargetException wrappedExc) {
                    Throwable exc = wrappedExc.getCause();
                    System.out.println(m + " μ‹€νŒ¨: " + exc);
                } catch (Exception exc) {
                    System.out.println("잘λͺ» μ‚¬μš©ν•œ @Test: " + m);
                }
            }
        }
        System.out.printf("성곡: %d, μ‹€νŒ¨: %d%n", passed, tests - passed);
    }
}
  • m.isAnnotationPresent(Test.class): @Test μ• λ„ˆν…Œμ΄μ…˜μ΄ 뢙은 λ©”μ„œλ“œλ₯Ό μ°Ύμ•„ μ‹€ν–‰ν•œλ‹€.
  • μ˜ˆμ™Έ 처리: InvocationTargetException으둜 감싸진 μ˜ˆμ™ΈλŠ” getCause()λ₯Ό 톡해 원본 μ˜ˆμ™Έλ₯Ό 좜λ ₯ν•œλ‹€.
  • κ²°κ³Ό 좜λ ₯: 총 ν…ŒμŠ€νŠΈ κ°œμˆ˜μ™€ 성곡/μ‹€νŒ¨ 개수λ₯Ό 좜λ ₯ν•˜μ—¬ ν…ŒμŠ€νŠΈμ˜ κ²°κ³Όλ₯Ό μš”μ•½ν•œλ‹€.

이 ν…ŒμŠ€νŠΈ λŸ¬λ„ˆλŠ” λͺ…λ Ήμ€„λ‘œλΆ€ν„° μ™„μ „ μ •κ·œν™”λœ 클래슀 이름을 λ°›μ•„, ν΄λž˜μŠ€μ—μ„œ @Test μ• λ„ˆν…Œμ΄μ…˜μ΄ 달린 λ©”μ„œλ“œλ₯Ό μ°Ύμ•„ μ°¨λ‘€λ‘œ ν˜ΈμΆœν•œλ‹€. 그리고 μ• λ„ˆν…Œμ΄μ…˜μ„ 잘λͺ» μ‚¬μš©ν•΄ μ˜ˆμ™Έκ°€ λ°œμƒν•œλ‹€λ©΄ 였λ₯˜ λ©”μ„Έμ§€λ₯Ό 좜λ ₯ν•œλ‹€.

4) νŠΉμ • μ˜ˆμ™Έλ₯Ό κΈ°λŒ€ν•˜λŠ” μ• λ„ˆν…Œμ΄μ…˜ : λ§€κ°œλ³€μˆ˜λ₯Ό λ°›λŠ” μ• λ„ˆν…Œμ΄μ…˜ νƒ€μž…

νŠΉμ • μ˜ˆμ™Έλ₯Ό λ°œμƒν•΄μ•Όλ§Œ μ„±κ³΅ν•˜λŠ” ν…ŒμŠ€νŠΈκ°€ ν•„μš”ν•œ 경우, @ExceptionTest μ• λ„ˆν…Œμ΄μ…˜μ„ μ‚¬μš©ν•΄ μ˜ˆμ™Έλ₯Ό λͺ…μ‹œν•  수 μžˆλ‹€.

λ§€κ°œλ³€μˆ˜λ₯Ό ν•˜λ‚˜λ₯Ό λ°›λŠ” μ• λ„ˆν…Œμ΄μ…˜ νƒ€μž…, @ExceptionTest μ• λ„ˆν…Œμ΄μ…˜ νƒ€μž… μ •μ˜

import java.lang.annotation.*;

/**
 * μ§€μ •ν•œ μ˜ˆμ™Έκ°€ λ°œμƒν•΄μ•Ό μ„±κ³΅ν•˜λŠ” ν…ŒμŠ€νŠΈ λ©”μ„œλ“œλ₯Ό μœ„ν•œ μ• λ„ˆν…Œμ΄μ…˜.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
    Class<? extends Throwable> value();  // λ§€κ°œλ³€μˆ˜λ‘œ μ˜ˆμ™Έ νƒ€μž…μ„ λ°›μŒ
}

@ExceptionTestλŠ” νŠΉμ • μ˜ˆμ™Έ νƒ€μž…μ„ λ§€κ°œλ³€μˆ˜λ‘œ λ°›μ•„, ν•΄λ‹Ή μ˜ˆμ™Έκ°€ λ°œμƒν•΄μ•Όλ§Œ ν…ŒμŠ€νŠΈκ°€ μ„±κ³΅ν•˜λŠ”μ§€ κ²€μ‚¬ν•œλ‹€.

예제: @ExceptionTest μ• λ„ˆν…Œμ΄μ…˜ μ‚¬μš©

public class Sample2 {
    @ExceptionTest(ArithmeticException.class)
    public static void m1() {  // 성곡해야 ν•˜λŠ” ν…ŒμŠ€νŠΈ
        int i = 0;
        i = i / i;
    }

    @ExceptionTest(ArithmeticException.class)
    public static void m2() {  // μ‹€νŒ¨: λ‹€λ₯Έ μ˜ˆμ™Έ λ°œμƒ
        int[] a = new int[0];
        int i = a[1];
    }

    @ExceptionTest(ArithmeticException.class)
    public static void m3() {  // μ‹€νŒ¨: μ˜ˆμ™Έκ°€ λ°œμƒν•˜μ§€ μ•ŠμŒ
    }
}

5) ν…ŒμŠ€νŠΈ 도ꡬ μˆ˜μ •

이제 @ExceptionTestλ₯Ό ν™œμš©ν•΄ ν…ŒμŠ€νŠΈ λ©”μ„œλ“œκ°€ μ˜¬λ°”λ₯Έ μ˜ˆμ™Έλ₯Ό λ˜μ§€λŠ”μ§€ ν™•μΈν•˜λŠ” λ‘œμ§μ„ μΆ”κ°€ν•œλ‹€. μ•žμ˜ μ½”λ“œμ™€ ν•œ κ°€μ§€ 차이라면, 이 μ½”λ“œλŠ” μ• λ„ˆν…Œμ΄μ…˜ λ§€κ°œλ³€μˆ˜μ˜ 값을 μΆ”μΆœν•˜μ—¬ ν…ŒμŠ€νŠΈ λ©”μ„œλ“œκ°€ μ˜¬λ°”λ₯Έ μ˜ˆμ™Έλ₯Ό λ˜μ§€λŠ”μ§€ ν™•μΈν•˜λŠ”λ° μ‚¬μš©ν•œλ‹€.

예제: 마컀 μ• λ„ˆν…Œμ΄μ…˜κ³Ό λ§€κ°œλ³€μˆ˜ ν•˜λ‚˜μ§œλ¦¬ μ• λ„ˆνƒœμ΄μ…˜μ„ μ²˜λ¦¬ν•˜λŠ” ν”„λ‘œκ·Έλž¨

if (m.isAnnotationPresent(ExceptionTest.class)) {
    tests++;
    try {
        m.invoke(null);  // λ©”μ„œλ“œλ₯Ό μ‹€ν–‰
        System.out.printf("ν…ŒμŠ€νŠΈ %s μ‹€νŒ¨: μ˜ˆμ™Έλ₯Ό λ˜μ§€μ§€ μ•ŠμŒ%n", m);
    } catch (InvocationTargetException wrappedEx) {
        Throwable exc = wrappedEx.getCause();
        Class<? extends Throwable> excType = m.getAnnotation(ExceptionTest.class).value();
        if (excType.isInstance(exc)) {  // μ˜ˆμ™Έ νƒ€μž… 일치 μ—¬λΆ€ 확인
            passed++;
        } else {
            System.out.printf("ν…ŒμŠ€νŠΈ %s μ‹€νŒ¨: κΈ°λŒ€ν•œ μ˜ˆμ™Έ %s, λ°œμƒν•œ μ˜ˆμ™Έ %s%n", m, excType.getName(), exc);
        }
    } catch (Exception exc) {
        System.out.println("잘λͺ» μ‚¬μš©ν•œ @ExceptionTest: " + m);
    }
}
  • m.getAnnotation(ExceptionTest.class).value(): @ExceptionTest μ• λ„ˆν…Œμ΄μ…˜μ—μ„œ μ§€μ •ν•œ μ˜ˆμ™Έ νƒ€μž…μ„ κ°€μ Έμ˜¨λ‹€.
  • excType.isInstance(exc): λ°œμƒν•œ μ˜ˆμ™Έκ°€ κΈ°λŒ€ν•œ μ˜ˆμ™Έ νƒ€μž…κ³Ό μΌμΉ˜ν•˜λŠ”μ§€ ν™•μΈν•œλ‹€.

6) λ‹€μˆ˜μ˜ μ˜ˆμ™Έλ₯Ό λͺ…μ‹œν•˜λŠ” μ• λ„ˆν…Œμ΄μ…˜ (λ°°μ—΄ λ§€κ°œλ³€μˆ˜)

@ExceptionTest에 λ°°μ—΄ λ§€κ°œλ³€μˆ˜λ₯Ό μ‚¬μš©ν•˜μ—¬, λ‹€μˆ˜μ˜ μ˜ˆμ™Έ 쀑 ν•˜λ‚˜λ§Œ λ°œμƒν•΄λ„ ν…ŒμŠ€νŠΈκ°€ μ„±κ³΅ν•˜λ„λ‘ μ„€μ •ν•  수 μžˆλ‹€.

@ExceptionTest μ• λ„ˆν…Œμ΄μ…˜μ˜ λ§€κ°œλ³€μˆ˜ νƒ€μž…μ„ Class 객체의 λ°°μ—΄λ‘œ μˆ˜μ •ν•˜λ©΄ λœλ‹€.

예제: λ°°μ—΄ λ§€κ°œλ³€μˆ˜λ₯Ό λ°›λŠ” μ• λ„ˆν…Œμ΄μ…˜ νƒ€μž…

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
    Class<? extends Throwable>[] value();  // μ—¬λŸ¬ μ˜ˆμ™Έ νƒ€μž…μ„ λ°°μ—΄λ‘œ λ°›μŒ
}

예제: λ°°μ—΄ λ§€κ°œλ³€μˆ˜λ₯Ό μ‚¬μš©ν•˜λŠ” @ExceptionTest μ• λ„ˆν…Œμ΄μ…˜ μ‚¬μš© 예

@ExceptionTest({ IndexOutOfBoundsException.class, NullPointerException.class })
public static void doublyBad() {
    List<String> list = new ArrayList<>();
    // μžλ°” API λͺ…세에 λ”°λ₯΄λ©΄ λ‹€μŒ λ©”μ„œλ“œλŠ” IndexOutOfBoundsExceptionμ΄λ‚˜
     // NullPointerException을 던질 수 μžˆλ‹€.
    list.addAll(5, null);  
}

예제: λ°°μ—΄ λ§€κ°œλ³€μˆ˜λ₯Ό μ²˜λ¦¬ν•˜λŠ” ν…ŒμŠ€νŠΈ 도ꡬ μ½”λ“œ

jif (m.isAnnotationPresent(ExceptionTest.class)) {
    tests++;
    try {
        m.invoke(null);
        System.out.printf("ν…ŒμŠ€νŠΈ %s μ‹€νŒ¨: μ˜ˆμ™Έλ₯Ό λ˜μ§€μ§€ μ•ŠμŒ%n", m);
    } catch (Throwable wrappedExc) {
        Throwable exc = wrappedExc.getCause();
        int oldPassed = passed;
        Class<? extends Throwable>[] excTypes = m.getAnnotation(ExceptionTest.class).value();
        for (Class<? extends Throwable> excType : excTypes) {
            if (excType.isInstance(exc)) {  // μ˜ˆμ™Έ νƒ€μž… 쀑 μΌμΉ˜ν•˜λŠ” 것이 있으면 성곡
                passed++;
                break;
            }
        }
        if (passed == oldPassed)
            System.out.printf("ν…ŒμŠ€νŠΈ %s μ‹€νŒ¨: %s %n", m, exc);
    }
}

7) λ‹€μˆ˜μ˜ μ˜ˆμ™Έλ₯Ό λͺ…μ‹œν•˜λŠ” μ• λ„ˆν…Œμ΄μ…˜ (@Repeatable μ‚¬μš©)

ν•˜μ§€λ§Œ μœ„μ˜ μ½”λ“œλ₯Ό 더 κ°„λ‹¨ν•˜κ²Œ κ°œμ„ ν•˜κ³  μ‹Άλ‹€λ©΄, μžλ°” 8μ—μ„œλŠ” μ—¬λŸ¬κ°œμ˜ 값을 λ°›λŠ” μ• λ„ˆν…Œμ΄μ…˜μ„, λ°°μ—΄ λ§€κ°œλ³€μˆ˜λ₯Ό μ‚¬μš©ν•˜λŠ” λŒ€μ‹  @Repeatable 메타 μ• λ„ˆν…Œμ΄μ…˜μ„ λ‹€λŠ” 방식을 μ„ νƒν•˜μ—¬ μ½”λ“œ 가독성을 높일 수 μžˆλ‹€.

@Repeatableλ₯Ό 단 μ• λ„ˆν…Œμ΄μ…˜μ€ ν•˜λ‚˜μ˜ ν”„λ‘œκ·Έλž¨ μš”μ†Œμ— μ—¬λŸ¬ 번 달 수 μžˆλ‹€.

주의 사항

  1. @Repeatable을 μ‚¬μš©ν•˜λ €λ©΄ μ»¨ν…Œμ΄λ„ˆ μ• λ„ˆν…Œμ΄μ…˜μ„ λ³„λ„λ‘œ μ •μ˜ν•΄μ•Ό ν•œλ‹€.
  2. @Repeatable에 μ»¨ν…Œμ΄λ„ˆ μ• λ„ˆν…Œμ΄μ…˜ 클래슀λ₯Ό 전달해야 ν•œλ‹€.
  3. μ»¨ν…Œμ΄λ„ˆ μ• λ„ˆν…Œμ΄μ…˜μ€ μ• λ„ˆν…Œμ΄μ…˜ 배열을 λ°˜ν™˜ν•˜λŠ” value λ©”μ„œλ“œλ₯Ό 포함해야 ν•œλ‹€.
  4. μ»¨ν…Œμ΄λ„ˆ μ• λ„ˆν…Œμ΄μ…˜ νƒ€μž…μ—λŠ” μ μ ˆν•œ 보쑴 μ •μ±…(@Retention)κ³Ό 적용 λŒ€μƒ(@Target)을 λͺ…μ‹œν•΄μ•Ό ν•œλ‹€. κ·Έλ ‡μ§€ μ•ŠμœΌλ©΄ 컴파일이 λ˜μ§€ μ•ŠλŠ”λ‹€.

예제: 반볡 κ°€λŠ₯ν•œ μ• λ„ˆν…Œμ΄μ…˜ νƒ€μž…κ³Ό μ»¨ν…Œμ΄λ„ˆ μ• λ„ˆν…Œμ΄μ…˜ μ •μ˜

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(ExceptionTestContainer.class)  // μ»¨ν…Œμ΄λ„ˆ μ• λ„ˆν…Œμ΄μ…˜ 클래슀
public @interface ExceptionTest {
    Class<? extends Throwable> value();
}

// μ»¨ν…Œμ΄λ„ˆ μ• λ„ˆν…Œμ΄μ…˜ μ •μ˜
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTestContainer {
    ExceptionTest[] value();
}

반볡 κ°€λŠ₯ μ• ν„°λ„€μ΄μ…˜μ€, μ²˜λ¦¬ν•  λ•Œλ„ 주의 사항이 μ‘΄μž¬ν•œλ‹€.

λ¨Όμ €, μ• λ„ˆν…Œμ΄μ…˜μ„ μ—¬λŸ¬κ°œ 달면 ν•˜λ‚˜λ§Œ λ‹¬μ•˜μ„ λ•Œμ™€ κ΅¬λΆ„ν•˜κΈ° μœ„ν•΄ ν•΄λ‹Ή 'μ»¨ν…Œμ΄λ„ˆ' μ• λ„ˆν…Œμ΄μ…˜ νƒ€μž…μ΄ 적용되기 λ•Œλ¬Έμ— m.isAnnotationPresent() μ—μ„œ λ‘˜μ„ λͺ…ν™•νžˆ κ΅¬λΆ„ν•˜κ³  μžˆλŠ” 것을 λ³Ό 수 μžˆλ‹€.

ν•˜μ§€λ§Œ, ν•΄λ‹Ή λ©”μ„œλ“œλ‘œ 반볡 κ°€λŠ₯ μ• λ„ˆν…Œμ΄μ…˜μ΄ λ‹¬λ ΈλŠ”μ§€ κ²€μ‚¬ν•œλ‹€λ©΄ 검사에 μ‹€νŒ¨ν•  것이고(μ• λ„ˆν…Œμ΄μ…˜μ„ μ—¬λŸ¬ 번 단 λ©”μ„œλ“œλ“€μ„ λ¬΄μ‹œ) μ»¨ν…Œμ΄λ„ˆ μ• λ„ˆν…Œμ΄μ…˜μ΄ λ‹¬λ ΈλŠ”μ§€λ§Œ κ²€μ‚¬ν•˜μ—¬λ„ 반볡 κ°€λŠ₯ μ• λ„ˆν…Œμ΄μ…˜μ„ ν•œλ²ˆλ§Œ 단 λ©”μ„œλ“œλ₯Ό λ¬΄μ‹œν•˜κ³  μ§€λ‚˜μΉ˜κΈ° λ•Œλ¬Έμ— λ‘˜μ„ λ”°λ‘œλ”°λ‘œ 확인해야 ν•œλ‹€.

예제: 반볡 κ°€λŠ₯ν•œ μ• λ„ˆν…Œμ΄μ…˜ μ‚¬μš© 예

@ExceptionTest(IndexOutOfBoundsException.class)
@ExceptionTest(NullPointerException.class)
public static void doublyBad() {
    List<String> list = new ArrayList<>();
    list.addAll(5, null);  // μ˜ˆμ™Έ λ°œμƒ κ°€λŠ₯
}

예제: 반볡 κ°€λŠ₯ν•œ μ• λ„ˆν…Œμ΄μ…˜μ„ μ²˜λ¦¬ν•˜λŠ” ν…ŒμŠ€νŠΈ 도ꡬ μ½”λ“œ

if (m.isAnnotationPresent(ExceptionTest.class) || m.isAnnotationPresent(ExceptionTestContainer.class)) {
    tests++;
    try {
        m.invoke(null);
        System.out.printf("ν…ŒμŠ€νŠΈ %s μ‹€νŒ¨: μ˜ˆμ™Έλ₯Ό λ˜μ§€μ§€ μ•ŠμŒ%n", m);
    } catch (Throwable wrappedExc) {
        Throwable exc = wrappedExc.getCause();
        int oldPassed = passed;
        ExceptionTest[] excTests = m.getAnnotationsByType(ExceptionTest.class);
        for (ExceptionTest excTest : excTests) {
            if (excTest.value().isInstance(exc)) {
                passed++;
                break;
            }
        }
        if (passed == oldPassed)
            System.out.printf("ν…ŒμŠ€νŠΈ %s μ‹€νŒ¨: %s %n", m, exc);
    }
}

@Repeatable을 μ‚¬μš©ν•œ 반볡 κ°€λŠ₯ν•œ μ• λ„ˆν…Œμ΄μ…˜μ„ μ²˜λ¦¬ν•  λ•ŒλŠ”, getAnnotationsByType λ©”μ„œλ“œλ₯Ό 톡해 κ°œλ³„ μ• λ„ˆν…Œμ΄μ…˜μ„ λ°°μ—΄ ν˜•νƒœλ‘œ κ°€μ Έμ˜¬ 수 μžˆλ‹€.

πŸ“š 핡심 정리

μ• λ„ˆν…Œμ΄μ…˜μœΌλ‘œ ν•  수 μžˆλŠ” 일을 λͺ…λͺ… νŒ¨ν„΄μœΌλ‘œ μ²˜λ¦¬ν•  μ΄μœ λŠ” μ—†μœΌλ©°, μžλ°” ν”„λ‘œκ·Έλž˜λ¨ΈλΌλ©΄ μ˜ˆμ™Έ 없이 μžλ°”κ°€ μ œκ³΅ν•˜λŠ” μ• λ„ˆν…Œμ΄μ…˜ νƒ€μž…λ“€μ€ μ‚¬μš©ν•΄μ•Ό ν•œλ‹€.(μ•„μ΄ν…œ 40, 27)

μ• λ„ˆν…Œμ΄μ…˜μ„ 톡해 μ½”λ“œλ₯Ό λ”μš± 읽기 쉽고 μœ μ§€ λ³΄μˆ˜ν•˜κΈ° μš©μ΄ν•˜κ²Œ λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€.

μ°Έκ³