JUnit์˜ Rule์ด๋ž€?

  • Rule์€ ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค์—์„œ ๋™์ž‘ ๋ฐฉ์‹์„ ์žฌ์ •์˜ ํ•˜๊ฑฐ๋‚˜ ์‰ฝ๊ฒŒ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.
  • ์‚ฌ์šฉ์ž๋Š” ๊ธฐ์กด์˜ Rule์„ ์žฌ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ํ™•์žฅํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

์–ด๋–ค Rule์ด ์žˆ์„๊นŒ?

TemporaryFolder Rule

  • ์ž„์‹œํด๋”, ํŒŒ์ผ๋“ค์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํ…Œ์ŠคํŠธ๊ฐ€ ๋ชจ๋‘ ๋๋‚œ ํ›„ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค.
  • ๊ธฐ๋ณธ์ ์œผ๋กœ resource๋ฅผ ์‚ญ์ œํ•˜๊ธฐ ๋ชปํ•˜๋Š” ๊ฒฝ์šฐ ์–ด๋– ํ•œ exception๋„ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
public static class HasTempFolder {
  @Rule
  public final TemporaryFolder folder = new TemporaryFolder();
 
  @Test
  public void testUsingTempFolder() throws IOException {
    File createdFile = folder.newFile("myfile.txt");
    File createdFolder = folder.newFolder("subfolder");
    // ...
  }
}
  • ์ž„์‹œ ์žฅ์†Œ์— ์ €์žฅ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ExternalResources Rule

  • ์™ธ๋ถ€ Resource(DB Connection, File, Socket) ์ดˆ๊ธฐํ™” / ๋ฐ˜ํ™˜์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • ํŠน์ • ์ž์›์„ ๋‹ค๋ฅธ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์—์„œ ์žฌ์‚ฌ์šฉํ•  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
public static class UsesExternalResource {
  Server myServer = new Server();
 
  @Rule
  public final ExternalResource resource = new ExternalResource() {
    @Override
    protected void before() throws Throwable {
      myServer.connect();
    };
 
    @Override
    protected void after() {
      myServer.disconnect();
    };
  };
 
  @Test
  public void testFoo() {
    new Client().run(myServer);
  }
}

ErrorCollector Rule

  • ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋”๋ผ๋„ ์ง€์†์ ์œผ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๊ฒŒ ๋„์™€์ฃผ๋Š” Rule์ž…๋‹ˆ๋‹ค.
public static class UsesErrorCollectorTwice {
  @Rule
  public final ErrorCollector collector = new ErrorCollector();
 
  @Test
  public void example() {
    collector.addError(new Throwable("first thing went wrong"));
    collector.addError(new Throwable("second thing went wrong"));
  }
}
  • Test๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ ๋ฐœ์ƒํ–ˆ๋˜ ๋ชจ๋“  Error ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›์•„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Verifier Rule

  • ํ…Œ์ŠคํŠธ ์ž์ฒด๋ฅผ ๊ฒ€์ฆํ•˜๋Š” assert์™€๋Š” ๋‹ค๋ฅด๊ฒŒ, ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ์‹คํ–‰ ํ›„ ๋งŒ์กฑํ•ด์•ผํ•˜๋Š” ํ™˜๊ฒฝ์กฐ๊ฑด์ด๋‚˜ Global์กฐ๊ฑด(๊ฐ์ฒด๋“ค์˜ ์ข…ํ•ฉ ์ƒํƒœ)์„ ๊ฒ€์‚ฌํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
public static class UsesVerifier {
 
  private static String sequence;
 
  @Rule
  public final Verifier collector = new Verifier() {
    @Override
    protected void verify() {
      sequence += "verify ";
    }
  };
 
  @Test
  public void example() {
    sequence += "test ";
  }
 
  @Test
  public void verifierRunsAfterTest() {
    sequence = "";
    assertThat(testResult(UsesVerifier.class), isSuccessful());
    assertEquals("test verify ", sequence);
  }
}

TestWatcher

  • ํ…Œ์ŠคํŠธ Interceptor (starting, succeeded, failed, finishedโ€ฆ)
  • AOP์™€ ๋น„์Šทํ•œ ์—ญํ• ์„ ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค.
import org.junit.AfterClass;
import org.junit.AssumptionViolatedException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
 
