πŸ€– 쒋은 ν…ŒμŠ€νŠΈ μ½”λ“œ | FIRST 원칙

리버 riverΒ·2023λ…„ 11μ›” 1일
0
post-thumbnail

1. FIRST μ›μΉ™μ΄λž€?


FIRST : 효과적인 ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„±ν•˜κΈ° μœ„ν•œ μ›μΉ™λ“€μ˜ 첫 κΈ€μžλ₯Ό λͺ¨μ•„ λ§Œλ“  μ•½μ–΄

F - Fast (빠름): ν…ŒμŠ€νŠΈλŠ” λΉ λ₯΄κ²Œ μ‹€ν–‰λ˜μ–΄μ•Ό ν•œλ‹€.
I - Independent/Isolated (독립적/격리된): 각 ν…ŒμŠ€νŠΈλŠ” μ„œλ‘œ 독립적이어야 ν•˜λ©°, λ‹€λ₯Έ ν…ŒμŠ€νŠΈμ™€ 곡유 μƒνƒœλ₯Ό 가지지 μ•Šμ•„μ•Ό ν•œλ‹€.
R - Repeatable (반볡 κ°€λŠ₯): ν…ŒμŠ€νŠΈλŠ” μ–΄λ–€ ν™˜κ²½μ—μ„œλ„ 반볡 κ°€λŠ₯ν•΄μ•Ό ν•œλ‹€.
S - Self-Validating (자체 검증 κ°€λŠ₯): ν…ŒμŠ€νŠΈλŠ” μ˜ˆμƒ κ²°κ³Όλ₯Ό 슀슀둜 검증할 수 μžˆμ–΄μ•Ό ν•˜λ©°, μˆ˜λ™ 검사λ₯Ό μš”κ΅¬ν•˜μ§€ μ•Šμ•„μ•Ό ν•œλ‹€.
T - Timely (μ μ‹œμ˜): ν…ŒμŠ€νŠΈλŠ” μ μ ˆν•œ μ‹œκΈ°μ— μž‘μ„±λ˜μ–΄μ•Ό ν•œλ‹€. 일반적으둜 ν…ŒμŠ€νŠΈ μ½”λ“œλŠ” μ‹€μ œ μ½”λ“œλ₯Ό μž‘μ„±ν•˜κΈ° μ „μ΄λ‚˜ λ™μ‹œμ— μž‘μ„±λ˜μ–΄μ•Ό ν•œλ‹€.


2. μ˜ˆμ‹œμ™€ μ„€λͺ…


2-1) F - Fast (빠름)

πŸ’¬Β Β μƒν™©

μ›Ή μ„œλΉ„μŠ€μ˜ 둜그인 κΈ°λŠ₯을 ν…ŒμŠ€νŠΈν•˜λŠ” 경우, μ‹€μ œ λ°μ΄ν„°λ² μ΄μŠ€λ‚˜ μ™ΈλΆ€ μ‹œμŠ€ν…œ 호좜 λŒ€μ‹ μ— Mock κ°μ²΄λ‚˜ 인-λ©”λͺ¨λ¦¬ λ°μ΄ν„°λ² μ΄μŠ€λ₯Ό μ‚¬μš©ν•¨μœΌλ‘œμ¨ ν…ŒμŠ€νŠΈ 속도λ₯Ό κ°œμ„ ν•œλ‹€.

πŸ’»Β Β μ˜ˆμ‹œ μ½”λ“œ

const { AuthenticationService, UserRepository } = require('./myservice');

describe('FastLoginTest', () => {
  test('test login with mock', () => {
    // UserRepository의 Mock 객체λ₯Ό 생성함
    const mockUserRepo = {
      validateUser: jest.fn()
    };

    // Mock 객체가 항상 Trueλ₯Ό λ°˜ν™˜ν•˜λ„λ‘ 섀정함
    mockUserRepo.validateUser.mockReturnValue(true);

    // AuthenticationService μΈμŠ€ν„΄μŠ€λ₯Ό Mock UserRepository와 ν•¨κ»˜ 생성함
    const authService = new AuthenticationService(mockUserRepo);

    // 둜그인 ν•¨μˆ˜κ°€ μ˜ˆμƒλŒ€λ‘œ μž‘λ™ν•˜λŠ”μ§€ 검증함
    expect(authService.login('username', 'password')).toBeTruthy();
  });
});
  • 이 μ½”λ“œμ—μ„œ UserRepositoryλŠ” Mock 객체둜 λŒ€μ²΄λ˜μ–΄ λ°μ΄ν„°λ² μ΄μŠ€λ‚˜ λ„€νŠΈμ›Œν¬ 호좜 없이 validate_user λ©”μ†Œλ“œκ°€ 호좜될 λ•Œ 항상 Trueλ₯Ό λ°˜ν™˜ν•˜λ„λ‘ μ„€μ •λ˜μ—ˆλ‹€. 이런 방식은 ν…ŒμŠ€νŠΈ 속도λ₯Ό λŒ€ν­ ν–₯μƒμ‹œν‚¨λ‹€.

