SpringBoot Part2 - Spring Test ์์ํ๊ธฐ
1. ์ํํธ์จ์ด ํ ์คํ
์ํํธ์จ์ด์ ๊ฒฐํจ์ ์ฐพ๋ ๊ณผ์
https://martinfowler.com/bliki/TestPyramid.html
Testing Level
- Unit tests
- Integration tests
- Component tests
- End-to-end tests
2. ๋จ์ ํ ์คํธ
๊ฐ์ฅ ์์ ๋จ์์ ํ ์คํธ
: ๋ณดํธ์ ์ผ๋ก ํ๋์ class์ ํ๋์ ๋จ์ํ ์คํธ๋ฅผ ์์ฑํ๋ค.
: ๊ณ ๋ฆฝํด์ ํ ์คํธ ํ๋ค.
: ์๋ก์ด ๊ธฐ๋ฅ์ ์ถ๊ฐํ์๋ ๊ธฐ์กด ๊ธฐ๋ฅ๋ค์ด ๋จ์ํ ์คํธ์ ์ํด ์ ์๋ํ๋์ง ํ์ธํ ์ ์๋ค.
: ํ ์คํธ ์ฝ๋(ํ ์คํธ ์ผ์ด์ค) ์์ฒด๊ฐ ๊ธฐ๋ฅ ๋ช ์ธ์๊ฐ ๋ ์ ์๋ค.
: Spring ํ๊ฒฝ์์๋ ์ ๊ณต์ด ๋๋ ํ๋์ class์ ๊ธฐ๋ฅ์ ์ง์คํ ํ ์คํธ๋ฅผ ํด์ผํ๊ธฐ ๋๋ฌธ์ ํฐ ์ด์ ์ ์๋ค.
→ Spring์ ํตํฉ ํ ์คํธ์ ๋ ํฐ ๋์์ ์ค๋ค.
SUT
: System Under Test, ํ ์คํธ ๋์
: ์ ์ฒด ์์คํ ์ด ์๋ ๋จ์ ํ ์คํธ์ ํ๋์ ๋จ์๋ฅผ ์ง์นญํ๋ค.
: ์์คํ ํน์ class์์ ์ ๊ณตํ๋ ๊ธฐ๋ฅ
์์กด๊ด๊ณ์ ์๋ ๋ค๋ฅธ ๊ฐ์ฒด๋ค์ ํ๋ ฅ๊ด๊ณ์๋ผ๊ณ ๋ถ๋ฅด๋ฉฐ, ์ค์ ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด ์ ๋ฌํ๊ธฐ ๋ณด๋ค๋ ํ ์คํธ ๋๋ธ๋ก ๋์ฒดํ์ฌ ์ฃผ์ ํ๋ค
ํ ์คํธ ๋๋ธ
: ์ค์ฌ ๊ฐ์ฒด๊ฐ ์๋ ์์กด ๊ตฌ์ฑ์์๋ฅผ ์ฌ์ฉํ ์ ์์๋ ํ ์คํธ ๋์ ์ฝ๋์ ์ํธ์์ฉํ๋ ๊ฐ์ฒด
: Stub, Mock ๊ฐ์ฒด ๋ฑ์ด ์๋ค.
3. ํตํฉํ ์คํธ
: ํ ์คํธํ๊ณ ์ ํ๋ ์ฝ๋๊ฐ ๋ค๋ฅธ ์์กด๊ด๊ณ์ ์ฐ๋์ด ์ ๋์๋์ง ํ์ธ
4. JUnit
๊ฐ์ฅ ๋ง์ด ์ฌ์ฉํ๋ ์คํ์์ค ํ๋ ์์ํฌ
- ๋งค ๋จ์ ํ ์คํธ์๋ง๋ค ํ ์คํธ ํด๋์ค์ ์ธ์คํด์ค๊ฐ ์์ฑ๋์ด ๋ ๋ฆฝ์ ์ธ ํ ์คํธ๊ฐ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค.
- ์ ๋ ธํ ์ด์ ์ ์ ๊ณตํด์ ํ ์คํธ ๋ผ์ดํ ์ฌ์ดํด์ ๊ด๋ฆฌํ๊ฒ ํด์ฃผ๊ณ ํ ์คํธ ์ฝ๋๋ฅผ ๊ฐ๊ฒฐํ๊ฒ ์์ฑํ๋๋ก ์ง์ํด์ค๋๋ค.
- ํ ์คํธ ๋ฌ๋๋ฅผ ์ ๊ณตํด์ ์ธํ ๋ฆฌ์ ์ด / ์ดํด๋ฆฝ์ค / ๋ฉ์ด๋ธ ๋ฑ์์ ํ ์คํธ ์ฝ๋๋ฅผ ์ฝ๊ฒ ์คํํ ๊ฒ ํด์ค๋๋ค.
- assert๋ก ํ ์คํธ ์ผ์ด์ค์ ์ํ ๊ฒฐ๊ณผ๋ฅผ ํ๋ณํ๊ฒ ํด์ค๋๋ค. → assertEquals(์์ ๊ฐ, ์ค์ ๊ฐ)
- ๊ฒฐ๊ณผ๋ ์ฑ๊ณต(๋ น์), ์คํจ(๋ถ์์) ์ค ํ๋๋ก ํ์ํด์ค๋๋ค
JUnit 4 → ํ๋์ jarํ์ผ๋ก ๊ตฌ์ฑ๋ฌ์ผ๋ฉด ํ์ฅ์ฑ์ด ๋จ์ด์ ธ ์ฐ๋์ด ํ๋ค์๋ค.
JUnit 5 → ํ์ ์ฑ์ ์ํด 3๊ฐ์ ๋ชจ๋๋ก ๋ถ๋ฆฌ๊ฐ ๋์๋ค. Junit Platform, JUnit Jupitor, JUnit Vintage
Junit Platform : ํ ์คํ ํ๋ ์์ํฌ๋ฅผ ๋ฐ์นญํ๊ธฐ ์ํ ๊ธฐ๋ฐ, ํ๋ ํผ์ ์ ๊ณต. ํ ์คํธ๋ฅผ ์คํํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์ฌ์ค
JUnit Jupitor : ์ค์ ๊ตฌํ์ฒด. ํ ์คํธ ์ฝ๋๋ฅผ ๋ฐ๊ฒฌํ๊ณ ์คํํ๋ค.
JUnit Vintage : ๊ธฐ์กด JUnit 4์์ ์์ฑํ ์ฝ๋๋ฅผ ์คํํด์ค.
5. JUnit ์ค์ต
: ์ด๋ป๊ฒ ํด์ผ ๋ง๊ฐํธ๋ฆด ์ ์์์ง ์ฝํธ๋ฅผ ์ง๊ธฐ
: ํ ์คํธ ๋ฉ์๋๋ return ๊ฐ์ด ์์ผ๋ฉด ์๋๋ค.
class FixedAmountVoucherTest { // FixedAmountVoucher -> SUT
private static final Logger logger = LoggerFactory.getLogger(FixedAmountVoucherTest.class);
@BeforeAll
static void setup(){
logger.info("@BeforeAll - ๋จ ํ๋ฒ ์คํ");
}
@BeforeEach
void init(){
logger.info("@BefeorEach - ๋งค ํ
์คํธ ๋ง๋ค ์คํ");
}
@Test
@DisplayName("๊ธฐ๋ณธ์ ์ธ assertEqual ํ
์คํธ ")
@Disabled // ์ด ํ
์คํธ ๊ฑด๋๋ฐ๊ธฐ
void testAssertEqual(){ // ์ด๋ ํ ๊ฐ๋ return ํ๋ฉด ์๋๋ค.
assertEquals("1","1");
}
@Test
@DisplayName("์ฃผ์ด์ง ๊ธ์ก๋งํผ ํ ์ธ์ ํด์ผํ๋ค.")
void testDiscount() {
var sut = new FixedAmountVoucher(UUID.randomUUID(),100);
assertEquals(900, sut.discount(1000));
}
@Test
@DisplayName("ํ ์ธ๋ ํผ์ก์ ๋ง์ด๋์ค๊ฐ ๋ ์ ์๋ค.")
void testMinusDiscountAmount() {
var sut = new FixedAmountVoucher(UUID.randomUUID(),1000);
assertEquals(0, sut.discount(900));
}
@Test
@DisplayName("ํ ์ธ ๊ธ์ก์ ์์๊ฐ ๋์๋ ์๋๋ค")
void testWithMinus(){
assertThrows(IllegalArgumentException.class,() ->new FixedAmountVoucher(UUID.randomUUID(),-100));
// ๋ฐ์๋ ์์ธ class๋ฅผ ์ธ์๋ก ๋ฐ์ ์์ธ๊ฐ ์ ๋ฐ์ํ๋ ํ์ธ : ์์ธ๊ฐ ๋ฐ์ํ์ง ์์ผ๋ฉด ์คํจ
}
@Test
@DisplayName("์ ํจํ ํ ์ธ ๊ธ์ก์ผ๋ก๋ง ์์ฑํ ์ ์๋ค.")
void testVoucherCreation(){
// ์ฌ๋ฌ ์ผ์ด์ค์ ํ
์คํธ๋ฅผ ํ๋ฒ์ ํ
์คํธ
assertAll("FixedAmountVoucher creation",
()-> assertThrows(IllegalArgumentException.class,()-> new FixedAmountVoucher(UUID.randomUUID(),0)),
()-> assertThrows(IllegalArgumentException.class,()-> new FixedAmountVoucher(UUID.randomUUID(),-100)),
()-> assertThrows(IllegalArgumentException.class,()-> new FixedAmountVoucher(UUID.randomUUID(),10000000)));
}
}
TTD ?
DDD ?
→ ์ด๋ค ๋ฐฉ์์ ํ๋ ํ ์คํธ ์ฝ๋๋ฅผ ๊ผญ ์์ฑํด์ผํ๋ค.
hamcrest
: ๋์ฑ ๋ค์ํ ๊ฒ์ฆ ๋ก์ง์ ์ ๊ณตํด์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import static org.junit.jupiter.api.Assertions.*;
public class HamcrestAssertionTests {
@Test
@DisplayName("์ฌ๋ฌ hamcrest matcher ํ
์คํธ")
void hamcrestTest() {
assertEquals(2, 1 + 1);
assertThat(1 + 1, equalTo(2)); //MatcherAssert ๋ฉ์๋
assertThat(1 + 1, is(2));
assertThat(1 + 1, anyOf(is(2), is(3)));
assertNotEquals(1,1+1);
assertThat(1+1, not(equalTo(1)));
}
// list๋ collection์ ํ
์คํธ ํ๊ธฐ ์ํ ํธ๋ฆฌ์ฑ์ ์ ๊ณตํ๋ค.
@Test
@DisplayName("์ปฌ๋ ์
์ ๋ํ matcher ํ
์คํธ")
void hamcrestListMatcherTest(){
var prices = List.of(1,2,3);
assertThat(prices, hasSize(3));
assertThat(prices, everyItem(greaterThan(0)));
assertThat(prices, containsInAnyOrder(1,2,3));
assertThat(prices, hasItem(greaterThanOrEqualTo(2)));
}
}
6. Mock Object
: ๋ชจ์ ๊ฐ์ฒด!
https://khalilstemmler.com/wiki/test-doubles/
stub → ๋ง๊ทธ๋๋ก ๊ฐ์ง ๊ฐ์ฒด
mock → ํธ์ถ์ ๋ํ ๊ธฐ๋๋ฅผ ๋ช ์ธ. ํ์์ ์ง์ค
๋ชฉ ๊ฐ์ฒด๋ ํ์๊ฒ์ฆ์ ์ฌ์ฉํ๊ณ , stub๋ฅผ ํฌํจํ ๋ค๋ฅธ ๋์ญ๋ค์ ์ํ ๊ฒ์ฆ์ ์ฌ์ฉํ๋ค.
์ํ ๊ฒ์ฆ : ๋ฉ์๋๊ฐ ์ํ๋ ํ, ๊ฐ์ฒด์ ์ํ๋ฅผ ํ์ธํ์ฌ ์ฌ๋ฐ๋ฅด๊ฒ ๋์ํ๋์ง๋ฅผ ํ์ธํ๋ ๊ฒ์ฆ๋ฒ. stub์ผ๋ก ํ์ธ
ํ์ ๊ฒ์ฆ : ๋ฉ์๋์ ๋ฆฌํด ๊ฐ์ ํ๋จํ ์ ์๋ ๊ฒฝ์ฐ ํน์ ๋์์ ์ํํ๋์ง ํ์ธํ๋ ๊ฒ์ฆ๋ฒ. ํน์ ๋ฉ์๋๊ฐ ํธ์ถ๋์๋์ง ํ์ธ
Mock Object ์์ฑ์ ๋์์ฃผ๋ TestFramework
- Mockito
- JMock
- EasyMock
7. ์ ๋ํ ์คํธ ์ค์ต
import static org.junit.jupiter.api.Assertions.*;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
class OrderServiceTest {
class OrderRepositoryStub implements OrderRepository{
@Override
public Order insert(Order order) {
return null;
}
}
// ์ํ์ ์ง์ค์ ํ ํ
์คํธ
@Test
@DisplayName("order๊ฐ ์์ฑ๋์ผ์ํ๋ค. (stub)")
void TestCreateOrder() {
// Given - ์ฌ์ฉํ ๊ฐ์ฒด ๋ง๋ค๊ธฐ
var voucherRepository = new MemoryVoucherRepository();
var fixedAmountVoucher = new FixedAmountVoucher(UUID.randomUUID(),100);
voucherRepository.insert(fixedAmountVoucher);
var sut = new OrderService(new VoucherService(voucherRepository),new OrderRepositoryStub());
// When
var order = sut.createOrder(UUID.randomUUID(), List.of(new OrderItem(UUID.randomUUID(), 200, 1)),fixedAmountVoucher.getVoucherId());
//Then
assertThat(order.totalAmount(), is(100L));
assertThat(order.getVoucher().isEmpty(), is(false));
assertThat(order.getVoucher().get().getVoucherId(), is(fixedAmountVoucher.getVoucherId()));
assertThat(order.getOrderStatus(), is(OrderStatus.ACCEPTED));
}
@Test
@DisplayName("order๊ฐ ์์ฑ๋์ผ์ํ๋ค. (mock)")
void TestCreateOrderByMock() {
// Given - ์ด๋ฌํ ์ํฉ์ ๋ง๋ฌ
var voucherServiceMock = mock(VoucherService.class);
var orderRepositoryMock = mock(OrderRepository.class);
var fixedAmountVoucher = new FixedAmountVoucher(UUID.randomUUID(),100);
when(voucherServiceMock.getVoucher(fixedAmountVoucher.getVoucherId())).thenReturn(fixedAmountVoucher);// ํด๋น๋ถ๋ถ๋ง ๋์ํ๋ค.
var sut = new OrderService(voucherServiceMock,orderRepositoryMock);
// When - order๋ง๋ค๊ธฐ
var order = sut.createOrder(UUID.randomUUID(), List.of(new OrderItem(UUID.randomUUID(), 200, 1)),
fixedAmountVoucher.getVoucherId());
// Then - order์ ๋ํ ์ํ๊ฒ์ฌ, orderservice์์์ ์ฌ์ฉ๋๋ mock๋ค์ด ์ด๋ ํ ํ๋์ ํ๋์ง ๊ฒ์ฆ
assertThat(order.totalAmount(), is(100L));
assertThat(order.getVoucher().isEmpty(), is(false));
var inOrder = inOrder(voucherServiceMock, orderRepositoryMock);//mock์ ๋ํ ์์๋ฅผ ๋ณด์ฅํ ์์๋ค.
// ํ์์ ๋ํ assert๊ฐ ์๋๋ผ ์ด๋ ํ ๋ฉ์๋๊ฐ ํธ์ถ๋ฌ๋์ง verify
inOrder.verify(voucherServiceMock).getVoucher(fixedAmountVoucher.getVoucherId());
inOrder.verify(orderRepositoryMock).insert(order);
inOrder.verify(voucherServiceMock).useVoucher(fixedAmountVoucher);
}
}
: mock์ ํตํด ๋ณ๋์ ์ฝ๋๋ฅผ ์ถ๊ฐํ์ง์๊ณ ๋ฉ์๋ ํธ์ถ์ฌ๋ถ๋ฅผ ๊ฒ์ฌํ ์ ์๋ค.
8. Spring Test
Spring์ JUnit ์ง์
https://docs.spring.io/spring-framework/docs/current/reference/html/testing.html
- ๋จ์ํ ์คํธ ์ง์
→ but ๋น์ง๋์ค๋ก์ง์ ํ๋ ์์ํฌ์ ์์ฑํ๋ ๊ฒ์ด ์ข๋ค.
- ํตํฉํ ์คํธ ์ง์
: ์๋ฒ๋ฅผ ๋์ฐ์ง์๊ณ spring container ๋ด์์ ๊ฐ๊ฐ์ bean๋ค๊ฐ์ ์ฐ๋์ ํ ์คํธ ํ ๋ ์ฌ์ฉ๋๋ค.
Spring TestContext
: ์ด๋ ธํ ์ด์ ์ ์ด์ฉํด์ applicationContext๋ฅผ ๋ง๋ค์ ์๋ค.
: ์บ์๊ธฐ๋ฅ์ด ์์ด ๋์ผํ ๋ด์ฉ์ ์ปจํ ์ด๋๋ผ๋ฉด ์ฌ์ฌ์ฉ์ด ๊ฐ๋ฅํ์ฌ ๋น ๋ฅด๊ฒ ๊ตฌ๋ ๊ฐ๋ฅํ๋ค.
9. Spring Test ์ค์ต
//@ExtendWith(SpringExtension.class) //test context์ ์ํธ์์ฉํ๊ฒ ํด์ค
//@ContextConfiguration
@SpringJUnitConfig
@ActiveProfiles("local")
public class KdtSpringContextTest {
@Configuration
@ComponentScan
static class Config{
}
@Autowired
ApplicationContext applicationContext;
@Autowired
OrderService orderService;
@Autowired
VoucherRepository voucherRepository;
@Test
@DisplayName("application context๊ฐ ์์ฑ๋๋ค.")
public void testApplicationContext(){
assertThat(applicationContext, notNullValue());
}
@Test
@DisplayName("VoucherRepository๊ฐ ๋น์ผ๋ก ๋ฑ๋ก๋์ด์์ด์ผํ๋ค.")
public void testVoucherRepository(){
var been = applicationContext.getBean(VoucherRepository.class);
assertThat(been, notNullValue());
}
@Test
@DisplayName("OrderService๋ฅผ ์ฌ์ฉํด์ ์ฃผ๋ฌธ์ ์์ฑํ ์์๋ค.")
public void testOrderService(){
// Given
var fixedAmountVoucher = new FixedAmountVoucher(UUID.randomUUID(),100);
voucherRepository.insert(fixedAmountVoucher);
// When
var order = orderService.createOrder(UUID.randomUUID(), List.of(new OrderItem(UUID.randomUUID(), 200, 1)),
fixedAmountVoucher.getVoucherId());
//Then
assertThat(order.totalAmount(), is(100L));
assertThat(order.getVoucher().isEmpty(), is(false));
assertThat(order.getVoucher().get().getVoucherId(), is(fixedAmountVoucher.getVoucherId()));
assertThat(order.getOrderStatus(), is(OrderStatus.ACCEPTED));
}
}
์ถ์ฒ - ํด๋ฆฌ : SpringBoot Part1
'Back-end ๋ฐ๋ธ์ฝ์ค > week 03 - 05 TIL (Spring)' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[TIL] 221109 - SpringBoot Part2 : Spring์ JDBC์ง์ (0) | 2022.11.10 |
---|---|
[TIL] 221108 - SpringBoot Part2 : JDBC (0) | 2022.11.08 |
[TIL] 221104 - SpringBoot Part1 : logging, SpringBoot (2) | 2022.11.06 |
[TIL] 221103 - SpringBoot Part1 - ์ ํ๋ฆฌ์ผ์ด์ ์์ฑ ๊ด๋ฆฌ (0) | 2022.11.06 |
[TIL] 221102 - SpringBoot Part1 : Dependecy injection, ์ปดํฌ๋ํธ ์ค์บ (0) | 2022.11.03 |
๋๊ธ