DevLog ๐Ÿ˜ถ

[Java] Classic TDD vs Mockist TDD ๋ณธ๋ฌธ

โœ๏ธ/Java

[Java] Classic TDD vs Mockist TDD

dolmeng2 2023. 3. 28. 08:59

์–ผ๋งˆ ์ „์— ์šฐํ…Œ์ฝ”์—์„œ ์ฒด์Šค ํ”ผ๋“œ๋ฐฑ 2 ๊ฐ•์˜๋ฅผ ๋“ค์œผ๋ฉด์„œ, ๋””ํŠธ๋กœ์ดํŠธ ํ•™ํŒŒ (classicist) vs ๋Ÿฐ๋˜ ํ•™ํŒŒ (Mockist)์— ๋Œ€ํ•ด์„œ ๋“ฃ๊ฒŒ ๋˜์—ˆ๋‹ค. ๋‘˜ ๋‹ค ์ฒ˜์Œ ๋“ค์–ด๋ณธ ์šฉ์–ด์—ฌ์„œ ๊ถ๊ธˆํ•œ ๋งˆ์Œ์— ์ด๊ฒƒ์ €๊ฒƒ ์ฐพ์•„๋ณด๋ฉฐ ์ž‘์„ฑํ•ด๋ณด๊ณ ์ž ํ•œ๋‹ค. 

(์ „๋ฐ˜์ ์ธ ๋‚ด์šฉ์€ ํ…Œ์ฝ”ํ†ก ๋ฐœํ‘œ ๋‚ด์šฉ์„ ๋งŽ์ด ์ฐธ๊ณ ํ•˜์˜€์Šต๋‹ˆ๋‹ค ๐Ÿ™‡‍โ™€๏ธ)


โœ”๏ธ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๊ฐ€ ๋ฌด์—‡์ผ๊นŒ?

๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํŠน์ง•์„ ๊ฐ€์ ธ์•ผ ํ•œ๋‹ค.

1. Low-level ํ…Œ์ŠคํŠธ๋ฅผ ๋‹ค๋ฃจ์–ด์•ผ ํ•œ๋‹ค.
2. ์ผ๋ฐ˜์ ์ธ ํ…Œ์ŠคํŠธ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.
3. ๋นจ๋ผ์•ผ ํ•œ๋‹ค.

 

์—ฌ๊ธฐ์„œ ๋‹จ์œ„๋ž€ ๋ฌด์—‡์ผ๊นŒ?

๋งŒ์•ฝ, ๊ธฐ๋Šฅ ๋‹จ์œ„๋ผ๊ณ  ๊ตฌ์„ฑ๋œ๋‹ค๋ฉด ๊ฐ์ฒด์ง€ํ–ฅ์˜ ๊ด€์ ์—์„œ๋Š” ํ•ด๋‹น ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๋Š” ํด๋ž˜์Šค์˜ ์ง‘ํ•ฉ์ด ๋‹จ์œ„๊ฐ€ ๋  ์ˆ˜๋„ ์žˆ๋‹ค.

 

์šฐ๋ฆฌ๋Š” ์ด๋Ÿฌํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด์„œ, ์–ด๋– ํ•œ ๋‹จ์œ„๊ฐ€ ์ƒํ˜ธ ์ž‘์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์‹ค์ œ ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, test double์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋Š” ํฌ๊ฒŒ 2๊ฐ€์ง€์˜ ์œ ํ˜•์œผ๋กœ ๋‚˜๋‰˜๋Š”๋ฐ, ์ด๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

https://www.xenonstack.com/blog/microservices-testing

1. Sociable Test

: ์ด๋Š” ์–ด๋– ํ•œ ๋‹จ์œ„์˜ 'ํ–‰์œ„'๋ฅผ ํ…Œ์ŠคํŠธํ•˜๋Š”๋ฐ ์ค‘์ ์„ ๋‘”๋‹ค. ์ธํ„ฐํŽ˜์ด์Šค ๋“ฑ์„ ํ™œ์šฉํ•˜์—ฌ ์ง์ ‘์ ์œผ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

test double์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์ƒ์œ„ ๋ชจ๋“ˆ์„ ํ†ตํ•ด์„œ ํ•˜์œ„ ๋ชจ๋“ˆ์„ ์ง์ ‘์ ์œผ๋กœ ํ…Œ์ŠคํŠธํ•œ๋‹ค.

 

2. Solitary Test

: ์–ด๋– ํ•œ ๋‹จ์œ„์˜ ํ–‰์œ„๊ฐ€ ๋‹ค๋ฅธ ํ–‰์œ„์™€ ์ข…์†๋˜์–ด ์žˆ๋‹ค๋ฉด, ์ตœ๋Œ€ํ•œ ๋…๋ฆฝ์ ์œผ๋กœ ๊ฐ€์ ธ๊ฐ€์„œ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

์ด๋•Œ, ๋…๋ฆฝ์ ์œผ๋กœ ๊ฐ€์ ธ๊ฐ€๊ธฐ ์œ„ํ•ด ๋‹ค๋ฅธ ํ–‰์œ„์— ๋Œ€ํ•ด์„œ test double์„ ์‚ฌ์šฉํ•˜๊ณค ํ•œ๋‹ค.

 

ํ”ํžˆ ์„œ๋น„์Šค ๊ณ„์ธต์— ๋Œ€ํ•ด์„œ ํ…Œ์ŠคํŠธ๋ฅผ ํ•  ๋•Œ, ๋‹ค์–‘ํ•œ ์„œ๋น„์Šค ๊ณ„์ธต์ด ์—ฎ์—ฌ ์žˆ์œผ๋ฉด ๊ธฐ๋Šฅ ๋‹จ์œ„๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๋”๋ผ๋„ ๋‹ค๋ฅธ ์„œ๋น„์Šค ๊ณ„์ธต์˜ ๊ธฐ๋Šฅ๊นŒ์ง€ ํ•จ๊ป˜ ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๋ฉด์„œ ์ผ์ข…์˜ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ์ฒ˜๋Ÿผ ๋Š๊ปด์งˆ ์ˆ˜ ์žˆ๋‹ค.

 

sociable test๋ฅผ ์ง€ํ–ฅํ•˜๋Š” ์‚ฌ๋žŒ๋“ค์„ classicist, solitary test๋ฅผ ์ง€ํ–ฅํ•˜๋Š” ์‚ฌ๋žŒ๋“ค์„ mockist๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.


โœ”๏ธ ์šฉ์–ด ์ •๋ฆฌ

์‹œ์ž‘ํ•˜๊ธฐ ์•ž์„œ, ํ…Œ์ŠคํŠธ ํŒจํ„ด์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์šฉ์–ด๋“ค์— ๋Œ€ํ•ด ๊ฐ„๋žตํ•˜๊ฒŒ ์ •๋ฆฌํ•˜๊ณ  ๊ฐ€์ž.

 

