๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
  • ์žฅ์›์ต ๊ธฐ์ˆ ๋ธ”๋กœ๊ทธ
๐Ÿ’Š Java & Kotlin & Spring/- spring framework +

[Spring Boot] JUnit5 BDDMockito๋กœ ์•Œ์•„๋ณด๋Š” TDD์™€ BDD ์˜ ์ฐจ์ด ๋ฐ BDD ์‹ค์Šต

by Wonit 2021. 4. 22.

๋ชฉ์ฐจ

  • TDD ๋ž€?
    • TDD ๋ž€?
    • TDD ์˜ˆ์ œ
  • BDD ๋ž€?
    • BDD ๋ž€?
    • BDD ์˜ˆ์ œ
  • TDD vs BDD
    • TDD์™€ BDD์˜ ํ•œ ํ‘œ ์š”์•ฝ
  • BDDMockito๋ฅผ ์ด์šฉํ•œ BDD
    • ์‹ค์Šต ํ™˜๊ฒฝ ๊ตฌ์„ฑ
    • Mockito vs BDD Mockito
    • Stubbing
    • given
      • any()
      • anyString()
      • anyLong()
      • willReturn()
      • will()
      • willThrow()

 

๋ณธ ์ฃผ์ œ๋ฅผ ํ•™์Šตํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” Java์™€ JUnit5์™€ BDDMockito ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ์˜์กด์„ฑ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

 


์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ๊ฐœ๋ฐœํ•˜๋ฉด์„œ ์ง€๊ธˆ๊นŒ์ง€ ๋Š๋‚€ ๊ฐ€์žฅ ํฐ ๋ณ€ํ™”๊ฐ€ ๋‚˜์—๊ฒŒ๋Š” TDD๊ฐ€ ์•„๋‹๊นŒ ์‹ถ๋‹ค.

์šฐ์—ฐํ•œ ๊ธฐํšŒ์— TDD๋ฅผ ์Šค์Šค๋กœ ๋„์ž…ํ•˜์˜€์„ ๋•Œ, ๋‚ด๊ฐ€ ๋“ค์—ˆ๋˜ ์ฒซ ์ƒ๊ฐ์€

๋„ˆ๋ฌด ๊ท€์ฐฎ์€๋ฐ,... ์ด๋ ‡๊ฒŒ ํ•˜๋‹ค๊ฐ€ ์–ธ์ œ ๋๋‚ด์ง€...? ์ด๊ฑฐ ๊ทธ๋ƒฅ ํ…Œ์ŠคํŠธ ์•ˆ ์งœ๊ณ  ๋ฐ”๋กœ ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ..

์ด๋Ÿฐ ์ƒ๊ฐ์„ ๊ฐ–๊ณ  TDD๋ฅผ ์ฆ์˜ค ํ•˜๋˜ ์‹œ๊ฐ„์ด ์–ผ๋งˆ ์ง€๋‚˜์ง€ ์•Š์•„์„œ ์‹คํƒ€๋ž˜์ฒ˜๋Ÿผ ๊ผฌ์ธ ์•„์ฃผ ๋ชจํ˜ธํ•œ ๊ธฐ๋Šฅ(Spring Security ์—์„œ) ๊ตฌํ˜„ํ•ด์•ผํ•  ๋•Œ๊ฐ€ ์žˆ์—ˆ๋‹ค.


๋Š˜ ๊ทธ๋ ‡๋“ฏ TDD๋กœ ์‹คํŒจํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๋จผ์ € ์งœ๊ณ  ๋‚ด๊ฐ€ ๋ญ˜ ํ•ด์•ผํ• ์ง€๋ฅผ ์ ์–ด๊ฐ€๋ฉด์„œ ํ•˜๋‚˜์”ฉ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ธฐ๋Šฅ์„ ๊ณ ์น˜๋ฉด์„œ ์ ์  ์‹คํƒ€๋ž˜๊ฐ€ ํ’€๋ฆฌ๋Š” ๊ฒƒ ๊ฐ™์€ ๊ธฐ๋ถ„์ด ๋“ค๊ฒŒ ๋˜์—ˆ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  ํ•˜๋‚˜ ํ•˜๋‚˜ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๋ฉฐ ์ตœ๊ณ ๋กœ ๋ณต์žกํ•˜๋˜ ๊ทธ ๊ธฐ๋Šฅ์„ ํ•˜๋‚˜์”ฉ ํ’€์–ด๊ฐ€๋ฉฐ ๋Š๊ผˆ๋˜ ๊ฐ์ •์€ ์ฆ์˜ค์—์„œ ๋งน์‹ ์œผ๋กœ ๋ฐ”๋€Œ๊ฒŒ ๋œ ๊ณ„๊ธฐ ๊ฐ™์•˜๋‹ค.

 

์ด๋ ‡๋“ฏ TDD๋Š” ์—ฌ๋Ÿฌ ๊ฐœ๋ฐœ์ž์—๊ฒŒ ์ข‹์€ ๊ฐœ๋ฐœ ์Šต๊ด€์„ ๊ฐ–๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ• ์ค‘ ๋‹จ์—ฐ ์ตœ๊ณ ๋ผ๊ณ  ์ƒ๊ฐํ•œ๋‹ค.


