๐Ÿ’ป Knowledge/ํ…Œ์ŠคํŠธ์ฝ”๋“œ

Test Doubles ์ •๋ฆฌ

ming412 2024. 12. 27. 13:31

Test Double ์ด๋ž€?

ํ…Œ์ŠคํŠธ ๋”๋ธ”์ด๋ž€ ์‹ค์ œ ๊ฐ์ฒด๋ฅผ ๋Œ€์‹ ํ•ด์„œ ํ…Œ์ŠคํŒ…์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ชจ๋“  ๋ฐฉ๋ฒ•์„ ์ผ์ปฌ์–ด ํ˜ธ์นญํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

 

Test Double

Dummy: ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š๋Š” ๊นกํ†ต ๊ฐ์ฒด

Fake: ๋‹จ์ˆœํ•œ ํ˜•ํƒœ๋กœ ๋™์ผํ•œ ๊ธฐ๋Šฅ์€ ์ˆ˜ํ–‰ํ•˜๋‚˜, ํ”„๋กœ๋•์…˜์—์„œ ์“ฐ๊ธฐ์—๋Š” ๋ถ€์กฑํ•œ ๊ฐ์ฒด (ex. FakeRepository)

Stub: ํ…Œ์ŠคํŠธ์—์„œ ์š”์ฒญํ•œ ๊ฒƒ์— ๋Œ€ํ•ด ๋ฏธ๋ฆฌ ์ค€๋น„ํ•œ ๊ฒฐ๊ณผ๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฐ์ฒด. ๊ทธ ์™ธ์—๋Š” ์‘๋‹ตํ•˜์ง€ ์•Š๋Š”๋‹ค.

Spy: Stub์ด๋ฉด์„œ ํ˜ธ์ถœ๋œ ๋‚ด์šฉ์„ ๊ธฐ๋กํ•˜์—ฌ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๋Š” ๊ฐ์ฒด. ์ผ๋ถ€๋Š” ์‹ค์ œ ๊ฐ์ฒด์ฒ˜๋Ÿผ ๋™์ž‘์‹œํ‚ค๊ณ  ์ผ๋ถ€๋งŒ Stubbing ํ•  ์ˆ˜ ์žˆ๋‹ค.

Mock: ํ–‰์œ„์— ๋Œ€ํ•œ ๊ธฐ๋Œ€๋ฅผ ๋ช…์„ธํ•˜๊ณ , ๊ทธ์— ๋”ฐ๋ผ ๋„์ž‘ํ•˜๋„๋ก ๋งŒ๋“ค์–ด์ง„ ๊ฐ์ฒด

 

Mocks Aren't Stubs

Mock๊ณผ Stub์€ ๋‹ค๋ฅด๋‹ค.

 

Stub - ์ƒํƒœ ๊ฒ€์ฆ(State Verification)

"mailer๊ฐ€ mail์„ 1๋ฒˆ ๋ณด๋ƒˆ์–ด"๋ผ๋Š” ์ƒํƒœ๋ฅผ ๊ฒ€์ฆ

// class OrderStateTester...

public void testOrderSendsMailIfUnfilled() {
    Order order = new Order(TALISKER, 51);
    MailServiceStub mailer = new MailServiceStub();
    order.setMailer(mailer);
    order.fill(warehouse);
    assertEquals(1, mailer.numberSent()); // โœ…
}

 

Mock - ํ–‰์œ„ ๊ฒ€์ฆ(Behavior Verification)

"mailer์˜ ๋ฉ”์„œ๋“œ๊ฐ€ ํ•œ ๋ฒˆ ๋ถˆ๋ ธ๋‹ค"๋ผ๋Š” ํ–‰์œ„ ์ž์ฒด์— ์ง‘์ค‘

// class OrderInteractionTester...