๐Ÿ’ฌ SUT

: System Under Test, ํ…Œ์ŠคํŠธ ๋Œ€์ƒ ์‹œ์Šคํ…œ์œผ๋กœ ์ผ์ข…์˜ ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๋ ค๋Š” ๋Œ€์ƒ์„ ์˜๋ฏธํ•œ๋‹ค.

์„ธ๋ถ€์ ์œผ๋กœ AUT (application Under Test), MUT (Method Under Test), CUT (Class Under Test), OUT (Object Under Test) ๋“ฑ์œผ๋กœ ๋‚˜๋‰˜๊ธฐ๋„ ํ•œ๋‹ค.

 

๐Ÿ’ฌ ํ˜‘๋ ฅ ๊ฐ์ฒด

SUT๊ฐ€ ์˜์กดํ•˜๋Š” ํด๋ž˜์Šค๋กœ, SUT์™€ ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ํด๋ž˜์Šค๋ฅผ ์˜๋ฏธํ•œ๋‹ค.

 


 

โœ”๏ธ Test Double

https://tecoble.techcourse.co.kr/post/2020-09-19-what-is-test-double/

 

์‹ค์ œ ๊ฐ์ฒด์˜ ํ–‰์œ„๊ฐ€ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์–ด๋ ต๊ฑฐ๋‚˜ ๋น„์šฉ์ด ๋งŽ์ด ๋“ค ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๊ฐ€์งœ ๊ฐ์ฒด์ด๋‹ค.

๋Œ€ํ‘œ์ ์œผ๋กœ๋Š” Dummy, Fake, Mock, Spy, Stub ๋“ฑ์ด ์กด์žฌํ•œ๋‹ค.

 

๊ฐ๊ฐ์— ๋Œ€ํ•ด ๊ฐ„๋‹จํ•˜๊ฒŒ ์„ค๋ช…ํ•ด๋ณด์ž๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค. ๊ฐ„๋‹จํ•œ ์˜ˆ์‹œ๋ฅผ ๋“ค๊ธฐ ์œ„ํ•ด์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž.

import java.util.Optional;

public interface UserDao {
    Optional<UserEntity> findByName(final String name);

    Long save(final String name);
}

// UserEntity
public final class UserEntity {
    private Long id;
    private final String name;

    public UserEntity(final Long id, final String name) {
        this.id = id;
        this.name = name;
    }
}

์ด๋ฒˆ ์ฒด์Šค ๋ฏธ์…˜์—์„œ ์‚ฌ์šฉํ–ˆ๋˜ ์ฝ”๋“œ๋กœ, ์‚ฌ์šฉ์ž์˜ ์ด๋ฆ„์„ ๋ฐ”ํƒ•์œผ๋กœ ์กฐํšŒํ•˜๋Š” ๊ธฐ๋Šฅ๊ณผ ์ €์žฅํ•˜๋Š” ๊ธฐ๋Šฅ์ด ๋‹ด๊ฒจ์žˆ๋‹ค.

์œ„์˜ ์ฝ”๋“œ๋Š” ์‹ค์ œ ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ตฌํ˜„๋˜์–ด ์žˆ๋‹ค.

public final class UserDaoImpl implements UserDao {

    private final JdbcTemplate jdbcTemplate;

    public UserDaoImpl(final JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public Optional<UserEntity> findByName(final String name) {
        final String query = "SELECT * FROM user WHERE user_name = ?";
        return jdbcTemplate.findOne(query, (resultSet -> new UserEntity(
                resultSet.getLong("user_id"),
                resultSet.getString("user_name"))
        ), name);
    }

    @Override
    public Long save(final String name) {
        final String query = "INSERT INTO user(user_name) VALUES(?)";
        return jdbcTemplate.executeUpdate(query, name);
    }
}

 

์ด ์ฝ”๋“œ๋ฅผ ๊ฐ๊ฐ์˜ ํ…Œ์ŠคํŠธ ๋”๋ธ”๋กœ ์ ์šฉํ•ด๋ณด์ž.

 

1. ์Šคํ… (Stub)

- ํ˜ธ์ถœ์ด ๋˜์—ˆ์„ ๋•Œ ๋ฏธ๋ฆฌ ์ •ํ•ด์ง„ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฐ์ฒด์ด๋‹ค.

public class MockUserDao implements UserDao {
    @Override
    public Optional<UserEntity> findByName(final String name) {
        return Optional.of(new UserEntity(1L, "journey"));
    }

    @Override
    public Long save(final String name) {
        return 1L;
    }
}

์œ„์™€ ๊ฐ™์ด, ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ํ•ญ์ƒ ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

 

 

2. ํŽ˜์ดํฌ (Fake)

- ์‹ค์ œ๋กœ ๋™์ž‘ํ•˜์ง€๋งŒ, ์กฐ๊ธˆ ๋” ๊ฐ€๋ฒผ์šด ๋ฒ„์ „์˜ ๊ตฌํ˜„์„ ์‚ฌ์šฉํ•˜์—ฌ ํ…Œ์ŠคํŠธ์˜ ์†๋„๋ฅผ ๋†’์ด๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

public class MockUserDao implements UserDao {
    private final Map<Long, UserEntity> STORAGE = new ConcurrentHashMap<>();
    private final AtomicLong pk = new AtomicLong(0L);

    @Override
    public Optional<UserEntity> findByName(final String name) {
        return STORAGE.entrySet().stream()
                .filter(entry -> entry.getValue().getName().equals(name))
                .map(entry -> new UserEntity(entry.getKey(), entry.getValue().getName()))
                .findFirst();
    }

    @Override
    public Long save(final String name) {
        STORAGE.put(pk.addAndGet(1L), new UserEntity(name));
        return pk.longValue();
    }
}

์‹ค์ œ๋กœ ๋‚˜๋Š” ์ด๋ฒˆ ์ฒด์Šค ๋ฏธ์…˜์—์„œ ์œ„์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ•˜์˜€๋‹ค.

ConcurrentHashMap ๊ฐ์ฒด๋ฅผ ์ผ์ข…์˜ DB ์Šคํ† ๋ฆฌ์ง€๋กœ ์‚ฌ์šฉํ•˜์—ฌ์„œ ์‹ค์ œ๋กœ ๊ฐ’์„ ์‚ฝ์ž…ํ•˜๊ณ , ์กฐํšŒํ•˜๋Š” ๊ธฐ๋Šฅ์ด ๋™์ž‘ํ•˜๋„๋ก ๋งŒ๋“ค์—ˆ๋‹ค.

 

 

3. ์ŠคํŒŒ์ด (Spy)

- Stub์˜ ์—ญํ• ์„ ๊ฐ€์ง€๋ฉด์„œ, ํ˜ธ์ถœ๋œ ํ•จ์ˆ˜์˜ ์ •๋ณด๋ฅผ ๊ธฐ๋กํ•˜์—ฌ ๋‚˜์ค‘์— ํ…Œ์ŠคํŠธ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ์ฒด๋ฅผ ์˜๋ฏธํ•œ๋‹ค.

public class MockUserDao implements UserDao {
    private final List<String> queries = new ArrayList<>();