2-2) I - Independent/Isolated (독립적/격리된)


πŸ’¬Β Β μƒν™©

각 λ‹¨μœ„ ν…ŒμŠ€νŠΈλŠ” λ‹€λ₯Έ ν…ŒμŠ€νŠΈμ˜ κ²°κ³Όλ‚˜ μƒνƒœμ— 영ν–₯을 받지 μ•Šκ³  λ…λ¦½μ μœΌλ‘œ μ‹€ν–‰λ˜μ–΄μ•Ό ν•œλ‹€. 예λ₯Ό λ“€μ–΄, μ‚¬μš©μž 관리 μ‹œμŠ€ν…œμ—μ„œ ν•œ ν…ŒμŠ€νŠΈκ°€ μ‚¬μš©μžλ₯Ό μΆ”κ°€ν•˜λŠ” 경우, λ‹€λ₯Έ ν…ŒμŠ€νŠΈμ—μ„œλŠ” 이 μΆ”κ°€λœ μ‚¬μš©μžμ— μ˜μ‘΄ν•΄μ„œλŠ” μ•ˆ λœλ‹€.

πŸ’»Β Β μ˜ˆμ‹œ μ½”λ“œ

   const { UserService } = require('./UserService');

describe('IndependentUserTests', () => {
  let userService;

  // 각 ν…ŒμŠ€νŠΈ 전에 userServiceλ₯Ό μƒˆλ‘œ μ΄ˆκΈ°ν™”ν•˜μ—¬ 각 ν…ŒμŠ€νŠΈκ°€ λ…λ¦½μ μœΌλ‘œ 싀행됨
  beforeEach(() => {
    userService = new UserService();
  });

  // μƒˆλ‘œμš΄ μ‚¬μš©μž μΆ”κ°€ κΈ°λŠ₯을 ν…ŒμŠ€νŠΈ
  test('test add user', () => {
    userService.addUser("newuser1");
    expect(userService.userExists("newuser1")).toBeTruthy();
  });

  // μ‚¬μš©μž 제거 κΈ°λŠ₯을 ν…ŒμŠ€νŠΈ
  test('test remove user', () => {
    userService.addUser("temporaryuser");
    userService.removeUser("temporaryuser");
    expect(userService.userExists("temporaryuser")).toBeFalsy();
  });
});
  • λ³€ν™˜λœ μ½”λ“œμ—μ„œ beforeEachλŠ” 각 ν…ŒμŠ€νŠΈκ°€ μ‹œμž‘ν•˜κΈ° 전에 userService μΈμŠ€ν„΄μŠ€λ₯Ό μ΄ˆκΈ°ν™”ν•œλ‹€. μ΄λ ‡κ²Œ ν•˜λ©΄ 각각의 ν…ŒμŠ€νŠΈκ°€ μ„œλ‘œ λ…λ¦½μ μœΌλ‘œ μ‹€ν–‰λœλ‹€. 첫 번째 ν…ŒμŠ€νŠΈ('test add user')λŠ” μ‚¬μš©μž μΆ”κ°€ κΈ°λŠ₯을, 두 번째 ν…ŒμŠ€νŠΈ('test remove user')λŠ” μ‚¬μš©μž 제거 κΈ°λŠ₯을 ν…ŒμŠ€νŠΈν•œλ‹€. 이 방식을 톡해 각 ν…ŒμŠ€νŠΈλŠ” λ‹€λ₯Έ ν…ŒμŠ€νŠΈμ˜ μ‹€ν–‰ μƒνƒœλ‚˜ 결과에 영ν–₯을 받지 μ•ŠμœΌλ©° λ…λ¦½μ μœΌλ‘œ μˆ˜ν–‰λœλ‹€.

2-3) S - Self-Validating (자체 검증)


πŸ’¬Β Β μƒν™©

ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λŠ” 슀슀둜 κ²°κ³Όκ°€ μ˜¬λ°”λ₯Έμ§€λ₯Ό νŒλ‹¨ν•  수 μžˆμ–΄μ•Ό ν•œλ‹€. 예λ₯Ό λ“€μ–΄, νŒŒμΌμ—μ„œ νŠΉμ • 데이터λ₯Ό μ½λŠ” κΈ°λŠ₯을 ν…ŒμŠ€νŠΈν•  λ•Œ, ν…ŒμŠ€νŠΈ μ½”λ“œλŠ” μ˜ˆμƒλ˜λŠ” κ²°κ³Ό 값을 가지고 μ‹€μ œ 결과와 μžλ™μœΌλ‘œ 비ꡐ해야 ν•œλ‹€. μˆ˜λ™μœΌλ‘œ κ²°κ³Όλ₯Ό 확인할 ν•„μš”κ°€ μ—†μ–΄μ•Ό ν•œλ‹€.

πŸ’»Β Β μ˜ˆμ‹œ μ½”λ“œ

 const { DataProcessor } = require('./DataProcessor');