์˜ค๋Š˜์€ TDD์—์„œ ์กฐ๊ธˆ ๋” ์ง„ํ™”ํ•œ BDD์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด๋ ค ํ•œ๋‹ค.

 

๊ทธ ์ „์— TDD์™€ BDD์— ๋Œ€ํ•œ ์šฉ์–ด ์ •๋ฆฌ๋ฅผ ํ•ด๋ณด์ž.

 

TDD, Test Driven Development

 

TDD๋Š” Test Driven Development์œผ๋กœ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฐœ๋ฐœ์„ ์ฃผ๋„ํ•œ๋‹ค๋Š” ๊ฐœ๋…์œผ๋กœ ์‚ฌ์šฉ๋œ๋‹ค.

 

TDD๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ๋จผ์ € ๋งŒ๋“ค๊ณ  ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผํ•˜๊ธฐ ์œ„ํ•œ ํ–‰๋™๋“ค์ด ๋ชจ๋‘ ๊ฐœ๋ฐœ์„ ์ฃผ๋„ํ•˜๋Š” ๊ฒƒ์„ ๋ชฉํ‘œ๋กœ ํ•œ๋‹ค.


๋ณดํ†ต ํ…Œ์ŠคํŠธ๋Š” ๊ฐœ๋ฐœ์ด ๋๋‚œ ํ›„์— ํ•˜๋Š” ๊ณผ์ •์ด๋ผ๊ณ  ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ๋‹ค.


ํ•˜์ง€๋งŒ TDD๋Š” ๊ฐœ๋ฐœ ์ค‘๊ฐ„ ์ค‘๊ฐ„์— ๋Š์ž„ ์—†๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ํ†ตํ•ด ๊ธฐ๋Šฅ์„ ๊ฐœ๋ฐœํ•ด์•ผ ํ•œ๋‹ค.

 

TDD๋Š” Kent Beck์ด ๊ณ ์•ˆํ•ด๋‚ธ ๋ฐฉ๋ฒ•๋ก ์œผ๋กœ ์ด ์ฑ…์€ ํ•˜๋‚˜์˜ ๋ฐ”์ด๋ธ”๋กœ ์—ฌ๋Ÿฌ ๊ฐœ๋ฐœ์ž๋“ค์—๊ฒŒ ์ฃผ๋ชฉ๋ฐ›๊ณ  ์žˆ๋Š”๋“ฏ ํ•˜๋‹ค.

 

TDD๋ฅผ ๋ฒˆ์—ญํ•œ ํ•œ๊ธ€ํŒ ์ฑ…์ด ์žˆ๋Š”๋ฐ, ํ˜น์‹œ ์ด์— ๋Œ€ํ•œ ์ •๋ณด๊ฐ€ ๊ถ๊ธˆํ•˜๋‹ค๋ฉด ํ•ด๋‹น ๋ธ”๋กœ๊ทธ์— ์žˆ๋Š” ๊ฐœ๋ฐœ์ž ์ฑ… ์ฝ๊ธฐ ์˜ ํ…Œ์ŠคํŠธ ์ฃผ๋„ ๊ฐœ๋ฐœ - Kent Beck ์„ ํ™•์ธํ•ด ๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ธ ๊ฒƒ ๊ฐ™๋‹ค.

TDD ์˜ˆ์ œ

 

๊ณ„์‚ฐ๊ธฐ๋ฅผ TDD๋กœ ๊ตฌํ˜„ํ•ด๋ณด์ž๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

 

Step 1. ์šฐ์„  ๋น„์–ด์žˆ๋Š” Calculator ํด๋ž˜์Šค์™€ CalculatorTest ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•˜์ž.

public class Calculator {

}

public class CalculatorTest {
    Calculaotr calc = new Calculator();
}

 

๊ฐ„๋‹จํ•˜๊ฒŒ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์— Calculator ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•ด์ฃผ์ž.

 

Step 2. ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ๊ธฐ๋Šฅ(๋ง์…ˆ, plus())์ด ์–ด๋–ป๊ฒŒ ์ƒ๊ฒผ๊ณ  ๋ญ˜ ๋ฐ˜ํ™˜ํ•˜๋Š”์ง€ CalculatorTest ์— ์ž‘์„ฑํ•œ๋‹ค.

public class Calculator {

}

public class CalculatorTest {
    Calculaotr calc = new Calculator();

    @Test
    void plus() {
        int a = 10;
        int b = 20;
        int result = calc.plus(a, b);
    }

}

 

ํ˜„์žฌ Calculator ํด๋ž˜์Šค์—๋Š” plus๋ผ๋Š” ๋ฉ”์„œ๋“œ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์‹คํŒจํ•˜๊ฒŒ ๋œ๋‹ค.

 

Step 3. Calculator ํด๋ž˜์Šค์— ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋ฅผ ํ†ต๊ณผํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ„๋‹จํ•œ ํด๋ž˜์Šค๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค.