    @Override
    public Optional<UserEntity> findByName(final String name) {
        queries.add("findByName");
        return Optional.of(new UserEntity(1L, "journey"));
    }

    @Override
    public Long save(final String name) {
        queries.add("save");
        return 1L;
    }

    public boolean isCall(final String query) {
        return queries.contains(query);
    }
}

๋‚ด๊ฐ€ ์ดํ•ดํ•œ ๋ฐ”๋กœ๋Š” ์ด๋Ÿฐ ์‹์œผ๋กœ Stub์˜ ๊ธฐ๋Šฅ์— ์–ด๋–ค ์‹์œผ๋กœ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€์— ๋Œ€ํ•œ ์ •๋ณด๋„ ํฌํ•จํ•˜๋Š” ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.

๊ทธ๋ž˜์„œ ๋‚˜์ค‘์— ์™ธ๋ถ€์ ์œผ๋กœ isCall์„ ํ†ตํ•ด์„œ ํ•ด๋‹น ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๊ฒŒ ์•„๋‹๊นŒ ์‹ถ๋‹ค.

 

 

4. ๋”๋ฏธ (Dummy) 

- ๋‹จ์ง€ ์ธ์Šคํ„ด์Šคํ™”๋œ ๊ฐ์ฒด๊ฐ€ ํ•„์š”ํ•˜๋ฉด์„œ, ํ•ด๋‹น ๊ฐ์ฒด์˜ ๊ธฐ๋Šฅ์€ ํ•„์š”์—†์ด ์‹ค์ œ๋กœ ์‚ฌ์šฉ๋˜์ง€ ์•Š์„ ๊ฒฝ์šฐ ์‚ฌ์šฉํ•œ๋‹ค.

์œ„์˜ ์˜ˆ์ œ ์ฝ”๋“œ๋ณด๋‹ค๋Š” ๋‹ค๋ฅธ ๊ฒฝ์šฐ๊ฐ€ ๋” ์ ํ•ฉํ•  ๊ฒƒ ๊ฐ™์•„์„œ, ์˜ˆ์ œ๋ฅผ ๋ฐ”๊พธ์–ด๋ณด์•˜๋‹ค.

public class MockUserDao {
    public void printUserInfo() {
        // nothing.
    }
}

์ด๋Ÿฐ ์‹์œผ๋กœ ๋‹จ์ˆœํžˆ printUserInfo()๋ฅผ ํ˜ธ์ถœํ–ˆ๋‹ค๋Š” ๊ฒƒ๋งŒ ํ•„์š”ํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.

๊ฐœ์ธ์ ์ธ ์ƒ๊ฐ์œผ๋กœ๋Š”, ์™ธ๋ถ€ api์— ๋Œ€ํ•ด์„œ ํ˜ธ์ถœ์„ ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ (ex. ์•Œ๋ฆผ ์ „์†ก ๊ฐ™์ด ๋ฐ˜ํ™˜๊ฐ’์€ ํ•„์š” ์—†๋Š” ๊ฒฝ์šฐ)๋‚˜ ๋ณต์žกํ•œ ui ๋กœ์ง์ด ํ•„์š”ํ•˜๋‹ค๋ฉด ๋”๋ฏธ๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์œผ๋กœ ํŒ๋‹จ๋œ๋‹ค.

 

 

5. ๋ชฉ (Mock)

ํ˜ธ์ถœ์— ๋Œ€ํ•ด์„œ ์–ด๋–ค ๊ฒƒ์„ ํ˜ธ์ถœํ•˜๊ณ  ์–ด๋–ค ๊ฒƒ์„ ๋ฐ˜ํ™˜ํ• ์ง€ ๋ฏธ๋ฆฌ ์ง€์ •ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

๋Œ€ํ‘œ์ ์œผ๋กœ Mockito ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์กด์žฌํ•œ๋‹ค. ์ด ์—ญ์‹œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

UserDao userDao = mock(UserDao.class);

// ๋ฌด์—‡์„ ํ˜ธ์ถœํ•˜๋ฉด ์–ด๋–ค ์‹์œผ๋กœ ๋ฐ˜ํ™˜ํ• ์ง€ ๋ฏธ๋ฆฌ ์ •์˜
        when(userDao.findByName(any())).thenReturn(new UserEntity(1L, "journey"));
        when(userDao.save(any())).thenReturn(1L);

 

ํ”ํžˆ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋‚˜๋ˆ„์–ด์ง„๋‹ค๊ณ ๋„ ํ•œ๋‹ค. (์–ด๋ ค์šด ๋‚ด์šฉ์ด๋‹ˆ ์šฐ์„ ์€ ๊ทธ๋ƒฅ ์ฝ๊ณ  ๋„˜์–ด๊ฐ€์ž!)

์ด๋ฆ„ ๋ชฉ์  ๋™์ž‘์ด ์žˆ๋‚˜? SUT์—
๊ฐ„์ ‘ ์ž…๋ ฅ ์ฃผ์ž…
SUT
๊ฐ„์ ‘ ์ถœ๋ ฅ ์ฒ˜๋ฆฌ
ํ…Œ์ŠคํŠธ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ฐ’
Stub SUT์˜
๊ฐ„์ ‘ ์ž…๋ ฅ ๊ฒ€์ฆ
์žˆ์Œ ํ•จ ๋ฌด์‹œ ์ž…๋ ฅ๊ฐ’
Fake ๋น ๋ฅธ ์‹คํ–‰ ์žˆ์Œ ์—†์Œ ๊ฐ„์ ‘ ์ถœ๋ ฅ ์‚ฌ์šฉ ์—†์Œ
Spy SUT์˜
๊ฐ„์ ‘ ์ถœ๋ ฅ ๊ฒ€์ฆ
์žˆ์Œ ์„ ํƒ ์‚ฌํ•ญ ๋‚˜์ค‘์— ๊ฒ€์ฆํ•˜๋ ค๊ณ  ํ•จ ์ž…๋ ฅ (์„ ํƒ ์‚ฌํ•ญ)
Dummy ์†์„ฑ์ด๋‚˜ ๋ฉ”์„œ๋“œ ์ธ์ž๋กœ์„œ ์ž‘๋™ ์—†์Œ ์•ˆ ํ•จ ์•ˆ ํ•จ ์—†์Œ
Mock SUT์˜
๊ฐ„์ ‘ ์ถœ๋ ฅ ๊ฒ€์ฆ
์žˆ์Œ ์„ ํƒ ์‚ฌํ•ญ ๊ธฐ๋Œ€๊ฐ’๊ณผ ๋น„๊ตํ•˜์—ฌ
์ •ํ™•ํ•œ์ง€ ๊ฒ€์ฆํ•จ 
์ž…๋ ฅ, ๊ธฐ๋Œ€ ์ถœ๋ ฅ

 

 


โœ”๏ธ Classicist - ์ƒํƒœ ๊ฒ€์ฆ / Mockist - ํ–‰์œ„ ๊ฒ€์ฆ

classicist์—์„œ๋Š” ์ฃผ๋กœ ๊ฒ€์ฆ์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์–ด๋– ํ•œ ์ƒํ™ฉ์„ ์ˆ˜ํ–‰ํ•œ ๋‹ค์Œ, ํ•ด๋‹น ๊ฐ์ฒด์˜ ๋‚ด๋ถ€ ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜๊ฒŒ ๋œ๋‹ค.

์ด๋•Œ, ํ–‰์œ„๊ฐ€ ์ˆ˜ํ–‰๋œ ๋‹ค์Œ ์–ด๋– ํ•œ ์ƒํƒœ์ธ์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด์„œ getter ๊ฐ™์€ ๋ฉ”์„œ๋“œ ๋“ฑ์ด ์ถ”๊ฐ€๋  ์ˆ˜ ์žˆ๋‹ค.

์œ„์—์„œ ๋งํ–ˆ๋˜ 'stub'์˜ ๊ฒฝ์šฐ ์ƒํƒœ ๊ฒ€์ฆ์˜ ์ผ๋ถ€๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

public class CrewServiceTest {

