๐ Test Double์ด๋?
โ
ํ
์คํธ ์ฝ๋์์ ์ค์ ๊ฐ์ฒด๋ฅผ ๋์ฒดํ๋ ๊ฐ์ง ๊ฐ์ฒด
โ
๋
๋ฆฝ์ ์ธ ํ
์คํธ๋ฅผ ์ํํ ์ ์๊ฒ ํจ
ํ์์ฑ
๐น ๋
๋ฆฝ์ฑ ๋ณด์ฅ
์ค์ ๊ฐ์ฒด๊ฐ ์ธ๋ถ DB, ๋คํธ์ํฌ, ํ์ผ ์์คํ
๋ฑ ์ธ๋ถ ์์คํ
์ ์์กด์ฑ์ ๊ฐ์ง๋ฉด ํ
์คํธ๊ฐ ์ํฅ์ ๋ฐ์ ์ ์๋ค.
Test Double์ ์ฌ์ฉํ๋ฉด ์ธ๋ถ ์์์ ๋ถ๋ฆฌํด ๋จ์ ํ
์คํธ๋ฅผ ์ํํ ์ ์๋ค.
๐น ์๋ ํฅ์
์ธ๋ถ ์์คํ
ํธ์ถ์ด ํ์ํ ํ
์คํธ๋ ์คํ ์๋๊ฐ ๋๋ฆฌ๋ค.
Test Double์ ์ฌ์ฉํ๋ฉด ๋น ๋ฅด๊ฒ ์คํํ ์ ์๋ ๊ฐ์ง ๊ฐ์ฒด๋ฅผ ํ์ฉํด ๋น ๋ฅด๊ฒ ํ
์คํธ๋ฅผ ์ํํ ์ ์๋ค.
๐น ํน์ ์ํฉ ์ฝ๊ฒ ์ฌํ
๋คํธ์ํฌ ์ค๋ฅ, ์๋ต ์ง์ฐ ๋ฑ์ ์ํฉ์ ์ฝ๊ฒ ์ฌํํ์ฌ ์๋ฎฌ๋ ์ด์
ํ ์ ์๋ค.
๐ Test Double์ ์ข ๋ฅ
Test Double์ ํฌ๊ฒ ๋ค์ฏ ๊ฐ์ง ์ ํ์ผ๋ก ๋ถ๋ฅ๋๋ค.
Dummy
โ
๋จ์ํ ํ๋ผ๋ฏธํฐ๋ฅผ ์ฑ์ฐ๊ธฐ ์ํด ์ฌ์ฉ
โ
ํ์์ ์ผ๋ก ์๊ตฌ๋์ง๋ง, ์ค์ ์ฌ์ฉํ์ง ์๋ ๊ฐ์ฒด
์์ ์ฝ๋
class DummyUser implements User {
@Override
public String getUsername() {
return null; // ์๋ฌด ๋์๋ ํ์ง ์์
}
}
// ํ
์คํธ ์ฝ๋
@Test
void testDummy() {
NotificationService notificationService = new NotificationService();
notificationService.sendNotification(new DummyUser()); // ์ค์ ์ฌ์ฉ X
}Stub
โ
๋ฏธ๋ฆฌ ์ ํด์ง ๊ฐ์ ๋ฐํํ๋ ๊ฐ์ฒด
โ
๋ณต์กํ ๋ก์ง ์์ด ํ
์คํธ์ ํ์ํ ์ต์ํ์ ๋์๋ง ์ํ
์์ ์ฝ๋
class PaymentServiceStub implements PaymentService {
@Override
public boolean processPayment(double amount) {
return true; // ํญ์ ์ฑ๊ณต ๋ฐํ
}
}
// ํ
์คํธ ์ฝ๋
@Test
void testStub() {
PaymentServiceStub paymentStub = new PaymentServiceStub();
OrderService orderService = new OrderService(paymentStub);
assertTrue(orderService.placeOrder(100.0)); // ํญ์ ์ฑ๊ณต
}Fake
โ
์ค์ ๊ฐ์ฒด์ ๋น์ทํ์ง๋ง, ๊ฐ๋จํ ์ธ๋ฉ๋ชจ๋ฆฌ ์ ์ฅ์ ๋ฑ์ ํ์ฉํด ํ
์คํธ ์ํ
โ
์ค์ ๊ตฌํ์ ๊ฐ๊น์ง๋ง, ์์ ํ ๊ธฐ๋ฅ์ ์ ๊ณตํ์ง ์์
์์ ์ฝ๋
class FakeUserRepository implements UserRepository {
private Map<String, User> database = new HashMap<>();
@Override
public void save(User user) {
database.put(user.getUsername(), user);
}
@Override
public User findByUsername(String username) {
return database.get(username);
}
}
// ํ
์คํธ ์ฝ๋
@Test
void testFake() {
FakeUserRepository fakeRepo = new FakeUserRepository();
User user = new User("testUser");
fakeRepo.save(user);
assertEquals(user, fakeRepo.findByUsername("testUser")); // ์ ์ฅ๋ ๊ฐ์ฒด ๋ฐํ
}Spy
โ
์ค์ ๊ฐ์ฒด๋ฅผ ๊ฐ์ธ ํธ์ถ ์ ๋ณด๋ฅผ ๊ฐ์ํ๋ ๊ฐ์ฒด
โ
์ค์ ๊ฐ์ฒด์ ๋ฉ์๋๋ฅผ ์คํํ๋ฉด์ ํธ์ถ ์ฌ๋ถ, ํ์ ๋ฑ์ ์ ๋ณด ๊ฒ์ฆ ๊ฐ๋ฅ
์์ ์ฝ๋
class SpyList<T> extends ArrayList<T> {
private int addCallCount = 0;
@Override
public boolean add(T element) {
addCallCount++;
return super.add(element);
}
public int getAddCallCount() {
return addCallCount;
}
}
// ํ
์คํธ ์ฝ๋
@Test
void testSpy() {
SpyList<String> spyList = new SpyList<>();
spyList.add("hello");
spyList.add("world");
assertEquals(2, spyList.getAddCallCount()); // add()๊ฐ ๋ช ๋ฒ ํธ์ถ๋์๋์ง ๊ฒ์ฆ
}๐น Mock๊ณผ์ ์ฐจ์ด์
Mock์ ๋ชจ๋ ๋์์ ๊ฐ๋ก์ฑ์ง๋ง, Spy๋ ์ค์ ๋ฉ์๋ ์คํํ๋ค. ๋ฐ๋ผ์ ์ฌ์ด๋ ์ดํํธ๋ฅผ ์ฃผ์ํด์ผ ํ๋ค.
Mock
โ ํธ์ถ์ ๋ํ ๊ธฐ๋๋ฅผ ๋ช ์ธํ๊ณ , ํด๋น ๋ด์ฉ์ ๋ฐ๋ผ ๋์ํ๋๋ก ํ๋ก๊ทธ๋๋ฐ ๋ ๊ฐ์ฒด
์์ ์ฝ๋
@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
public void shouldReturnUserWhenUserIdIsProvided() {
// Given
Long userId = 1L;
User mockUser = new User(userId, "John Doe", "john@example.com");
when(userRepository.findById(userId)).thenReturn(mockUser);
// When
User result = userService.getUserById(userId);
// Then
assertNotNull(result);
assertEquals("John Doe", result.getName());
verify(userRepository).findById(userId);
}
@Test
public void shouldThrowExceptionWhenUserNotFound() {
// Given
Long userId = 2L;
when(userRepository.findById(userId)).thenThrow(new UserNotFoundException("User not found"));
// When & Then
assertThrows(UserNotFoundException.class, () -> userService.getUserById(userId));
verify(userRepository).findById(userId);
}
}๐ ํ์ ๊ฒ์ฆ & ์ํ ๊ฒ์ฆ
ํ์ ๊ฒ์ฆ
ํ ์คํธ ๋์(SUT)์ด ์์๋๋ก ๋ค๋ฅธ ๊ฐ์ฒด์ ์ํธ์์ฉํ๋์ง(ํน์ ๋์์ ์ํํ๋์ง) ํ์ธ
์ํ ๊ฒ์ฆ
ํน์ ์์ ์ํ ํ ๋ฐํ๊ฐ์ด ์์๋๋ก ๋์ค๋์ง ํ์ธ
๋น๊ต
when(userRepository.findById(userId)).thenReturn(mockUser);Mock ๊ฐ์ฒด์ธ userRepository๋ฅผ Stub์ฒ๋ผ ํ๋ํ๊ฒ ํ๋ค.
ํ์ง๋ง, "์ฌ์ ์ ์๋ ๊ธฐ๋"๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ๋ค๋ ์ ์์ Mock ๊ฐ์ฒด์ด๋ค.
assertNotNull(result);
assertEquals("John Doe", result.getName());์ํ ๊ฒ์ฆ์ด์ง๋ง, ๊ทธ ์ํ๊ฐ ํน์ ํ์(UserRepository์ ํธ์ถ)์ ์ํด ๋ง๋ค์ด์ง ๊ฒ์ด๋ฏ๋ก, ๊ฐ์ ์ ์ผ๋ก ํ์ ๊ฒ์ฆ์ ์๋ฏธ๋ ํฌํจ๋ ์ ์๋ค.
verify(userRepository).findById(userId);์ค์ ์ํธ ์์ฉ์ด ์ํ๋์๋์ง ํ์ธํ๋ ํ์ ๊ฒ์ฆ์ด๋ค.
๐น Mock vs Stub
- Stub์ ๋จ์ํ ์ ๋ ฅ๊ฐ์ ๋ํด ๊ณ ์ ๋ ๊ฐ์ ๋ฐํํ๋ค. (์ํ ๊ฒ์ฆ)
- Mock์ ๋ช ์ธ๋๋ก ํ๋ํ๋ค. ๋ฐ๋ผ์ ๋ฐํ๊ฐ ๋ฟ๋ง ์๋๋ผ ์ํธ ์์ฉ์ด ์์๋๋ก ์ํ๋์๋์ง ๊ฒ์ฆํ ์ ์๊ฒ ํ๋ค. (์ํ ๊ฒ์ฆ, ํ์ ๊ฒ์ฆ)
๐ ์ฐธ๊ณ
https://stackoverflow.com/questions/3459287/whats-the-difference-between-a-mock-stub