public class Calculator {
    public int plus(int a, int b) {
        return 0;
    };
}

public class CalculatorTest {
    Calculaotr calc = new Calculator();

    @Test
    void plus() {
        int a = 10;
        int b = 20;
        int result = calc.plus(a, b);
    }
}

 

Calculator์˜ plus ๋ฉ”์„œ๋“œ์—์„œ๋Š” ์–ด๋–ค ๊ฐ’์ด ๋ฐ˜ํ™˜๋˜๋„ ์ข‹๋‹ค.


๊ทธ๋ƒฅ ์ผ๋‹จ ์ปดํŒŒ์ผ์„ ์„ฑ๊ณต์‹œํ‚ค๋Š” ๊ฒƒ์ด ๋ชฉ์ ์ด๋‹ค.

 

Step 4. Calculator ํด๋ž˜์Šค์—์„œ ์–ด๋–ค ๊ฒฐ๊ณผ๋ฅผ ๋‚ด์–ด์•ผ ํ• ์ง€ CalculatorTest ํด๋ž˜์Šค์—์„œ ๋‹ค์‹œ ์ •์˜ํ•œ๋‹ค.

public class Calculator {
    public int plus(int a, int b) {
        return 0;
    };
}

public class CalculatorTest {
    Calculaotr calc = new Calculator();

    @Test
    void plus() {
        int a = 10;
        int b = 20;
        int result = calc.plus(a, b);

        assertEquals(result, a + b);
    }
}

assertEquals ๋Š” JUnit 5์— ์กด์žฌํ•˜๋Š” ๊ฒ€์ฆ๋ฌธ์œผ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜ 2๊ฐœ๊ฐ€ ์„œ๋กœ ๋™์ผํ•˜๋ฉด ํ…Œ์ŠคํŠธ ์„ฑ๊ณต์„ ์˜๋ฏธํ•œ๋‹ค.

 

ํ˜„์žฌ result ์—์„œ๋Š” 0์„ ๋ฐ˜ํ™˜ํ•˜๋‹ˆ ํ•ด๋‹น ํ…Œ์ŠคํŠธ๋Š” ๋˜ ์‹คํŒจํ•  ๊ฒƒ์ด๋‹ค.

 

Step 5. Calculator ํด๋ž˜์Šค์—์„œ ํ•ด๋‹น ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผํ•  ์ˆ˜ ์žˆ๋Š” ๋กœ์ง์„ ๊ตฌํ˜„ํ•œ๋‹ค.

public class Calculator {
    public int plus(int a, int b) {
        return a + b;
    };
}

public class CalculatorTest {
    Calculaotr calc = new Calculator();

    @Test
    void plus() {
        int a = 10;
        int b = 20;
        int result = calc.plus(a, b);

        assertEquals(result, a + b);
    }
}

BDD, Behavior Driven Development

 

BDD๋Š” ์ „ํ˜€ ์ƒˆ๋กœ์šด ๋ฐฉ๋ฒ•์ด ์•„๋‹ˆ๋‹ค.

 

Danial Terhorst-North์™€ Charis Matts๊ฐ€ ์ฐฉ์•ˆ BDD Introducing - Dan north & associates์•ˆ ๋ฐฉ๋ฒ•๋ก ์œผ๋กœ BDD์˜ ๋ชจ๋“  ๊ทผ๊ฐ„์€ TDD์—์„œ ์ฐฉ์•ˆ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— TDD๊ฐ€ ์ถ”๊ตฌํ•˜๋Š” ๊ฐ€์น˜์™€ ํฌ๊ฒŒ ๋‹ค๋ฅด์ง€ ์•Š๋‹ค.

 

 

Danial Terhorst-North๊ฐ€ ์ฒ˜์Œ์œผ๋กœ BDD๋ฅผ ์ƒ๊ฐํ•ด ๋‚ธ ๋•Œ๋Š” ๋ฐ”๋กœ TDD๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ณ  ์žˆ๋˜ ๋„์ค‘์ด์—ˆ๋‹ค๊ณ  ํ•œ๋‹ค.

 

The deeper I got into TDD, the more I felt that my own journey had been less of a wax-on, wax-off process of gradual mastery than a series of blind alleys. I remember thinking “If only someone had told me that!” far more often than I thought “Wow, a door has opened.” I decided it must be possible to present TDD in a way that gets straight to the good stuff and avoids all the pitfalls.

My response is behaviour-driven development (BDD). It has evolved out of established agile practices and is designed to make them more accessible and effective for teams new to agile software delivery. Over time, BDD has grown to encompass the wider picture of agile analysis and automated acceptance testing

 