    private CrewService crewService;
    private CrewRepository crewRepository;

    @Before
    public void setUp() {
        crewRepository = mock(CrewRepository.class);
        crewService = new CrewService(crewRepository);
    }

    @Test
    public void createCrew() {
        // given
        Crew crew = new Crew("journey");

        // when
        crewRepository.createCrew(crew);

        // then
        Crew createdCrew = crewRepository.findByName("journey");
        assertThat(createdCrew.getName()).isEqualTo("journey");
    }
}

createCrew()๋ฅผ ํ†ตํ•ด์„œ ํ•ด๋‹น ํฌ๋ฃจ๊ฐ€ ์ž˜ ์ƒ์„ฑ๋˜์—ˆ๋Š”์ง€ ํ…Œ์ŠคํŠธ ํ•˜๋Š” ์ฝ”๋“œ์ด๋‹ค.

crew๋ฅผ ์‚ฝ์ž…ํ•œ ๋‹ค์Œ, ํฌ๋ฃจ์˜ ์ด๋ฆ„์„ ํ†ตํ•ด์„œ ํ•ด๋‹น ํฌ๋ฃจ๋ฅผ ์ฐพ์€ ๋‹ค์Œ getName()์„ ํ†ตํ•ด์„œ ์šฐ๋ฆฌ๊ฐ€ ์ƒ์„ฑํ•œ ํฌ๋ฃจ์ธ์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด๋Ÿฐ ์‹์œผ๋กœ ํ•ด๋‹น ํฌ๋ฃจ๋ฅผ ์‚ฝ์ž…ํ•œ ๋’ค, ๋‚ด๋ถ€์˜ ๊ฐ์ฒด๊ฐ€ ์ž˜ ์ƒ์„ฑ๋˜์—ˆ๋Š”์ง€ ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

 

๋ฐ˜๋ฉด์— Mockist์˜ ๊ฒฝ์šฐ ํŠน์ •ํ•œ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๊ฒ€์ฆ ๋ฐฉ๋ฒ•์ด๋‹ค.

์œ„์—์„œ ๋งํ–ˆ๋˜ 'mock'์˜ ๊ฒฝ์šฐ ํ–‰์œ„ ๊ฒ€์ฆ์˜ ์ผ๋ถ€๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

public class CrewServiceTest {

    private CrewService crewService;
    private CrewRepository crewRepository;

    @Before
    public void setUp() {
        crewRepository = mock(CrewRepository.class);
        crewService = new CrewService(crewRepository);
    }