describe('SelfValidatingTest', () => {
  test('data reading from file', () => {
    const processor = new DataProcessor();
    const expectedOutput = { name: "John", age: 30 };
    expect(processor.readDataFromFile("user_data.txt")).toEqual(expectedOutput);
  });
});
  • μœ„μ˜ μ½”λ“œμ—μ„œ DataProcessor의 readDataFromFile λ©”μ†Œλ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ user_data.txt νŒŒμΌμ—μ„œ 데이터λ₯Ό μ½λŠ”λ‹€. ν…ŒμŠ€νŠΈλŠ” 읽은 데이터가 μ˜ˆμƒλ˜λŠ” 결과인 { name: "John", age: 30 }와 같은지λ₯Ό μžλ™μœΌλ‘œ κ²€μ¦ν•œλ‹€. κ²°κ³Ό 확인이 μžλ™ν™”λ˜μ–΄ 있기 λ•Œλ¬Έμ— κ°œλ°œμžλŠ” 맀번 μˆ˜λ™μœΌλ‘œ κ²°κ³Όλ₯Ό 검사할 ν•„μš”κ°€ μ—†λ‹€. μ΄λ ‡κ²Œ μžλ™ 검증을 ν•˜λŠ” ν…ŒμŠ€νŠΈλŠ” 였λ₯˜ κ°€λŠ₯성을 쀄이고, ν…ŒμŠ€νŠΈ κ³Όμ •μ˜ νš¨μœ¨μ„±μ„ λ†’μ—¬μ€€λ‹€.

2-4) T - Timely (μ μ‹œμ—)


πŸ’¬Β Β μƒν™©

ν…ŒμŠ€νŠΈλŠ” μ μ ˆν•œ μ‹œκΈ°μ— μž‘μ„±λ˜μ–΄μ•Ό ν•œλ‹€. μ΄λŠ” 보톡 ν…ŒμŠ€νŠΈ λŒ€μƒ κΈ°λŠ₯이 개발되기 λ°”λ‘œ 전을 μ˜λ―Έν•œλ‹€. 예λ₯Ό λ“€μ–΄, μƒˆλ‘œμš΄ κΈ°λŠ₯을 κ°œλ°œν•˜κΈ° 전에 κ·Έ κΈ°λŠ₯을 검증할 ν…ŒμŠ€νŠΈλ₯Ό λ¨Όμ € μž‘μ„±ν•˜λŠ” 것을 λ§ν•œλ‹€. 이 방식은 ν…ŒμŠ€νŠΈ 주도 개발(Test-Driven Development, TDD)의 핡심 원칙 쀑 ν•˜λ‚˜μ΄λ‹€.

πŸ’»Β Β μ˜ˆμ‹œ μ½”λ“œ

    import unittest
    from my_app import new_feature_function

    class TimelyTest(unittest.TestCase):
        def test_new_feature(self):
            # 아직 κ΅¬ν˜„λ˜μ§€ μ•Šμ€ μƒˆλ‘œμš΄ κΈ°λŠ₯을 ν…ŒμŠ€νŠΈν•œλ‹€.
            # ν…ŒμŠ€νŠΈκ°€ μ‹€νŒ¨ν•˜λŠ” 것은 κ΅¬ν˜„λ˜μ§€ μ•Šμ•˜κΈ° λ•Œλ¬Έμ΄λ‹€.
            self.assertEqual(new_feature_function(), expected_result)

    if __name__ == "__main__":
        unittest.main()
  • 이 μ½”λ“œμ—μ„œ new_feature_function은 아직 κ΅¬ν˜„λ˜μ§€ μ•Šμ•˜λ‹€. μš°λ¦¬λŠ” λ¨Όμ € 이 κΈ°λŠ₯에 λŒ€ν•œ ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•˜κ³ , κ·Έ λ‹€μŒμ— μ‹€μ œ κΈ°λŠ₯을 κ΅¬ν˜„ν•œλ‹€. μ΄λ ‡κ²Œ ν…ŒμŠ€νŠΈλ₯Ό λ¨Όμ € μž‘μ„±ν•˜λ©΄ μ„€κ³„μ˜ 문제λ₯Ό 쑰기에 λ°œκ²¬ν•˜κ³ , μΆ”ν›„ μ½”λ“œλ₯Ό λ¦¬νŒ©ν† λ§ν•  λ•Œ μ‹€μˆ˜λ₯Ό 쀄일 수 μžˆλŠ” 이점이 μžˆλ‹€. λ˜ν•œ, κΈ°λŠ₯이 μ˜¬λ°”λ₯΄κ²Œ μž‘λ™ν•˜λŠ”μ§€ 확인할 수 μžˆλŠ” μžλ™ 검증 μˆ˜λ‹¨μ„ 미리 κ°–μΆ”κ²Œ λœλ‹€.
profile
ν”„λ‘ νŠΈμ—”λ“œ 개발자

0개의 λŒ“κΈ€

κ΄€λ ¨ μ±„μš© 정보