์ด ๋ง์„ ๊ฐ„๋‹จํ•˜๊ฒŒ ํ•˜์ž๋ฉด TDD๋ฅผ ํ•˜๋‹ค๊ฐ€ ํ•ด๋‹น ์ฝ”๋“œ๋ฅผ ๋ถ„์„ํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋งŽ์€ ์ฝ”๋“œ๋“ค์„ ๋ถ„์„ํ•ด์•ผํ•˜๊ณ  ๋ณต์žก์„ฑ์œผ๋กœ ์ธํ•ด '๋ˆ„๊ตฐ๊ฐ€๊ฐ€ ๋‚˜์—๊ฒŒ ์ด ์ฝ”๋“œ๋Š” ์–ด๋–ค์‹์œผ๋กœ ์งœ์—ฌ์กŒ์–ด! ๋ผ๊ณ  ๋ง์„ ํ•ด์คฌ์œผ๋ฉด ์ข‹์•˜์„ ํ…๋ฐ' ๋ผ๋Š” ์ƒ๊ฐ์„ ํ•˜๋‹ค๊ฐ€ ๋ณด๋‹ˆ ํ–‰๋™ ์ค‘์‹ฌ ๊ฐœ๋ฐœ์„ ํ•˜๋ฉด ์ข‹๊ฒ ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.

 

BDD๋Š” ํ–‰๋™์— ๊ธฐ๋ฐ˜ํ•˜์—ฌ TDD๋ฅผ ์ˆ˜ํ–‰ํ•˜์ž๋Š” ๊ณตํ†ต์˜ ์ดํ•ด์ธ๋ฐ, ์ด๋ฅผ ํ•œ ๋ฌธ์žฅ์œผ๋กœ ๋ง ํ•˜์ž๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

 

BDD๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์–ด๋–ป๊ฒŒ ํ–‰๋™ํ•ด์•ผ ํ•˜๋Š”์ง€์— ๋Œ€ํ•œ ๊ณตํ†ต๋œ ์ดํ•ด๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

BDD์˜ ํ–‰๋™

 

BDD ์—์„œ๋Š” ํ–‰๋™์— ๋Œ€ํ•œ ์ŠคํŽ™์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

 

  1. Narrative
  2. Given / When / Then

๋ชจ๋“  ํ…Œ์ŠคํŠธ ๋ฌธ์žฅ์€ Narrative ํ•˜๊ฒŒ ๋˜์–ด์•ผ ํ•œ๋‹ค. ์ฆ‰, ์ฝ”๋“œ๋ณด๋‹ค ์ธ๊ฐ„์˜ ์–ธ์–ด์™€ ์œ ์‚ฌํ•˜๊ฒŒ ๊ตฌ์„ฑ๋˜์–ด์•ผ ํ•œ๋‹ค.

 

TDD ์—์„œ๋Š” ์‚ฌ์‹ค์ƒ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ด์šฉํ•œ ๊ตฌํ˜„์— ์ดˆ์ ์ด ๋งž์ถฐ์ € ์žˆ๋‹ค.

 

ํ•˜์ง€๋งŒ BDD๋Š” ๊ทธ๋ ‡์ง€ ์•Š๋‹ค.

 

BDD๋Š” TDD๋ฅผ ์ˆ˜ํ–‰ํ•˜๋ ค๋Š” ์–ด๋– ํ•œ ํ–‰๋™๊ณผ ๊ธฐ๋Šฅ์„ ๊ฐœ๋ฐœ์ž๊ฐ€ ๋” ์ดํ•ดํ•˜๊ธฐ ์‰ฝ๊ฒŒํ•˜๋Š” ๊ฒƒ์ด ๋ชฉ์ ์ด๋‹ค.

 

๋ชจ๋“  ํ…Œ์ŠคํŠธ ๋ฌธ์žฅ์€ Given / When / Then ์œผ๋กœ ๋‚˜๋ˆ ์„œ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.

 

์œ„์—์„œ ๋ง ํ•œ Narrative๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•œ ์‹ค์งˆ์ ์ธ ๊ฐœ๋…์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋  ๊ฒƒ ๊ฐ™๋‹ค.

 

  • Given
    • ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด ์ฃผ์–ด์ง„ ์ƒํƒœ
    • ํ…Œ์ŠคํŠธ ๋Œ€์ƒ์—๊ฒŒ ์ฃผ์–ด์ง„ ์กฐ๊ฑด
    • ํ…Œ์ŠคํŠธ๊ฐ€ ๋™์ž‘ํ•˜๊ธฐ ์œ„ํ•ด ์ฃผ์–ด์ง„ ํ™˜๊ฒฝ
  • When
    • ํ…Œ์ŠคํŠธ ๋Œ€์ƒ์—๊ฒŒ ๊ฐ€ํ•ด์ง„ ์–ด๋– ํ•œ ์ƒํƒœ
    • ํ…Œ์ŠคํŠธ ๋Œ€์ƒ์—๊ฒŒ ์ฃผ์–ด์ง„ ์–ด๋– ํ•œ ์กฐ๊ฑด
    • ํ…Œ์ŠคํŠธ ๋Œ€์ƒ์˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝ์‹œํ‚ค๊ธฐ ์œ„ํ•œ ํ™˜๊ฒฝ
  • Then
    • ์•ž์„  ๊ณผ์ •์˜ ๊ฒฐ๊ณผ