public void testOrderSendsMailIfUnfilled() {
    Order order = new Order(TALISKER, 51);
    Mock warehouse = mock(Warehouse.class);
    Mock mailer = mock(MailService.class);
    order.setMailer((MailService) mailer.proxy());

    mailer.expects(once()).method("send"); // โœ…
    warehouse.expects(once()).method("hasInventory")
      .withAnyArguments()
      .will(returnValue(false));

    order.fill((Warehouse) warehouse.proxy());
}

 

https://martinfowler.com/articles/mocksArentStubs.html

 

Mocks Aren't Stubs

Explaining the difference between Mock Objects and Stubs (together with other forms of Test Double). Also the difference between classical and mockist styles of unit testing.

martinfowler.com

 

Mock vs Spy

@Mock: ๋ชจ์˜(mock) ๊ฐ์ฒด๋ฅผ ํ™œ์šฉํ•œ๋‹ค.

๋งŒ์•ฝ ์‹ค์ œ ๊ฐ์ฒด์— ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๋Š” ์ฝ”๋“œ๊ฐ€ ์žˆ๋‹ค๋ฉด, @Mock์„ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ์—๋Š” ๋‹น์—ฐํžˆ ๋กœ๊ทธ๊ฐ€ ๋‚จ์ง€ ์•Š์„ ๊ฒƒ์ด๋‹ค.

@ExtendWith(MockitoExtension.class) // ํ…Œ์ŠคํŠธ๊ฐ€ ์‹œ์ž‘๋  ๋•Œ Mockito ์‚ฌ์šฉํ•ด์„œ ๋ชจ์˜(mock) ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ ๋‹ค.
class MailServiceTest {

	@Mock
    private MailSendClient mailSendClient;

    @DisplayName("๋ฉ”์ผ ์ „์†ก ํ…Œ์ŠคํŠธ")
    @Test
    void sendMail() {
    	// given
        when(mailSendClient.sendEmail(anyString(), anyString(), anyString(), anyString()))
        .thenReturn(true);
        // ...
    }

}

@Spy: ์‹ค์ œ ๊ฐ์ฒด๋ฅผ ํ™œ์šฉํ•œ๋‹ค.

๋งŒ์•ฝ ์‹ค์ œ ๊ฐ์ฒด์— ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๋Š” ์ฝ”๋“œ๊ฐ€ ์žˆ๋‹ค๋ฉด, @Spy๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋กœ๊ทธ๊ฐ€ ๋‚จ๋Š”๋‹ค.

@ExtendWith(MockitoExtension.class) // ํ…Œ์ŠคํŠธ๊ฐ€ ์‹œ์ž‘๋  ๋•Œ Mockito ์‚ฌ์šฉํ•ด์„œ ๋ชจ์˜(mock) ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ ๋‹ค.
class MailServiceTest {

	@Spy
    private MailSendClient mailSendClient;

    @DisplayName("๋ฉ”์ผ ์ „์†ก ํ…Œ์ŠคํŠธ")
    @Test
    void sendMail() {
    	// given
        doReturn(true)
        	.when(mailSendClient)
            .sendEmail(anyString(), anyString(), anyString(), anyString());
       // ...
    }

}

 

์ผ๋ถ€๋Š” ์‹ค์ œ ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ๊ณ , ์ผ๋ถ€๋Š” ๋ชจ์˜ ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์„ ๋•Œ,

@Spy์™€ @Mock์„ ์„ž์–ด์„œ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

@ExtendWith(MockitoExtension.class) // ํ…Œ์ŠคํŠธ๊ฐ€ ์‹œ์ž‘๋  ๋•Œ Mockito ์‚ฌ์šฉํ•ด์„œ ๋ชจ์˜(mock) ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ ๋‹ค.
class MailServiceTest {

	@Spy
    private MailSendClient mailSendClient;
    
    @Mock
    private MailSendHistoryRepository mailSendHistoryRepository;
    
    @InjectMocks
    private MailService mailService;
    
    // ...
}