π λͺ λͺ ν¨ν΄μ λ¬Έμ μ
μ ν΅μ μΌλ‘ λꡬλ νλ μμν¬κ° νΉλ³ν λ€λ€μΌ ν νλ‘κ·Έλ¨ μμμλ λ± κ΅¬λΆλλ λͺ λͺ ν¨ν΄μ μ μ©ν΄ μλ€.
μλ₯Ό λ€μ΄, ν μ€νΈ νλ μμν¬μΈ JUnitμ λ²μ 3κΉμ§ ν μ€νΈ λ©μλ μ΄λ¦μ testλ‘ μμνκ²λ νλ€. ν¨κ³Όμ μΈ λ°©λ²μ΄μ§λ§ μλμ κ°μ 3κ°μ§ ν° λ¨μ μ΄ μλ€.
- μ€νκ° λλ©΄ μ λλ€.
μ€μλ‘ μ΄λ¦μ tsetSafetyOverrideλ‘ μ§μΌλ©΄ μ΄ λ©μλλ 무μλλ€. - μ¬λ°λ₯Έ νλ‘κ·Έλ¨ μμμμλ§ μ¬μ©λλ¦¬λΌ λ³΄μ¦ν λ°©λ²μ΄ μλ€.
ν΄λμ€ μ΄λ¦μ TestSafetyMechanismsλ‘ μ§μ΄ μ΄ ν΄λμ€μ μ μλ ν μ€νΈ λ©μλλ€μ μννκΈΈ λ°λΌμ§λ§ JUnitμ ν΄λμ€ μ΄λ¦μλ κ΄μ¬μ΄ μλ€. - νλ‘κ·Έλ¨ μμλ₯Ό 맀κ°λ³μλ‘ μ λ¬ν λ§λ
ν λ°©λ²μ΄ μλ€.
νΉμ μμΈλ₯Ό λμ ΈμΌλ§ μ±κ³΅νλ ν μ€νΈκ° μλ€κ³ ν λ, κΈ°λνλ μμΈ νμ μ ν μ€νΈμ 맀κ°λ³μλ‘ μ λ¬ν μ μλ€.
π μμ 1 - λ§μ»€(marker) μ λν μ΄μ
μ λν μ΄μ μ μμ λ¬Έμ λ₯Ό ν΄κ²°ν΄ μ£Όλλ°, JUnitλ λ²μ 4λΆν° μ λ©΄ λμ νμλ€. μ§μ μ μν μμ ν μ€νΈ νλ μμν¬λ₯Ό μ΄μ©νμ¬ μ λν μ΄μ μ λμ λ°©μμ μ΄ν΄λ³΄μ.
/**
* ν
μ€νΈ λ©μλμμ μ μΈνλ μ λν
μ΄μ
μ΄λ€.
* 맀κ°λ³μ μλ μ μ λ©μλ μ μ©μ΄λ€.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
}
@Test μ λν μ΄μ μ μΈ μ체μλ λ κ°μ§μ λ€λ₯Έ μ λν μ΄μ μ΄ λ¬λ € μλλ°, μ΄μ²λΌ μ λν μ΄μ μ μΈμ λ€λ₯Έ μ λν μ΄μ μ λ©νμ λν μ΄μ (meta-annotation)μ΄λΌ νλ€.
2κ°μ§ λ©νμ λν μ΄μ μ λ€μμ μλ―Ένλ€.
- @Retention(RetentionPolicy.RUNTIME)
@Testκ° λ°νμμλ μ μ§λμ΄μΌ νλ€. - @Target(ElementType.METHOD)
@Testκ° λ°λμ λ©μλ μ μΈμμλ§ μ¬μ©λΌμΌ νλ€.
@Test μ λν μ΄μ μ λ§μ»€(marker) μ λν μ΄μ μ΄λΌ νλ€. μ΄ μ λν μ΄μ μ μ¬μ©νλ©΄ νλ‘κ·Έλλ¨Έκ° Test μ΄λ¦μ μ€νλ₯Ό λ΄κ±°λ λ©μλ μ μΈ μΈμ νλ‘κ·Έλ¨ μμμ λ¬λ©΄ μ»΄νμΌ μ€λ₯λ₯Ό λ΄μ€λ€.
μ£ΌμμΌλ‘ "맀κ°λ³μ μλ μ μ λ©μλ μ μ©μ΄λ€"λΌκ³ μ°μ¬μλ€. μ΄ μ μ½μ μ»΄νμΌλ¬κ° κ°μ ν μ μμ΄μ, μ»΄νμΌμ μ λκ² μ§λ§ ν
μ€νΈ λꡬλ₯Ό μ€νν λ λ¬Έμ κ° λλ€.
μ»΄νμΌλ¬κ° κ°μ νλλ‘ νλ €λ©΄ javax.annotation.processing API λ¬Έμλ₯Ό μ°Έκ³ μ μ ν μ λν
μ΄μ
μ²λ¦¬κΈ°λ₯Ό μ§μ ꡬνν΄μΌ νλ€.
@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() {
// μλͺ» μ¬μ©ν μ: μ μ λ©μλκ° μλλ€.
}
public static void m6() { }
@Test
public static void m7() {
// μ€ν¨ν΄μΌ νλ€.
throw new RuntimeException("μ€ν¨");
}
public static void m8() { }
}
public static void main(String[] args) throws Exception {
int tests = 0;
int passed = 0;
Class<?> testClass = Class.forName("com.example.demo.item39.Sample");
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);
// μΆλ ₯ κ²°κ³Ό
// public static void com.example.demo.item39.Sample.m3() μ€ν¨: java.lang.RuntimeException: μ€ν¨
// public static void com.example.demo.item39.Sample.m7() μ€ν¨: java.lang.RuntimeException: μ€ν¨
// μλͺ» μ¬μ©ν @Test: public void com.example.demo.item39.Sample.m5()
// μ±κ³΅: 1, μ€ν¨: 3
}
InvocationTargetExceptionμ m3()κ³Ό m7()μμ λ°μνλ€.
@Test μ λν μ΄μ μ μ λͺ» μ¬μ©ν κ²½μ° InvocationTargetException μΈμ μμΈκ° λ°μνλλ°, m5()μμ λ°μνλ€.
π μμ 2 - 맀κ°λ³μ νλλ₯Ό λ°λ μ λν μ΄μ
νΉμ μμΈλ₯Ό λμ ΈμΌλ§ μ±κ³΅νλ ν μ€νΈλ₯Ό μ΄ν΄λ³΄μ.
/**
* λͺ
μν μμΈλ₯Ό λμ μΌλ§ μ±κ³΅νλ ν
μ€νΈ λ©μλμ© μ λν
μ΄μ
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
Class<? extends Throwable> value();
}
μ΄ μ λν μ΄μ μ 맀κ°λ³μ νμ μ Class<? extends Throwable>μ΄λ€.
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() {
// μ€ν¨ν΄μΌ νλ€. (μμΈκ° λ°μνμ§ μμ)
}
}
public static void main(String[] args) throws Exception {
int tests = 0;
int passed = 0;
Class<?> testClass = Class.forName("com.example.demo.item39.Sample2");
for(Method m : testClass.getDeclaredMethods()) {
if(m.isAnnotationPresent(ExceptionTest.class)) {
tests++;
try {
m.invoke(null);
System.out.printf("ν
μ€νΈ %s μ€ν¨: μμΈλ₯Ό λμ§μ§ μμ%n", m);
} catch (InvocationTargetException wrappedExc) {
Throwable exc = wrappedExc.getCause();
Class<? extends Throwable> excType =
m.getAnnotation(ExceptionTest.class).value();
if(excType.isInstance(exc)) {
passed++;
} else {
System.out.printf(
"ν
μ€νΈ %s μ€ν¨: κΈ°λν μμΈ %s,%n λ°μν μμΈ %s%n",
m, excType.getName(), exc);
}
} catch (Exception exc) {
System.out.println("μλͺ» μ¬μ©ν @ExceptionTest: " + m);
}
}
}
System.out.printf("μ±κ³΅: %d, μ€ν¨: %d%n",
passed, tests - passed);
// μΆλ ₯ κ²°κ³Ό
// ν
μ€νΈ public static void com.example.demo.item39.Sample2.m2() μ€ν¨: κΈ°λν μμΈ java.lang.ArithmeticException,
// λ°μν μμΈ java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 0
// ν
μ€νΈ public static void com.example.demo.item39.Sample2.m3() μ€ν¨: μμΈλ₯Ό λμ§μ§ μμ
// μ±κ³΅: 1, μ€ν¨: 2
}
μ΄ μ½λλ μ λν μ΄μ 맀κ°λ³μμ κ°μ μΆμΆνμ¬ ν μ€νΈ λ©μλκ° μ¬λ°λ₯Έ μμΈλ₯Ό λμ§λμ§ νμΈνλ€. Sample2 ν΄λμ€μ μ£Όμμμ νμΈν μ μλ―μ΄ 1κ°κ° μ±κ³΅νκ³ , 2κ°κ° μ€ν¨νλ€.
π μμ 3 - λ°°μ΄ λ§€κ°λ³μλ₯Ό λ°λ μ λν μ΄μ νμ
π 1. λ°°μ΄μ μ΄μ©νλ λ°©λ²
μ§κΈκΉμ§ μμΈ 1κ°λ₯Ό λ°λ μ λν μ΄μ μ μ΄ν΄λ³΄μλ€. μλ μ λν μ΄μ κ³Ό κ°μ΄ 'μμΈλ₯Ό μ¬λ¬ κ° λͺ μνκ³ κ·Έμ€ νλκ° λ°μνλ©΄ μ±κ³΅'νκ² λ§λ€ μλ μλ€.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
Class<? extends Throwable>[] value();
}
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() {
// μ€ν¨ν΄μΌ νλ€. (μμΈκ° λ°μνμ§ μμ)
}
// λ°°μ΄ λ§€κ°λ³μ
@ExceptionTest({ IndexOutOfBoundsException.class,
NullPointerException.class})
public static void doubleBad() {
// μ±κ³΅ν΄μΌ νλ€.
List<String> list = new ArrayList<>();
// μλ° API λͺ
μΈμ λ°λ₯΄λ©΄ λ€μ λ©μλλ IndexOutOfBoundsExceptionμ΄λ
// NullPointerExceptionμ λμ§ μ μλ€.
list.addAll(5, null);
}
}
public static void main(String[] args) throws Exception {
int tests = 0;
int passed = 0;
Class<?> testClass = Class.forName("com.example.demo.item39.Sample2");
for(Method m : testClass.getDeclaredMethods()) {
if(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);
}
}
}
}
System.out.printf("μ±κ³΅: %d, μ€ν¨: %d%n",
passed, tests - passed);
// ν
μ€νΈ public static void com.example.demo.item39.Sample2.m2() μ€ν¨: java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 0
// ν
μ€νΈ public static void com.example.demo.item39.Sample2.m3() μ€ν¨: μμΈλ₯Ό λμ§μ§ μμ
// μ±κ³΅: 2, μ€ν¨: 2
}
μμκ° μ¬λΏμΈ λ°°μ΄μ μ§μ ν λλ μμ κ°μ΄ μμλ€μ μ€κ΄νΈλ‘ κ°μΈκ³ μΌνλ‘ κ΅¬λΆν΄μ£ΌκΈ°λ§ νλ©΄ λλ€.
π 2. @Repeatable λ©νμ λν μ΄μ μ μ΄μ©νλ λ°©λ²
μλ° 8μμλ μ¬λ¬ κ°μ κ°μ λ°λ μ λν μ΄μ μ λ°°μ΄ λ§€κ°λ³μ λμ @Repeatable λ©νμ λν μ΄μ μ μ΄μ©νμ¬ κ΅¬νν μ μλ€. νλμ νλ‘κ·Έλ¨ μμμ @Repeatableμ λ¨ μ λν μ΄μ μ μ¬λ¬ κ° λ¬ μ μλλ‘ νλ κ²μ΄λ€.
@Repeatableμ κ°μ§ μ λν μ΄μ μ ꡬνν λλ μλμ λ΄μ©μ μ£Όμν΄μ λ§λ€μ΄μΌ νλ€.
- @Repeatableμ λ¨ μ λν μ΄μ μ λ°ννλ '컨ν μ΄λ μ λν μ΄μ 'μ νλ λ μ μνκ³ , @Repeatableμ μ΄ μ»¨ν μ΄λ μ λν μ΄μ μ class κ°μ²΄λ₯Ό 맀κ°λ³μλ‘ μ λ¬ν΄μΌ νλ€.
- 컨ν μ΄λ μ λν μ΄μ μ λ΄λΆ μ λν μ΄μ νμ μ λ°°μ΄μ λ°ννλ value λ©μλλ₯Ό μ μν΄μΌ νλ€.
- 컨ν μ΄λ μ λν μ΄μ νμ μλ μ μ ν 보쑴 μ μ± (@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();
}
isAnnotationPresentλ‘ μ»¨ν μ΄λ μ λν μ΄μ μ΄ λ¬λ Έλμ§ κ²μ¬νλ€λ©΄ λ°λ³΅ κ°λ₯ μ λν μ΄μ μ ν λ²λ§ λ¨ λ©μλλ 무μλλ€. κ·Έλμ getAnnotationByType λ©μλλ₯Ό μ΄μ©νμ¬ λ°λ³΅ κ°λ₯ μ λν μ΄μ μ μμ μκ΄μμ΄ λͺ¨λ κ²μ¬νλλ‘ ν΄μΌ νλ€.
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() {
// μ€ν¨ν΄μΌ νλ€. (μμΈκ° λ°μνμ§ μμ)
}
// λ°λ³΅ κ°λ₯ν μλν
μ΄μ
μ¬μ©
@ExceptionTest(IndexOutOfBoundsException.class)
@ExceptionTest(NullPointerException.class)
public static void doubleBad() {
// μ±κ³΅ν΄μΌ νλ€.
List<String> list = new ArrayList<>();
// μλ° API λͺ
μΈμ λ°λ₯΄λ©΄ λ€μ λ©μλλ IndexOutOfBoundsExceptionμ΄λ
// NullPointerExceptionμ λμ§ μ μλ€.
list.addAll(5, null);
}
}
public static void main(String[] args) throws Exception {
int tests = 0;
int passed = 0;
Class<?> testClass = Class.forName("com.example.demo.item39.Sample2");
for(Method m : testClass.getDeclaredMethods()) {
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);
}
}
}
}
System.out.printf("μ±κ³΅: %d, μ€ν¨: %d%n",
passed, tests - passed);
// μΆλ ₯ κ²°κ³Ό
// ν
μ€νΈ public static void com.example.demo.item39.Sample2.m2() μ€ν¨: java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 0
// ν
μ€νΈ public static void com.example.demo.item39.Sample2.m3() μ€ν¨: μμΈλ₯Ό λμ§μ§ μμ
// μ±κ³΅: 2, μ€ν¨: 2
}
λ°λ³΅ κ°λ₯ μ λν μ΄μ μ μ¬μ©νλ©΄ μ½λμ κ°λ μ±μ λμΌ μ μλ€.
νμ§λ§ μ λν μ΄μ μ μ μΈνκ³ μ΄λ₯Ό μ²λ¦¬νλ λΆλΆμμλ μ½λ μμ΄ λμ΄λλ©°, νΉν μ²λ¦¬ μ½λκ° λ³΅μ‘ν΄μ Έ μ€λ₯κ° λ κ°λ₯μ±μ΄ 컀μ§μ λͺ μ¬ν΄μΌ νλ€.
μ λν μ΄μ μΌλ‘ ν μ μλ μΌμ λͺ λͺ ν¨ν΄μΌλ‘ μ²λ¦¬ν μ΄μ λ μλ€.
λ€λ₯Έ νλ‘κ·Έλλ¨Έκ° μμ€μ½λμ μΆκ° μ 보λ₯Ό μ 곡ν μ μλ λꡬλ₯Ό λ§λλ μΌμ νλ€λ©΄ μ λΉν μ λν μ΄μ νμ λ ν¨κ» μ μν΄ μ 곡νλλ‘ νμ.
ν΄λΉ κΈμ Joshua Block λμ 'Effective Java 3/E'λ₯Ό μ°Έκ³ νμμ΅λλ€.