์ฆ‰, ์–ด๋–ค ์ƒํƒœ์—์„œ ์ถœ๋ฐœ (given) ํ•˜์—ฌ ์–ด๋–ค ์ƒํƒœ์˜ ๋ณ€ํ™”๋ฅผ ๊ฐ€ํ–ˆ์„ ๋•Œ (when) ๊ธฐ๋Œ€ํ•˜๋Š” ์–ด๋– ํ•œ ์ƒํƒœ๊ฐ€ ๋˜์–ด์•ผ ํ•œ๋‹ค.(then)

BDD ์˜ˆ์ œ

 

์•ž์„  ์ƒํ™ฉ์—์„œ TDD์˜ ํ๋ฆ„์„ ๋นผ๊ณ  BDD์˜ ํ•ต์‹ฌ ๊ฐœ๋…์ธ ํ–‰๋™์— ๋”ฐ๋ฅธ Given When Then ์„ ๋„์ž…ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

 

public class Calculator {
    public int plus(int a, int b) {
        return a + b;
    };
}

public class CalculatorTest {
    Calculaotr calc = new Calculator();

    @Test
    void plus() {
        // given
        int a = 10;
        int b = 20;

        // when
        int result = calc.plus(a, b);

        // then
        assertEquals(result, a + b);
    }
}

 

ํฐ ์ฐจ์ด๊ฐ€ ์—†์ง€ ์•Š๋‚˜?

 

๋‹น์—ฐํ•œ ๊ฒƒ์ด๋‹ค. BDD ์ž์ฒด๊ฐ€ TDD์—์„œ ๋” ์ƒˆ๋กœ์šด ๊ฐœ๋…์ด ์•„๋‹ˆ๋ผ TDD๋ฅผ ๋” ์ž˜, ๋” ๋ฉ‹์ง€๊ฒŒ, ๋” ํ˜‘์กฐ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

TDD vs BDD

์•ž์„œ ๋ง ํ–ˆ์ง€๋งŒ TDD์™€ BDD๋ฅผ ๋น„๊ตํ•œ๋‹ค๋Š” ๊ฒƒ์€ ์•ฝ๊ฐ„ ๋‹ค๋ฅธ ์ฃผ์ œ์˜ ์ด์•ผ๊ธฐ๋ผ ๋น„๊ต ํ•œ๋‹ค๋Š” ๊ฒƒ์ด ํž˜๋“ค๊ฒƒ ๊ฐ™์ง€๋งŒ ๊ฐœ์ธ์ ์œผ๋กœ ๋น„๊ต๋ฅผ ํ•ด๋ณด์ž๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ‘œ๊ฐ€ ๋‚˜์˜ฌ ๊ฒƒ ๊ฐ™๋‹ค.

 

์ด๋ฆ„ ์ฐฝ์‹œ์ž Based ํ•ต์‹ฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
TDD Kent-Beck ์‹œ๋‚˜๋ฆฌ์˜ค ๊ธฐ๋ฐ˜ ํ…Œ์ŠคํŠธ๊ฐ€ ์ฃผ๋„ํ•˜๋Š” ๊ฐœ๋ฐœ JUnit5, Mockito
BDD Dan North ํ–‰๋™ ๊ธฐ๋ฐ˜ ์ž์—ฐ์–ด์™€ ๋” ๊ฐ€๊น๊ฒŒ TDD JUnit5, BDDMockito

BDDMockito๋ฅผ ์ด์šฉํ•œ BDD

BDD๋ฅผ ์‹คํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” Given / When / Then ๊ตฌ์กฐ๋ฅผ ์ž˜ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

 

์•ž์„  ๋ฐฉ๋ฒ•์ฒ˜๋Ÿผ ์ฃผ์„์„ ์ด์šฉํ•˜์—ฌ ๊ตฌ๋ถ„ํ•˜๋Š” ๊ฒƒ๋„ BDD์ด์ง€๋งŒ ์ด๋ณด๋‹ค ๋” BDD ์Šค๋Ÿฝ๊ฒŒ BDD๋ฅผ ์‹ค์ฒœํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ๋ฐ”๋กœ BDDMockito๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

 

BDDMockito๋ž€ Mockito ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋‚ด์— ์กด์žฌํ•˜๋Š” BDDMockito ํด๋ž˜์Šค๋ฅผ ๋ง ํ•˜๋ฉฐ BDD ์ง€ํ–ฅ์ ์ธ ๊ฐœ๋ฐœ์„ mockito ์—์„œ api ํ˜•ํƒœ๋กœ ์ œ๊ณตํ•œ๋‹ค.

์‹ค์Šต ํ™˜๊ฒฝ ๊ตฌ์„ฑ, Dependencies

 

Springboot ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋ฉด ์ž๋™์œผ๋กœ JUnit-jupiter ์˜์กด์„ฑ์ด ์ถ”๊ฐ€๋˜๋Š”๋ฐ ํ•ด๋‹น ์˜์กด์„ฑ์ด ์ถ”๊ฐ€๋˜๋ฉฐ ํ•จ๊ป˜ mockito ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์ถ”๊ฐ€๋œ๋‹ค.

 

 

maven

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>1.10.19</version>
    <scope>test</scope>
</dependency>

 

gradle

testImplementation group: 'org.mockito', name: 'mockito-all', version: '1.10.19'