import static org.junit.Assert.fail;
 
public class WatchermanTest {
	private static String watchedLog = "";
 
	@Rule
	public final TestRule watchman = new TestWatcher() {
		@Override
		public Statement apply(Statement base, Description description) {
			return super.apply(base, description);
		}
 
		@Override
		protected void succeeded(Description description) {
			watchedLog += description.getDisplayName() + " " + "success!\n";
		}
 
		@Override
		protected void failed(Throwable e, Description description) {
			watchedLog += description.getDisplayName() + " " + e.getClass().getSimpleName() + "\n";
		}
 
		@Override
		protected void skipped(AssumptionViolatedException e, Description description) {
			watchedLog += description.getDisplayName() + " " + e.getClass().getSimpleName() + "\n";
		}
 
		@Override
		protected void starting(Description description) {
			super.starting(description);
		}
 
		@Override
		protected void finished(Description description) {
			super.finished(description);
		}
	};
 
	@AfterClass
	public static void teardown(){
		System.out.println(watchedLog);
	}
 
	@Test
	public void fails() {
		fail();
	}
 
	@Test
	public void test_success() {
	}
}
  • ํ…Œ์ŠคํŠธ ์ •๋ณด๋ฅผ ๋‚จ๊ธฐ๋Š” ์ฝ”๋“œ๋ฅผ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ธฐ๋กํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

TestName

  • ํ…Œ์ŠคํŠธ ๋ฉ”์†Œ๋“œ๋ช…์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
public class NameRuleTest {
  @Rule
  public final TestName name = new TestName();
 
  @Test
  public void testA() {
    assertEquals("testA", name.getMethodName());
  }
 
  @Test
  public void testB() {
    assertEquals("testB", name.getMethodName());
  }
}

Timeout

  • ํ•˜๋‚˜์˜ ํ…Œ์ŠคํŠธ๊ฐ€ ํ†ต๊ณผํ•˜๊ธฐ ์œ„ํ•œ timeout ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (vs @Timeout)
public static class HasGlobalTimeout {
  public static String log;
 
  @Rule
  public final TestRule globalTimeout = Timeout.millis(20);
 
  @Test
  public void testInfiniteLoop1() {
    log += "ran1";
    for(;;) {}
  }
 
  @Test
  public void testInfiniteLoop2() {
    log += "ran2";
    for(;;) {}
  }
}

ExpectedException

  • ์˜ˆ์™ธ ์ง์ ‘ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (vs @Expected)
  • Error ๋ฉ”์‹œ์ง€๋„ ๊ฒ€์ฆ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
public static class HasExpectedException {
  @Rule
  public final ExpectedException thrown = ExpectedException.none();
 
  @Test
  public void throwsNothing() {
 
  }
 
  @Test
  public void throwsNullPointerException() {
    thrown.expect(NullPointerException.class);
    throw new NullPointerException();
  }
 
  @Test
  public void throwsNullPointerExceptionWithMessage() {
    thrown.expect(NullPointerException.class);
    thrown.expectMessage("happened?");
    thrown.expectMessage(startsWith("What"));
    throw new NullPointerException("What happened?");
  }
}

ClassRule

  • TestSuite์˜ ํด๋ž˜์Šค๋งˆ๋‹ค ์ ์šฉํ•  ์ˆ˜ ์žˆ๋Š” Rule์ž…๋‹ˆ๋‹ค.
@RunWith(Suite.class)
@SuiteClasses({A.class, B.class, C.class})
public class UsesExternalResource {
  public static final Server myServer = new Server();
 
  @ClassRule
  public static final ExternalResource resource = new ExternalResource() {
    @Override
    protected void before() throws Throwable {
      myServer.connect();
    };
 
    @Override
    protected void after() {
      myServer.disconnect();
    };
  };
}

RuleChain

  • ์—ฌ๋Ÿฌ๊ฐœ์˜ Rule chaining์œผ๋กœ ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
public class UseRuleChain {
    @Rule
    public final TestRule chain = RuleChain
                           .outerRule(new LoggingRule("outer rule"))
                           .around(new LoggingRule("middle rule"))
                           .around(new LoggingRule("inner rule"));
 
    @Test
    public void example() {
        assertTrue(true);
    }
}

Custom Rule

System Rule

Reference