    @Test
    public void createCrew() {
        // given
        Crew crew = new Crew("journey");
        given(crewRepository.save(crew)).willReturn(crew);

        // when
        crewRepository.createCrew(crew);

        // then
        verify(crewRepository).save(crew);
    }
}

์œ„์˜ ์ฝ”๋“œ์—์„œ CrewService๋Š” CrewRepository๋ฅผ ์˜์กดํ•˜๊ณ  ์žˆ๋Š” ํ˜•ํƒœ์ด๋‹ค. 

 

์ด๋•Œ, ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์—์„œ Crew ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ , save ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ์ƒ์„ฑํ•œ crew๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ•ด๋ณด์ž.

์ด๋•Œ, verify๋ฅผ ํ†ตํ•ด์„œ crew ๊ฐ์ฒด์™€ ํ•จ๊ป˜ ํ•ด๋‹น save ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•œ๋‹ค. 

save() ๋ฉ”์„œ๋“œ์˜ ํ˜ธ์ถœ์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ–ˆ๋‹ค๊ณ ๋„ ๋ณผ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋งŒ์•ฝ ์œ„์˜ ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ–ˆ๋‹ค๋ฉด save() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š์•˜๊ฑฐ๋‚˜, ์ธ์ž๋กœ ๋„˜๊ธด crew ๊ฐ์ฒด๊ฐ€ ์ž˜๋ชป๋˜์—ˆ์Œ์„ ๋œปํ•˜๊ฒŒ ๋œ๋‹ค.

 

์ด๋Ÿฐ ์‹์œผ๋กœ mockist๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด getter ๊ฐ™์ด ์ƒํƒœ๋ฅผ ๋ฐ›์•„์˜ฌ ๋ฉ”์„œ๋“œ๋Š” ํ•„์š”์—†์ง€๋งŒ, SUT์— ๋Œ€ํ•ด ๊ตฌํ˜„ ๋ฐฉ์‹์ด ๋…ธ์ถœ๋œ๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ๋‹ค.

 


โœ”๏ธ  ๊ฐ๊ฐ์˜ ์žฅ๋‹จ์ ์„ ์ฒดํฌํ•ด๋ณด๊ธฐ

๐Ÿ’ฌ ๋ฌธ์ œ ์ƒํ™ฉ 1. ์™ธ๋ถ€ API๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒฝ์šฐ

์†Œ์…œ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ์— ๋Œ€ํ•ด์„œ ์ƒ๊ฐํ•ด๋ณด์ž.

์‚ฌ์šฉ์ž๊ฐ€ ํฌ๋ฃจ๋กœ์„œ ์ถ”๊ฐ€๋˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์‚ฌ์šฉ์ž์˜ ์†Œ์…œ ๋กœ๊ทธ์ธ ์ •๋ณด๊ฐ€ ํ•„์š”ํ•˜๋ฉฐ, ์™ธ๋ถ€ api๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์ž‘์—…์ด ํ•„์š”ํ•˜๋‹ค.

๊ทธ๋Ÿฌ๋‚˜, ์ด๋Ÿฌํ•œ ์™ธ๋ถ€ api๋Š” ํ•ญ์ƒ ๋™์ผํ•œ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ฌ ๊ฒƒ์ด๋ผ๊ณ  ๊ธฐ๋Œ€ํ•  ์ˆ˜๋Š” ์—†์œผ๋ฉฐ, ๋น„์šฉ์ด ๋น„์‹ธ๋‹ค.

(๋งŒ์•ฝ ์™ธ๋ถ€ api ์„œ๋ฒ„๊ฐ€ ๋ง๊ฐ€์กŒ๋‹ค๋ฉด? ํ˜น์€, ๋‚ด๊ฐ€ ์š”์ฒญํ•œ ์ •๋ณด๊ฐ€ ์ค‘๊ฐ„์— ์œ ์‹ค๋˜์—ˆ๋‹ค๋ฉด?)

 

๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์—, ๋งค๋ฒˆ ์™ธ๋ถ€ api๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํ…Œ์ŠคํŠธ ํ•˜๋Š” ๊ฒƒ์€ ๋ณ„๋กœ ์ข‹์€ ๋ฐฉ๋ฒ•์€ ์•„๋‹ˆ๋‹ค.

์ด๋Ÿฐ ๊ฒฝ์šฐ์—์„œ๋Š” mockist๊ฐ€ ๋” ์ข‹๋‹ค๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ์™ธ๋ถ€ api๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ๋‹จ์ˆœํžˆ ์˜ˆ์ธก์„ ํ•ด์ฃผ๋ฉด ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

@Test
public void createCrew() {
        Crew crew = new Crew("journey");
        given(crewRepository.save(crew)).willReturn(crew);
        given(oauthService.getUserInfo(crew.getEmail()).willReturn(...); // here!

        crewRepository.createCrew(crew);

        verify(crewRepository).save(crew);
        verify(oauthService).getUserInfo(crew.getEmail());
        }

 

classicist๋Š” ์ด๋Ÿฐ ๊ฒฝ์šฐ์— ํ•ญ์ƒ ์™ธ๋ถ€ api๋ฅผ ํ˜ธ์ถœํ• ๊นŒ?

๊ทธ๋ ‡์ง€๋Š” ์•Š๋‹ค. ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๊ธฐ ์–ด๋ ต๋‹ค๋ฉด, test double์˜ ์ข…๋ฅ˜์— ์ข…์†๋˜์ง€ ์•Š๊ณ  ๋‹ค๋ฅธ ๊ฒƒ์„ ํ™œ์šฉํ•˜๋ฉด ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

 

๐Ÿ’ฌ ๋ฌธ์ œ ์ƒํ™ฉ 2. ์š”๊ตฌ์‚ฌํ•ญ์ด ์ถ”๊ฐ€๋˜์—ˆ์„ ๊ฒฝ์šฐ

๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ƒํ™ฉ์„ ๋“ค ์ˆ˜๋„ ์žˆ๋‹ค.

๋งŒ์•ฝ ํฌ๋ฃจ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ธฐ ์ „์—, ๋‚ด๋ถ€์ ์œผ๋กœ MissionService๋ฅผ ํ†ตํ•ด์„œ ํ•ด๋‹น ํฌ๋ฃจ์˜ ๋ฏธ์…˜ ํ˜„ํ™ฉ์— ๋Œ€ํ•ด ๊ฒ€์ฆํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ด๋ณด์ž.

@Test
public void createCrew() {
        // given
        Crew crew = new Crew("journey");

        // when
        crewRepository.createCrew(crew);

        // then
        Crew createdCrew = crewRepository.findByName("journey");
        assertThat(createdCrew.getName()).isEqualTo("journey");
        }

classicist์˜ ๊ฒฝ์šฐ ์ฝ”๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š”๋‹ค. ์™œ๋ƒํ•˜๋ฉด, ๋‚ด๋ถ€์ ์œผ๋กœ createCrew()์—์„œ ๋ฌด์Šจ ํ–‰์œ„๋ฅผ ํ•˜๋“  ํ…Œ์ŠคํŠธ๋Š” ์•Œ์ง€ ๋ชปํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๊ทธ์ € ๋‹จ์ˆœํžˆ ์ง„ํ–‰์„ ํ•˜์˜€์„ ๋•Œ ํฌ๋ฃจ๊ฐ€ ์ €์žฅ์ด ๋˜๊ณ , ํฌ๋ฃจ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ๋งŒ ํ•˜๋ฉด ๋œ๋‹ค.

ํ”„๋กœ๋•์…˜์˜ ์ฝ”๋“œ ๋ณ€ํ™”๊ฐ€ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์— ๋ณ€ํ™”๋ฅผ ๋งŽ์ด ๋ผ์น˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—, ๊ธฐ๋Šฅ ์ถ”๊ฐ€๊ฐ€ ์›ํ™œํ•ด์ง„๋‹ค.

 

@Test
public void createCrew() {
        Crew crew = new Crew("journey");
        given(crewRepository.save(crew)).willReturn(crew);
        given(missionService.isAllMissionPass(crew.getEmail()).willReturn(...); // here!

        crewRepository.createCrew(crew);

        verify(crewRepository).save(crew);
        verify(missionService).isAllMissionPass(crew.getEmail()); // here!
        }

๋ฐ˜๋ฉด, mockist์˜ ๊ฒฝ์šฐ ์ฝ”๋“œ์˜ ๋ณ€ํ™”๊ฐ€ ์ƒ๊ธธ ์ˆ˜๋ฐ–์— ์—†๋‹ค.

์™œ๋ƒํ•˜๋ฉด, save์—์„œ ๋‚ด๋ถ€์ ์œผ๋กœ ํ˜ธ์ถœ๋˜๋Š” ํ–‰์œ„์— ๋Œ€ํ•ด์„œ ๋ช…์‹œ์ ์œผ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•ด์ฃผ์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์œ„์™€ ๊ฐ™์ด ํ•ด๋‹น ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜์—ˆ์„ ๋•Œ ์–ด๋–ค ํ–‰์œ„๋ฅผ ํ• ์ง€, ๊ทธ๋ฆฌ๊ณ  ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค. ๋งŒ์•ฝ TDD๋ฅผ ์• ์šฉํ•œ๋‹ค๋ฉด ์ด๋Ÿฐ ๋ณ€ํ™”์— ๊ณ„์† ๋Œ€์‘ํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ƒ๋‹นํžˆ ๊ท€์ฐฎ์•„์งˆ ์ˆ˜๋„ ์žˆ๋‹ค.

 

๐Ÿ’ฌ ๋ฌธ์ œ ์ƒํ™ฉ 3. ๋ฒ„๊ทธ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ

๋งŒ์•ฝ, ์œ„์˜ ์ƒํ™ฉ์—์„œ ๊ฐ‘์ž๊ธฐ MissionService์— ์žฅ์• ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ด๋ณด์ž. 

์ด๋ ‡๊ฒŒ ๋˜๋ฉด classicist๋กœ ๊ตฌํ˜„ํ•œ ์ฝ”๋“œ๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ์žฅ์• ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด์„œ ํ•ด๋‹น ํ…Œ์ŠคํŠธ๊นŒ์ง€ ์‹คํŒจํ•˜๊ฒŒ ๋  ๊ฒƒ์ด๋‹ค.

์ฆ‰, ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฒฉ๋ฆฌ๋˜์ง€ ์•Š๊ณ  ์™ธ๋ถ€์—์„œ ๋‚ด๋ถ€ ์š”์†Œ๋“ค๊นŒ์ง€ ํ•จ๊ป˜ ํ…Œ์ŠคํŠธ ํ•˜๋ฉด์„œ ์žฅ์• ๊ฐ€ ์ „ํŒŒ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

 

๋งŒ์•ฝ MissionService๊ฐ€ CrewService๋ฟ๋งŒ ์•„๋‹ˆ๋ผ PrologService, JjimggongService์—๋„ ์—ฐ๊ด€์ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž.

์ด๋ ‡๊ฒŒ ๋˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์˜ค๋ฅ˜๊ฐ€ ๋ชจ๋‘์— ์ „ํŒŒ๋œ๋‹ค.

ํ•˜์ง€๋งŒ, mockist๋Š” ๋‹จ์ˆœํžˆ ๋ฒ„๊ทธ๊ฐ€ ๋ฐœ์ƒํ•œ ํด๋ž˜์Šค์— ๋Œ€ํ•ด์„œ๋งŒ ์‹คํŒจํ•˜๊ฒŒ ๋œ๋‹ค.

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ƒ์œผ๋กœ ๋‚ด๋ถ€ ๋กœ์ง์€ ๋ชจ๋‘ ์‹ค์ œ ํ˜ธ์ถœ์ด ์•„๋‹Œ, ์–ด๋–ค ์‹์œผ๋กœ ํ˜ธ์ถœ๋  ๊ฒƒ์ธ์ง€ ์ œ์–ด๋ฅผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์˜ํ–ฅ์ด ์—†๋Š” ๊ฒƒ์ด๋‹ค.

ํ•˜์ง€๋งŒ, classicist๋Š” ์ด๋Ÿฐ ์˜ค๋ฅ˜ ์ „ํŒŒ์— ํฌ๊ฒŒ ์‹ ๊ฒฝ์“ฐ์ง€ ์•Š๋Š”๋‹ค.

์–ด์ฐจํ”ผ TDD๋ฅผ ์ง„ํ–‰ํ•˜๊ฒŒ ๋œ๋‹ค๋ฉด ์ž‘์€ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜์—ฌ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๊นŒ์ง€, ๊ณ„์†ํ•ด์„œ ์ „์ฒด ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๋Œ๋ฆฌ๊ฒŒ ๋  ๊ฒƒ์ด๊ณ , ๊ทธ๋Ÿฌ๋ฉด ํ…Œ์ŠคํŠธ๋ฅผ ๋Œ๋ฆด ๋•Œ ๋งˆ์ง€๋ง‰์œผ๋กœ ์ˆ˜์ •ํ•œ ํŒŒ์ผ์ด ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒ๋œ ์ง€์ ์ž„์„ ์‰ฝ๊ฒŒ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํฐ ๋ฌธ์ œ๊ฐ€ ๋˜์ง€ ์•Š๋Š”๋‹ค๊ณ  ๋งํ•œ๋‹ค.

 

 

๐Ÿ’ฌ ๋ฌธ์ œ ์ƒํ™ฉ 4. ์„ธ๋ถ€ ์š”์†Œ๋Š” ์ž˜ ๋˜๋Š”๋ฐ, ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋Š” ์•ˆ ๋  ๋•Œ

๋งŒ์•ฝ, CrewService์—์„œ canGraduate() ๋ฉ”์„œ๋“œ๊ฐ€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‹คํ–‰๋œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž.

ํฌ๋ฃจ๋“ค์˜ ์ˆ˜๋ฃŒ ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•˜๊ธฐ ์œ„ํ•ด์„œ PrologService์—์„œ ๊ธ€์„ ์ž‘์„ฑํ•˜์˜€๋Š”์ง€, JjimggongService์—์„œ ์˜ˆ์•ฝ์„ ํ•œ ์  ์žˆ๋Š”์ง€, MissionService์—์„œ ๋ชจ๋“  ๋ฏธ์…˜์„ ํ†ต๊ณผํ–ˆ๋Š”์ง€ ํŒ๋‹จํ•˜๋Š” ๋กœ์ง์„ ํ˜ธ์ถœํ•œ๋‹ค.

classicist๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ”Œ๋กœ์šฐ๋กœ ์ง„ํ–‰ํ•œ๋‹ค.

๐Ÿ’ฌ CrewService์—์„œ ์กธ์—… ๊ฐ€๋Šฅ ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•˜๋Š” ํ…Œ์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•ด!

1. PrologService์—์„œ ๊ธ€ ์ž‘์„ฑ ์—ฌ๋ถ€ ํŒ๋‹จ ๋ฉ”์„œ๋“œ ์ƒ์„ฑ
2. JjimggonService์—์„œ ์˜ˆ์•ฝ ์—ฌ๋ถ€ ํŒ๋‹จ ๋ฉ”์„œ๋“œ ์ƒ์„ฑ 
3. MissionService์—์„œ ๋ชจ๋“  ๋ฏธ์…˜ ํ†ต๊ณผ ์—ฌ๋ถ€ ํŒ๋‹จ ๋ฉ”์„œ๋“œ ์ƒ์„ฑ 
4. CrewService์—์„œ ํ…Œ์ŠคํŠธ ์ง„ํ–‰!

ํ•˜์ง€๋งŒ, classicist๋Š” ๋‹น์žฅ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๊ณ  ์žˆ๋Š” CrewService์˜ canGraduate() ๋ฉ”์„œ๋“œ์˜ ํ…Œ์ŠคํŠธ์—๋งŒ ์ง‘์ค‘ํ•˜๊ณ  ์žˆ์—ˆ์„ ๊ฒƒ์ด๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์ค‘๊ฐ„์— ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์ง€ ์•Š์•˜๊ฑฐ๋‚˜, ํ˜น์€ ์ œ๋Œ€๋กœ ํ™•์ธํ•˜์ง€ ๋ชปํ–ˆ๋‹ค๋ฉด ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋”๋ผ๋„ ๋„˜์–ด๊ฐ”์„ ์ˆ˜๋„ ์žˆ๋‹ค.

์ฆ‰, classicist์—์„œ๋Š” ์ด๋Ÿฌํ•œ ํ˜‘๋ ฅ ๊ฐ์ฒด์— ๋Œ€ํ•ด์„œ๋Š” ๊ผผ๊ผผํ•œ ํ…Œ์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

 

๋ฐ˜๋ฉด์—, mockist๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ง„ํ–‰ํ•œ๋‹ค.

1. PrologService์—์„œ ๊ธ€ ์ž‘์„ฑ ์—ฌ๋ถ€ ํŒ๋‹จ ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•œ mock ๊ฐ์ฒด ์ƒ์„ฑ
2. JjimggonService์—์„œ ์˜ˆ์•ฝ ์—ฌ๋ถ€ ํŒ๋‹จ ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•œ mock ๊ฐ์ฒด ์ƒ์„ฑ
3. MissionService์—์„œ ๋ชจ๋“  ๋ฏธ์…˜ ํ†ต๊ณผ ์—ฌ๋ถ€ ํŒ๋‹จ mock ๊ฐ์ฒด ์ƒ์„ฑ
4. CrewService์—์„œ ํ…Œ์ŠคํŠธ ์™„๋ฃŒ! 
5. ๊ฐ๊ฐ์— ๋Œ€ํ•œ ์„ธ๋ถ€ ๊ตฌํ˜„ ์ง„ํ–‰

ํ˜‘๋ ฅ ๊ฐ์ฒด๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์ „๋ถ€ํ„ฐ mock ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ด๋‘๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ๋ณธ์ ์œผ๋กœ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ด์•ผ ํ•˜๋Š” ๋ชฉ๋ก๋“ค์— ๋Œ€ํ•ด ๋” ์ž˜ ์ธ์‹ํ•˜๊ฒŒ ๋œ๋‹ค.

Mockist๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ํ˜‘๋ ฅ ๊ฐ์ฒด๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์ „์— SUT์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜์ง€๋งŒ, classicist๋Š” SUT์— ๋Œ€ํ•œ ๊ธฐ๋Šฅ์„ ๋ชจ๋‘ ๊ตฌํ˜„ํ•œ ๋‹ค์Œ์— ํ˜‘๋ ฅ ๊ฐ์ฒด์— ๋Œ€ํ•ด ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•˜์—ฌ ๋†“์น  ํ™•๋ฅ ์ด ๋†’๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

 


โœ”๏ธ Inside-Out vs Outside-In

classicist๋Š” inside-out์„, mockist๋Š” outside-in์„ ํ™œ์šฉํ•ด์„œ ๊ฐœ๋ฐœ์„ ์ง„ํ–‰ํ•œ๋‹ค.

์ „์ž๋Š” ํ•˜๋‚˜์˜ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ๋•Œ ๋‚ด๋ถ€ ๊ตฌ์„ฑ ์š”์†Œ๋“ค์„ ํ•˜๋‚˜์”ฉ ๊ตฌ์„ฑํ•ด๋‚˜๊ฐ€๋ฉด์„œ (๋„๋ฉ”์ธ๋ถ€ํ„ฐ ์„ค๊ณ„) ๊ฐ€์žฅ ๋งˆ์ง€๋ง‰์— controller๋ฅผ ์ƒ์„ฑํ•ด ์‚ฌ์šฉ์ž์—๊ฒŒ ์ œ๊ณตํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๊ณ , ํ›„์ž๋Š” UI์™€ ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ๊ณ„์ธต๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜์—ฌ ๋„๋ฉ”์ธ๊นŒ์ง€ ๋‚ด๋ ค๊ฐ€๋Š” ๋ฐฉ์‹์ด๋‹ค.

 

Inside-out์˜ ๊ฒฝ์šฐ ์–ด๋–ค ๋„๋ฉ”์ธ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜๋ฉด ์ข‹์„์ง€ ๊ฐˆํ”ผ๋ฅผ ์žก๊ธฐ ์–ด๋ ค์šด ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜, ๋ฆฌํŒฉํ„ฐ๋ง์„ ์ง„ํ–‰ํ•˜๋ฉด์„œ ์ฃผ๋กœ ๊ฐ์ฒด์— ๋Œ€ํ•œ ์ฑ…์ž„ ๋ถ„๋ฆฌ๋ฅผ ์ง„ํ–‰ํ•  ๋•Œ ๋ณด๋‹ค ๋‚˜์€ ๋„๋ฉ”์ธ ๋ชจ๋ธ์ด ๋„์ถœ๋  ์ˆ˜๋„ ์žˆ๊ณ , ์ž‘์€ ์š”์†Œ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์˜ค๋ฒ„ ์—”์ง€๋‹ˆ์–ด๋ง๋„ ๋น„๊ต์  ํ”ผํ•  ์ˆ˜ ์žˆ๋‹ค. ๋˜ํ•œ, ๋‚ด๋ถ€ ์š”์†Œ์— ๋Œ€ํ•œ ๊ตฌํ˜„์„ ์œ„์ฃผ๋กœ ์ง„ํ–‰ํ•˜๋‹ค ๋ณด๋‹ˆ๊นŒ ๊ธฐ๋Šฅ์ ์œผ๋กœ ์•ˆ์ •์„ฑ์„ ๋ณด์•˜์„ ๋•Œ ์ดˆ๊ธฐ์—๋Š” ๋” ์œ ๋ฆฌํ•  ์ˆ˜๋„ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค. ๊ทธ๋Ÿฌ๋‚˜, ํ˜‘๋ ฅ ๊ด€๊ณ„๋ฅผ ์ œ๋Œ€๋กœ ์„ธ์šฐ์ง€ ์•Š์œผ๋ฉด ์ž˜๋ชป๋œ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜๋„ ์žˆ๋‹ค๋Š” ๊ฒƒ์ด ๋‹จ์ ์ด๋‹ค. (์œ„์—์„œ ๋งํ–ˆ๋˜ ๊ฒƒ์ฒ˜๋Ÿผ ๋นผ๋จน๋Š”๋‹ค๋“ ์ง€... ๊ทธ๋Ÿฐ ๊ฒฝ์šฐ๊ฐ€ ์ƒ๊ธธ ์ˆ˜๋„ ์žˆ์œผ๋‹ˆ๊นŒ)

 

๋ฐ˜๋ฉด, Outside-in์˜ ๊ฒฝ์šฐ ํ˜‘๋ ฅ ๊ฐ์ฒด ์ž์ฒด๊ฐ€ ๊ตฌํ˜„๋˜์ง€ ์•Š์€ ์ƒํƒœ์—์„œ ์ง„ํ–‰ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์–ด๋–ค ๊ฐ์ฒด๊ฐ€ ์–ด๋–ค ๊ฒƒ์„ ์˜์กดํ•˜๋Š”์ง€, ์ƒํ˜ธ ์ž‘์šฉ์„ ์–ด๋–ค ์‹์œผ๋กœ ํ•˜๋Š”์ง€์— ๋” ์ง‘์ค‘ํ•˜๊ฒŒ ๋œ๋‹ค. ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ฐ€์žฅ ๋งž๋‹ฟ์€ ์˜์—ญ๋ถ€ํ„ฐ ์ง„ํ–‰ํ•˜๊ธฐ ๋•Œ๋ฌธ์— '์‚ฌ์šฉ์ž์˜ ์š”์ฒญ์ด ๋“ค์–ด์™”์„ ๋•Œ, ๋ฌด์—‡์„ ์ œ๊ณตํ•ด์•ผ ํ•˜์ง€?'๋ผ๋Š” ๋ฌผ์Œ์œผ๋กœ ์‹œ์ž‘ํ•˜์—ฌ, ๋‚ด๋ถ€ ํ˜‘๋ ฅ ๊ฐ์ฒด์˜ ๊ตฌ์„ฑ์— ๋Œ€ํ•ด์„œ ๊ณ ๋ฏผํ•˜๊ฒŒ ๋œ๋‹ค. ์–ด์ฐจํ”ผ ๊ตฌํ˜„๋˜์ง€ ์•Š์€ ๋ถ€๋ถ„์€ Mock์œผ๋กœ ์„ค๊ณ„ํ•˜๋ฉด ๋˜๋Š” ๊ฒƒ์ด๋‹ˆ๊นŒ. ์ด ๋•๋ถ„์— ๊ฐ์ฒด์˜ ์„ธ๋ถ€ ๊ตฌํ˜„๋ณด๋‹ค๋Š” ํ–‰์œ„์— ์ง‘์ค‘ํ•˜๊ฒŒ ๋˜๊ณ , ์ฑ…์ž„์— ๋Œ€ํ•ด ๋” ๋ช…ํ™•ํ•˜๊ฒŒ ๋‚˜ํƒ€๋‚ผ ์ˆ˜ ์žˆ์–ด์„œ ๊ฐ์ฒด์ง€ํ–ฅ์ ์ธ ์ฝ”๋“œ ์ž‘์„ฑ์ด ๊ฐ€๋Šฅํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ์ฑ…์ž„ ๋ถ„๋ฆฌ์— ์ต์ˆ™ํ•˜์ง€ ์•Š๋‹ค๋ฉด ์˜คํžˆ๋ ค ์˜ค๋ฒ„ ์—”์ง€๋‹ˆ์–ด๋ง์„ ๊ฐ€์งˆ ์ˆ˜๋„ ์žˆ๋‹ค๋Š” ๊ฒŒ ๋‹จ์ ์ด๋‹ค.

 

  classicist mockist
ํ…Œ์ŠคํŠธ ๋‹จ์œ„ sociable test solitary test
ํ…Œ์ŠคํŠธ ๊ฒ€์ฆ ์ƒํƒœ ๊ฒ€์ฆ ํ–‰์œ„ ๊ฒ€์ฆ
ํ˜‘๋ ฅ ๊ฐ์ฒด ์ค€๋น„ Fixture ๊ตฌ์„ฑ ๋ฐ ์žฌ์‚ฌ์šฉ ๋ฉ”์„œ๋“œ๋งˆ๋‹ค Mock ์ƒ์„ฑ ๋ฐ ๊ธฐ๋Œ€๊ฐ’ ์„ค์ •
๊ฐ„๋‹จํ•œ ํ˜‘๋ ฅ ๊ฐ์ฒด ์‹ค์ œ ๊ฐ์ฒด ์‚ฌ์šฉ ํ…Œ์ŠคํŠธ ๋”๋ธ” ์ ๊ทน ์‚ฌ์šฉ
์–ด๋ ค์šด ํ˜‘๋ ฅ ๊ฐ์ฒด  ํ…Œ์ŠคํŠธ ๋”๋ธ” ์‚ฌ์šฉ ๊ณ ๋ ค ํ…Œ์ŠคํŠธ ๋”๋ธ” ์ ๊ทน ์‚ฌ์šฉ
ํ…Œ์ŠคํŠธ ์œ ์ง€ ๋ณด์ˆ˜ Fixture์˜ ๋ณ€๊ฒฝ์ด ์žˆ์„ ๋•Œ ํ…Œ์ŠคํŠธ๊ฐ€ ๊นจ์ง„๋‹ค ๊ตฌํ˜„ ํ…Œ์ŠคํŠธ๋กœ ์ด์–ด์ ธ์„œ ํ…Œ์ŠคํŠธ ์ˆ˜์ • ์‹œ ๋งŽ์€ ์‹œ๊ฐ„์ด ์†Œ์š”๋  ์ˆ˜ ์žˆ๋‹ค
ํ…Œ์ŠคํŠธ ๊ฒฉ๋ฆฌ X (๋””๋ฒ„๊ทธ๊ฐ€ ํž˜๋“ค๋‹ค) O (ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ํ•„์š”)
๋””์ž์ธ ์Šคํƒ€์ผ Inside-out Outside-in

 

์ด ๋ง์„ ๋“ฃ๊ณ  ์ผ์ข…์˜ DDD์™€ ๋น„์Šทํ•œ ๊ฒƒ์ผ๊นŒ? ๋ผ๋Š” ์ƒ๊ฐ๋„ ํ–ˆ์—ˆ๋Š”๋ฐ,

์ƒ๊ฐํ•ด๋ณด๋ฉด DDD๋Š” ๋„๋ฉ”์ธ ์ฃผ๋„ ๊ฐœ๋ฐœ์ด๊ณ , ์šฐ๋ฆฌ๊ฐ€ ๋งํ•˜๋Š” classicist์™€ Mockist๋Š” TDD - ์ฆ‰, ํ…Œ์ŠคํŠธ ์ฃผ๋„ ๊ฐœ๋ฐœ์ด๋‹ค.

์•„์˜ˆ ๋‹ค๋ฅธ ๊ฐœ๋ฐœ ๋ฐฉ๋ฒ•๋ก ์ด์ง€๋งŒ, ๋งŒ์•ฝ DDD๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ฒŒ ๋œ๋‹ค๋ฉด ์ด๋Š” classicist์— ์กฐ๊ธˆ ๋” ๊ฐ€๊นŒ์šด ํ˜•ํƒœ๊ฐ€ ์•„๋‹๊นŒ ์‹ถ๋‹ค. (๋„๋ฉ”์ธ ํ…Œ์ŠคํŠธ -> ์ž‘์„ฑ... ์ด๋Ÿฐ ๋Š๋‚Œ ์•„๋‹๊นŒ?)

 


ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์—ด์‹ฌํžˆ ์ž‘์„ฑํ•ด์•ผ๊ฒ ๋‹ค... ๋!

Comments