Mockito vs BDDMockito

์ˆœ์ˆ˜ Mockito์—์„œ BDD์˜ Given / When / Then ์„ ์œ„ํ•ด์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด when(obj) ๋ฉ”์„œ๋“œ์™€ thenReturn() ์ด์šฉํ•˜๊ณ  verity() ๊ตฌ๋ฌธ์„ ์ด์šฉํ•ด ๊ฒ€์ฆํ•œ๋‹ค.

 

์ด๋Ÿฐ์‹์œผ๋กœ ํŠน์ • ์ƒํ™ฉ์— ๋Œ€ํ•œ (when๊ณผ ๊ฐ™์ด) ์šฐ๋ฆฌ๊ฐ€ ๊ฐ€์งœ๋กœ ๊ฒฐ๊ณผ๋ฅผ ๋งŒ๋“ค์–ด ์ฃผ๋Š” ๊ฒƒ์„ Stubbing(์Šคํ„ฐ๋น™) ์ด๋ผ๊ณ  ํ•œ๋‹ค. ์ฆ‰ ๊ฐ€์งœ๋กœ ์ˆ˜ํ–‰ํ•  ๊ฐ์ฒด๋ฅผ ๋„ฃ์–ด์ฃผ๋Š” ๊ฒƒ์ด๋‹ค.

 

when(phoneBookRepository.contains(momContactName))
  .thenReturn(false);

phoneBookService.register(momContactName, momPhoneNumber);

verify(phoneBookRepository)
  .insert(momContactName, momPhoneNumber);

 

์ด๊ฒŒ ์ผ๋ จ์˜ BDD์˜ Stubbing ๊ณผ์ •์ธ๋ฐ, ๋ญ”๊ฐ€ ๋งž์ง€ ์•Š๋Š” ๋ถ€๋ถ„์ด ์žˆ๋‹ค.

 

  • when()
  • thenReturn()
  • verity()

 

์œ„์— ์กด์žฌํ•˜๋Š” 3๊ฐ€์ง€ ํ–‰๋™ ๊ณผ์ •์ธ๋ฐ, ๋ญ”๊ฐ€ ์ด์ƒํ•˜๋‹ค.

 

๊ฐœ๋…์ ์œผ๋กœ given ์— ํ•ด๋‹น๋˜๋Š” Mockito์˜ when(phoneBookRepository.contains(momContactName))์˜ ์ด๋ฆ„์ด when์ด๋ผ ์‰ฝ๊ฒŒ ํ–‡๊ฐˆ๋ฆด ์ˆ˜ ์žˆ๊ฒŒ ๋˜์–ด์žˆ๋‹ค.

 

์ด๋Ÿฌํ•œ ๋ฌธ์ œ์ ์„ BDDMockito์˜ given() ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

BBDMockito๋ฅผ ์ด์šฉํ•˜๋ฉด Mockito์˜ when() ์„ given() ์ด๋ผ๋Š” ๋ฉ”์„œ๋“œ๋กœ ๋” ์ •ํ™•ํ•œ ์˜๋ฏธ ์ „๋‹ฌ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

given(someClass.method()).willReturn() 

 

 

given() ๋ฉ”์„œ๋“œ

์šฐ์„  given() ๋ฉ”์„œ๋“œ๋Š” BDD์˜ givne์„ ๊ทธ๋Œ€๋กœ ๋‚ดํฌํ•˜๊ณ  ์žˆ๋‹ค.

 

์ด๋Ÿฐ givne()์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ด์šฉํ•ด์„œ ์–ด๋–ค ์ƒํ™ฉ์— ์ฆ‰, ์–ด๋–ค ๋ฉ”์„œ๋“œ๊ฐ€ ์‹คํ–‰๋˜์—ˆ์„ ๋•Œ์˜ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ์ƒํ™ฉ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ƒํ™ฉ์˜ ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž.

 

public interface MemberRepository extends JpaRepository<Member, Long> {
    boolean existsByEmail(String email);
}

public class MemberService {
    @Autowired
    private MemberRepository memberRepository;

    public boolean isExistEmail(String email) {
        return memberRepository.existsByEmail(email); // ์กด์žฌํ•˜๋ฉด true, ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด false ๋ฐ˜ํ™˜
    }
}

 

๊ทธ๋Ÿผ ์•„๋ž˜์™€ ๊ฐ™์ด ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

 

public interface MemberRepository extends JpaRepository<Member, Long> {
    boolean existsByEmail(String email);
}

public class MemberService {
    @Autowired
    private MemberRepository memberRepository;

    public boolean isExistEmail(String email) {
        return memberRepository.existsByEmail(email); // ์กด์žฌํ•˜๋ฉด true, ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด false ๋ฐ˜ํ™˜
    }
}

public class MemberServiceTest {
    @MockBean
    private MemberService memberService;

    private final MemberRepository memberRepository = mock(MemberRepository.class);

    @BeforeEach
    void stubbing() { 
        given(memberRepository.existsByEmail(any(String.class)))
                    .willReturn(false);
    }
}

 

๊ทธ๋Ÿผ ๋” ์ž์„ธํžˆ ์•Œ์•„๋ณด์ž.

 

 

์œ„์˜ Stubbing ๋ฌธ์žฅ์—์„œ 3๊ฐ€์ง€์˜ ์˜๋ฏธ๋ฅผ ๋‹ด๊ณ  ์žˆ๋‹ค.

 

  1. Mocking ํ•  ๋ฉ”์„œ๋“œ
  2. ํ•ด๋‹น ๋ฉ”์„œ๋“œ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ
  3. ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋ฅผ ์ˆ˜ํ–‰ํ–ˆ์„ ๋•Œ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฐ’

Mocking ํ•  ๋ฉ”์„œ๋“œ

 

Mocking์„ ํ•  ๋ฉ”์„œ๋“œ๋Š” ๋ญ˜๊นŒ?

 

์šฐ๋ฆฌ๋Š” ์ง€๊ธˆ MemberService๋ฅผ ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๋ คํ•œ๋‹ค.

 

Unit Test์—์„œ ์ค‘์š”ํ•œ ๊ฒƒ์€ ํ…Œ์ŠคํŠธํ•˜๋ ค๋Š” ๋Œ€์ƒ์˜ ๊ณ ๋ฆฝ์ด๋‹ค.

 

ํ…Œ์ŠคํŠธ ๋Œ€์ƒ์„ ๊ณ ๋ฆฝํ•œ๋‹ค๋Š” ๋œป์€, ํ…Œ์ŠคํŠธ ๋Œ€์ƒ์— ์—ฐ๊ด€๋œ ๋‹ค๋ฅธ ๊ฐ์ฒด๋“ค์€ ๊ด€์—ฌํ•˜์ง€ ์•Š๋„๋ก ์šฐ๋ฆฌ๊ฐ€ ๊ฐ€์งœ ๊ฐ์ฒด๋ฅผ ๋„ฃ์–ด์ค˜์•ผ ํ•œ๋‹ค๋Š” ์†Œ๋ฆฌ์™€ ๋น„์Šทํ•˜๋‹ค๊ณ  ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์ด๋ ‡๊ฒŒ ํ…Œ์ŠคํŠธ ๋Œ€์ƒ์„ ๊ณ ๋ฆฝํ•˜๊ธฐ ์œ„ํ•ด์„œ Mockito์˜ mock() ๋ฅผ ์ด์šฉํ•˜์˜€๊ณ , ํ…Œ์ŠคํŠธ ๋Œ€์ƒ์ด ํŠน์ • ๊ฒฐ๊ณผ๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ์—ฐ๊ด€๋œ ๊ฐ์ฒด์˜ ์—ฐ์‚ฐ์„ ์ฃผ์ž…ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

 

MemberService ์—๋Š” ํ˜„์žฌ isExistByEmail ๋ฉ”์„œ๋“œ๊ฐ€ ์กด์žฌํ•˜๊ณ  ํ•ด๋‹น ๋ฉ”์„œ๋“œ ๋‚ด๋ถ€์— MemberRepository ์ธ์Šคํ„ด์Šค๊ฐ€ existsByEmail ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ ์šฐ๋ฆฌ๋Š” MemberRepository์˜ existsByEmail ์„ ๊ฐ€์งœ๋กœ ์ฃผ์ž…ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

 

ํ•ด๋‹น ๋ฉ”์„œ๋“œ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ

 

ํ•ด๋‹น ๋ฉ”์„œ๋“œ, ์ฆ‰ existsByEmail ์ด๋ผ๋Š” ๋ฉ”์„œ๋“œ๋Š” String ์œผ๋กœ email์„ ๋ฐ›๊ณ  ์žˆ๋‹ค.

 

๊ทธ๋Ÿผ ์šฐ๋ฆฌ์—๊ฒŒ ์—ฌ๋Ÿฌ ์„ ํƒ์ง€๊ฐ€ ์กด์žฌํ•œ๋‹ค.

 

  • ๋ชจ๋“  ๊ฐ’์„ ๋ฐ›์•˜์„ ๋•Œ์˜ ํ–‰๋™ ์ •์˜ํ•˜๊ธฐ
  • ํŠน์ • ๊ฐ’์„ ๋ฐ›์•˜์„ ๋•Œ์˜ ํ–‰๋™ ์ •์˜ํ•˜๊ธฐ

๋ชจ๋“  ๊ฐ’์„ ๋ฐ›์•˜์„ ๋•Œ์˜ ํ–‰๋™ ์ •์˜ํ•˜๊ธฐ, any()

 

๋ชจ๋“  ๊ฐ์ฒด๊ฐ€ ๋“ค์–ด์™”์„ ๋•Œ์˜ ํ–‰๋™์€ any(Object object) ๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์—ฌ๊ธฐ์„œ๋Š” String ์ธ ๋ชจ๋“  ๊ฐ์ฒด๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ์†Œ๋ฆฌ๋‹ค.

 

์œ„์™€ ๊ฐ™์€ ์ƒํ™ฉ์— (์–ด๋– ํ•œ String ๊ฐ’์ด๋ผ๋„ ๊ฐ€๋Šฅํ•œ ์ƒํ™ฉ) ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”์„œ๋“œ๊ฐ€ ์—ฌ๋Ÿฟ ์กด์žฌํ•œ๋‹ค

 

  • anyString() : ์–ด๋– ํ•œ ๋ฌธ์ž์—ด
  • anyLong() : ์–ด๋– ํ•œ Long
  • anyBoolean() : ์–ด๋– ํ•œ Boolean ๊ฐ’
  • any() : ๋ชจ๋“  ๊ฐ์ฒด
public class MemberServiceTest {
    // ... ์ƒ๋žต
    @BeforeEach
    void stubbing() { 
        given(memberRepository.existsByEmail(anyString()));

        given(memberRepository.findById(anyLong()));

        given(memberRepository.findAllByOffline(anyBoolean()))
                    .willReturn(false);
    }
}

ํŠน์ • ๊ฐ’์„ ๋ฐ›์•˜์„ ๋•Œ์˜ ํ–‰๋™ ์ •์˜ํ•˜๊ธฐ, eq()

์œ„์˜ ์ƒํ™ฉ์—์„œ๋Š” ๋ชจ๋“  String์„ ๋ฐ›์•˜์„ ๋•Œ์˜ ํ–‰๋™์„ ์ •์˜ํ•˜๋Š”๋ฐ, ๋งŒ์•ฝ ํŠน์ •ํ•œ String ๊ฐ’์ด ๋“ค์–ด์™€์•ผ ํ•œ๋‹ค๋ฉด?


ํ˜น์€ ํŠน์ •ํ•œ ๊ฐ์ฒด๊ฐ€ ๋“ค์–ด์™€์•ผ ํ•œ๋‹ค๋ฉด?


๊ทธ๋Ÿด ๋•Œ ์‚ฌ์šฉํ•˜๋Š”๊ฒŒ ๋ฐ”๋กœ eq() ๋ฉ”์„œ๋“œ์ด๋‹ค.

 

  • eq() : ๋™์ผํ•œ ๊ฐ’ ํ˜น์€ ๊ฐ์ฒด์˜ ํ–‰๋™

ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋ฅผ ์ˆ˜ํ–‰ํ–ˆ์„ ๋•Œ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฐ’

 

์œ„์™€ ๊ฐ™์ด ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ ํ–‰๋™์„ ์ •์˜ํ•˜์˜€๋‹ค.


๊ทธ๋Ÿผ ์ด์ œ ํ•ด๋‹น ํ–‰๋™์— ์ ์ ˆํ•œ ๋ฐ˜ํ™˜์„ ํ•ด์ค˜์•ผ ํ•œ๋‹ค.


ํ–‰๋™์„ ๋ฐ˜ํ™˜ํ•  ๋•Œ๋Š” ํฌ๊ฒŒ 3๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์กด์žฌํ•œ๋‹ค.

  • willReturn()
  • will()
  • willThrow()

willReturn()

๋œป ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ ๊ฐ’์„ ๋ช…์‹œํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.

public class MemberServiceTest {

    // ... ์ƒ๋žต
    @BeforeEach
    void stubbing() { 
        given(memberRepository.existsByEmail(any(String.class)))
                    .willReturn(false);
    }

    @Test
    void exists_valid() {
        boolean isExist = memberService.isExistEmail("valid@gmail.com");

        assertTrue(isExist);
    }
}

will() + invocation

will()์€ willReturn๊ณผ๋Š” ์กฐ๊ธˆ ๋‹ค๋ฅด๋‹ค.

 

willReturn์€ ๊ณ ์ •๋œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š”๋ฐ, will() ์—์„œ๋Š” invocation ์„ ํ†ตํ•ด์„œ ์ƒˆ๋กœ์šด ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜ ์•„์˜ˆ ์ƒˆ๋กœ์šด ํ–‰๋™์„ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

public class MemberServiceTest {

    // ... ์ƒ๋žต
    @BeforeEach
    void stubbing() { 
        given(memberRepository.findById(anyLong()))
                    .will(invocation -> {
                        Member member = invocatoin.getArgument(0);
                        return MemberData.builder()
                                    .email("test123@gmail.com")
                                    .isFound(true)
                                    .build();
                    });
    }

    @Test
    void exists_valid() {
        MemberData memberData = memberService.findMember(1L);

        assertNotNull(memberData);
    }
}

willThrow()

 

willThrow๋Š” ๋ง ๊ทธ๋Œ€๋กœ ์˜ˆ์™ธ๋ฅผ ๋˜์ง„๋‹ค.

 

public class MemberServiceTest {

    @BeforeEach
    void stubbing() { 
        given(memberRepository.existsByEmail(eq("hello@gmail.com")))
                    .willThrow(MemberDuplicationException("exists");
    }

    @Test
    void exists_valid() {
        MemberDuplicationException exeption = assertThrow( 
            MemberDuplicationException.class,
            () -> memberService.isExistEmail("valid@gmail.com")
        );

        assertEquals(exception.getMessage(), "exists");
    }
}

 

๋Œ“๊ธ€