Avatar
πŸ˜‰

Organizations

  • Java Springμ—μ„œ “reflection(λ¦¬ν”Œλ ‰μ…˜)“은 Java의 λŸ°νƒ€μž„ μ‹œμ μ— 클래슀, λ©”μ„œλ“œ, ν•„λ“œ 등에 μ ‘κ·Όν•˜κ³  μ‘°μž‘ν•  수 μžˆλŠ” κΈ°λŠ₯을 μ˜λ―Έν•©λ‹ˆλ‹€. 일반적인 컴파일 μ‹œμ μ΄ μ•„λ‹Œ, μ‹€ν–‰ 쀑인 μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ ꡬ쑰λ₯Ό λ™μ μœΌλ‘œ λΆ„μ„ν•˜κ±°λ‚˜ λ³€κ²½ν•˜λŠ” 데 ν™œμš©λ©λ‹ˆλ‹€.


    πŸ’‘ λ¦¬ν”Œλ ‰μ…˜μ΄λž€?

    Java λ¦¬ν”Œλ ‰μ…˜ APIλŠ” java.lang.reflect νŒ¨ν‚€μ§€λ₯Ό 톡해 제곡되며, λ‹€μŒκ³Ό 같은 κΈ°λŠ₯을 μ œκ³΅ν•©λ‹ˆλ‹€:

    • 클래슀 μ΄λ¦„μœΌλ‘œ Class 객체λ₯Ό μ–»κΈ°
    • 클래슀의 μƒμ„±μž, λ©”μ„œλ“œ, ν•„λ“œ 정보 쑰회
    • λ©”μ„œλ“œ 호좜, ν•„λ“œ κ°’ 읽기/μ“°κΈ° λ“± 동적 μ‘°μž‘
    • μ ‘κ·Ό μ œν•œμž(private λ“±) λ¬΄μ‹œν•˜κ³  μ ‘κ·Ό (setAccessible(true))

    πŸ›  Springμ—μ„œ λ¦¬ν”Œλ ‰μ…˜μ΄ μ“°μ΄λŠ” μ£Όμš” 예

    Spring ν”„λ ˆμž„μ›Œν¬λŠ” λ‚΄λΆ€μ μœΌλ‘œ λ¦¬ν”Œλ ‰μ…˜μ„ 맀우 κ΄‘λ²”μœ„ν•˜κ²Œ μ‚¬μš©ν•©λ‹ˆλ‹€:

    Created Tue, 29 Jul 2025 14:07:45 +0900
  • Spring Boot 3 + JPA + MySQL ν™˜κ²½μ—μ„œ νŠΉμ • ν…Œμ΄λΈ”μ— λŒ€ν•΄ find 및 save μž‘μ—… μ‹œ row-level lock만 μ‚¬μš©ν•˜κ³  table-level lock을 ν”Όν•˜κ³  싢을 경우, λ‹€μŒκ³Ό 같은 μ „λž΅μ„ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.


    βœ… 핡심 κ°œλ… μš”μ•½

    • MySQL InnoDB μŠ€ν† λ¦¬μ§€ 엔진은 기본적으둜 row-level lock을 μ§€μ›ν•˜λ©°, JPA의 κΈ°λ³Έ λ™μž‘μ€ row-level lock을 μ‚¬μš©ν•©λ‹ˆλ‹€.

    • κ·ΈλŸ¬λ‚˜ λ‹€μŒ 쑰건을 λ§Œμ‘±ν•˜μ§€ μ•ŠμœΌλ©΄ innoDB라도 table lock이 λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€:

      • WHERE μ ˆμ— 인덱슀λ₯Ό 잘 μ‚¬μš©ν•˜μ§€ λͺ»ν•˜κ±°λ‚˜
      • 락 λͺ¨λ“œκ°€ λͺ…μ‹œλ˜μ§€ μ•Šμ•˜κ±°λ‚˜
      • UPDATEλ‚˜ SELECT ... FOR UPDATEμ—μ„œ λ²”μœ„ 검색을 μ œλŒ€λ‘œ κ΅¬μ„±ν•˜μ§€ μ•Šμ•˜κ±°λ‚˜

    βœ… μ „μ œ 쑰건: ν…Œμ΄λΈ”μ΄ InnoDB 엔진인지 확인

    SHOW TABLE STATUS WHERE Name = 'your_table_name';
    
    • Engine 컬럼이 InnoDBμ—¬μ•Ό row-level lock이 μ μš©λ©λ‹ˆλ‹€.
    • λ§Œμ•½ MyISAM이면 row-level lock이 λΆˆκ°€λŠ₯ν•˜λ―€λ‘œ λ°˜λ“œμ‹œ InnoDB둜 λ³€κ²½ν•΄μ•Ό ν•©λ‹ˆλ‹€:
    ALTER TABLE your_table_name ENGINE = InnoDB;
    

    βœ… JPAμ—μ„œ row-level locking만 μ μš©ν•˜κΈ°

    1. @Lock(LockModeType.PESSIMISTIC_WRITE) λ˜λŠ” @Lock(LockModeType.PESSIMISTIC_READ)

    @Repository
    public interface OrderRepository extends JpaRepository<Order, Long> {
    
        @Lock(LockModeType.PESSIMISTIC_WRITE)
        @Query("SELECT o FROM Order o WHERE o.id = :id")
        Optional<Order> findByIdForUpdate(@Param("id") Long id);
    }
    
    • PESSIMISTIC_WRITEλŠ” SELECT ... FOR UPDATEλ₯Ό μ‹€ν–‰ν•˜μ—¬ row-level exclusive lock을 κ²λ‹ˆλ‹€.
    • 이 μΏΌλ¦¬λŠ” ν•΄λ‹Ή rowλ₯Ό λ‹€λ₯Έ νŠΈλžœμž­μ…˜μ—μ„œ λ³€κ²½ν•˜μ§€ λͺ»ν•˜κ²Œ λ§‰μŠ΅λ‹ˆλ‹€.
    • PESSIMISTIC_READλŠ” κ³΅μœ λ½μ„ κ±Έλ©° μ½κΈ°λŠ” ν—ˆμš©ν•˜λ˜ μ“°κΈ°λŠ” λ§‰μŠ΅λ‹ˆλ‹€.

    ⚠️ 주의: 이 방법은 λ°˜λ“œμ‹œ νŠΈλžœμž­μ…˜(@Transactional) μ•ˆμ—μ„œ μ‚¬μš©λ˜μ–΄μ•Ό μ‹€μ œλ‘œ 락이 μ μš©λ©λ‹ˆλ‹€.

    Created Thu, 24 Jul 2025 10:07:45 +0900
  • μ•„λž˜μ— Java Pathfinder (JPF) μ‚¬μš©ν•˜λŠ” 방법을 μžμ„Ένžˆ μ•ˆλ‚΄λ“œλ¦½λ‹ˆλ‹€. Java PathfinderλŠ” μžλ°” ν”„λ‘œκ·Έλž¨μ˜ λͺ¨λΈ 체킹(model checking) λ„κ΅¬λ‘œ, ν”„λ‘œκ·Έλž¨μ˜ μƒνƒœ 곡간을 νƒμƒ‰ν•˜μ—¬ λ²„κ·Έλ‚˜ λ°λ“œλ½ 등을 μ°ΎλŠ” 데 ν™œμš©λ©λ‹ˆλ‹€.

    μ•„λž˜ λ‚΄μš©μ„ λ‹¨κ³„λ³„λ‘œ μ„€λͺ…λ“œλ¦΄κ²Œμš”.


    🧭 1. Java Pathfinder(JPF)λž€?

    **Java Pathfinder (JPF)**λŠ” μžλ°” ν”„λ‘œκ·Έλž¨μ„ μ‹€ν–‰ν•˜λ©° μƒνƒœκ³΅κ°„μ„ μ²΄κ³„μ μœΌλ‘œ νƒμƒ‰ν•˜λŠ” νˆ΄μž…λ‹ˆλ‹€.

    μ£Όμš” κΈ°λŠ₯

    • λ©€ν‹°μŠ€λ ˆλ”© 버그 탐지
    • Assertion μœ„λ°˜ κ²€μΆœ
    • Deadlock 탐지
    • Non-deterministic λ™μž‘ 검증
    • Symbolic execution (jpf-symbc) 지원

    βš™οΈ 2. μ„€μΉ˜

    JPFλŠ” GitHub μ €μž₯μ†Œμ—μ„œ 직접 λΉŒλ“œν•˜λŠ” 것이 μΌλ°˜μ μž…λ‹ˆλ‹€.

    Created Thu, 03 Jul 2025 10:49:24 +0900
  • μ•„λž˜μ— Javaμ—μ„œ PIT (Pitest) Mutation Testing을 μ‚¬μš©ν•˜λŠ” 방법을 λ‹¨κ³„λ³„λ‘œ 정리해 λ“œλ¦½λ‹ˆλ‹€. PITλŠ” μžλ°” μ½”λ“œμ˜ ν…ŒμŠ€νŠΈ 컀버리지λ₯Ό μ‹€μ œλ‘œ κ²€μ¦ν•˜λŠ” κ°•λ ₯ν•œ λ„κ΅¬λ‘œ, 잘λͺ»λœ(λ³€μ΄λœ) μ½”λ“œλ₯Ό ν…ŒμŠ€νŠΈκ°€ μ–Όλ§ˆλ‚˜ 잘 μž‘μ•„λ‚΄λŠ”μ§€λ₯Ό μΈ‘μ •ν•©λ‹ˆλ‹€.


    πŸ“Œ 1. PIT(Pitest) μ†Œκ°œ

    • Mutation Testingμ΄λž€?

      • κΈ°μ‘΄ μ½”λ“œλ₯Ό μΌλΆ€λŸ¬ β€œλ³€μ΄(mutant)” μ‹œμΌœμ„œ ν…ŒμŠ€νŠΈλ₯Ό λŒλ €λ³΄λŠ” κΈ°λ²•μž…λ‹ˆλ‹€.
      • ν…ŒμŠ€νŠΈκ°€ 이 변이λ₯Ό κ²€μΆœν•˜μ§€ λͺ»ν•˜λ©΄, ν…ŒμŠ€νŠΈμ— ν—ˆμ μ΄ μžˆλ‹€λŠ” λœ»μž…λ‹ˆλ‹€.
      • λ‹¨μˆœν•œ 라인 컀버리지보닀 더 μ‹ λ’°μ„± μžˆλŠ” ν…ŒμŠ€νŠΈ ν’ˆμ§ˆ μ§€ν‘œλ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€.
    • PIT의 νŠΉμ§•

      • JUnit 기반 ν…ŒμŠ€νŠΈμ™€ 잘 ν†΅ν•©λ©λ‹ˆλ‹€.
      • Maven, Gradle, Antμ—μ„œ μ‰½κ²Œ μ‚¬μš© κ°€λŠ₯ν•©λ‹ˆλ‹€.
      • HTML 및 XML 리포트λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.

    βš™οΈ 2. Maven ν”„λ‘œμ νŠΈμ— PIT 적용

    2.1 Maven ν”ŒλŸ¬κ·ΈμΈ μΆ”κ°€

    pom.xml에 λ‹€μŒ ν”ŒλŸ¬κ·ΈμΈμ„ μ„€μ •ν•©λ‹ˆλ‹€:

    Created Thu, 03 Jul 2025 10:44:24 +0900
  • Spring Boot 3μ—μ„œ SSH 터널을 ν†΅ν•œ PostgreSQL 연결을 μœ„ν•œ μ„€μ • 방법을 μ•Œλ €λ“œλ¦¬κ² μŠ΅λ‹ˆλ‹€.

    1. application.yml μ„€μ •

    spring:
      datasource:
        url: jdbc:postgresql://localhost:5433/your_database_name
        username: your_db_username
        password: your_db_password
        driver-class-name: org.postgresql.Driver
      
      jpa:
        hibernate:
          ddl-auto: validate
        show-sql: true
        properties:
          hibernate:
            dialect: org.hibernate.dialect.PostgreSQLDialect
            format_sql: true
    
    # SSH 터널 μ„€μ • (μ»€μŠ€ν…€ ν”„λ‘œνΌν‹°)
    app:
        ssh:
          tunnel:
            host: your-ssh-server.com
            port: 22
            username: ssh_username
            private-key-path: ~/.ssh/id_rsa
            local-port: 5433
            remote-host: localhost  # SSH μ„œλ²„μ—μ„œ λ³Έ DB 호슀트
            remote-port: 5432       # μ‹€μ œ PostgreSQL 포트
    

    2. SSH 터널 μžλ™ 연결을 μœ„ν•œ Configuration 클래슀## 3. ν•„μš”ν•œ μ˜μ‘΄μ„± μΆ”κ°€ (build.gradle)

    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
        implementation 'org.postgresql:postgresql'
        implementation 'com.github.mwiede:jsch:0.2.17'
        
        // 기타 ν•„μš”ν•œ μ˜μ‘΄μ„±λ“€...
    }
    

    3. ssh key λ§Œλ“€κΈ° μ˜ˆμ‹œ

    ssh-keygen -t rsa -b 4096 -m PEM -f ~/.ssh/id_rsa -C "ceo@pink-spider.io"
    
    • DBκ°€ μ„€μΉ˜λœ μ„œλ²„μ— ~/.ssh 등에 authorized_keys (κΆŒν•œ 644) νŒŒμΌμ— id_rsa.pub λ‚΄μš© μΆ”κ°€ν•΄μ•Ό λ©λ‹ˆλ‹€.
    • rsaκ°€ μ•„λ‹Œ OpenSSh둜 keygen ν–ˆμ„ 경우 com.github.mwiede:jsch κ°€ μ•„λ‹Œ λ‹€λ₯Έ 라이브러리λ₯Ό 써야 λ©λ‹ˆλ‹€.

    SshTunnelConfig

    
    @Configuration
    @Slf4j
    @Profile({"local", "dev", "stage", "prod"})
    public class SshTunnelConfig {
    
        @Value("${app.ssh.tunnel.host}")
        private String sshHost;
    
        @Value("${app.ssh.tunnel.port}")
        private int sshPort;
    
        @Value("${app.ssh.tunnel.username}")
        private String sshUsername;
    
        @Value("${app.ssh.tunnel.private-key-path}")
        private String privateKeyPath;
    
        @Value("${app.ssh.tunnel.local-port}")
        private int localPort;
    
        @Value("${app.ssh.tunnel.remote-host}")
        private String remoteHost;
    
        @Value("${app.ssh.tunnel.remote-port}")
        private int remotePort;
    
        @Value("${app.ssh.tunnel.passphrase:}")
        private String passphrase;
    
        private Session session;
    
        @PostConstruct
        public void createSshTunnel() {
            try {
                log.info("JSch SSH 터널 μ—°κ²° μ‹œμž‘...");
    
                // 둜컬 ν¬νŠΈκ°€ μ‚¬μš© κ°€λŠ₯ν•œμ§€ 확인
                if (!isPortAvailable(localPort)) {
                    throw new RuntimeException("둜컬 포트 " + localPort + "κ°€ 이미 μ‚¬μš© μ€‘μž…λ‹ˆλ‹€.");
                }
    
                JSch jsch = new JSch();
    
                // κ°œμΈν‚€ 파일 경둜 처리
                String keyPath = privateKeyPath;
                if (keyPath.startsWith("~/")) {
                    keyPath = System.getProperty("user.home") + keyPath.substring(1);
                }
    
                // 파일 쑴재 및 읽기 κΆŒν•œ 확인
                File keyFile = new File(keyPath);
                if (!keyFile.exists()) {
                    throw new RuntimeException("SSH κ°œμΈν‚€ νŒŒμΌμ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€: " + keyPath);
                }
    
                if (!keyFile.canRead()) {
                    throw new RuntimeException("SSH κ°œμΈν‚€ νŒŒμΌμ„ 읽을 수 μ—†μŠ΅λ‹ˆλ‹€. 파일 κΆŒν•œμ„ ν™•μΈν•˜μ„Έμš”: " + keyPath);
                }
    
                log.info("SSH κ°œμΈν‚€ 파일: {}", keyPath);
    
                // ν‚€ ν˜•μ‹ 확인 (RSA ν˜•μ‹μΈμ§€ 체크)
                try (java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.FileReader(keyFile))) {
                    String firstLine = reader.readLine();
                    log.info("ν‚€ 파일 ν˜•μ‹: {}", firstLine);
    
                    if (firstLine != null && !firstLine.contains("BEGIN RSA PRIVATE KEY")) {
                        log.warn("RSA ν˜•μ‹μ΄ μ•„λ‹Œ ν‚€κ°€ κ°μ§€λ˜μ—ˆμŠ΅λ‹ˆλ‹€: {}", firstLine);
                        log.warn("RSA ν˜•μ‹ ν‚€λ₯Ό μƒμ„±ν•˜λ €λ©΄: ssh-keygen -t rsa -b 4096 -m PEM -f {}", keyPath);
                    }
                }
    
                // κ°œμΈν‚€ μΆ”κ°€
                if (passphrase != null && !passphrase.trim().isEmpty()) {
                    jsch.addIdentity(keyPath, passphrase);
                    log.info("νŒ¨μŠ€ν”„λ ˆμ΄μ¦ˆμ™€ ν•¨κ»˜ κ°œμΈν‚€ λ‘œλ“œ μ™„λ£Œ");
                } else {
                    jsch.addIdentity(keyPath);
                    log.info("κ°œμΈν‚€ λ‘œλ“œ μ™„λ£Œ");
                }
    
                // SSH μ„Έμ…˜ 생성
                session = jsch.getSession(sshUsername, sshHost, sshPort);
                session.setConfig("StrictHostKeyChecking", "no");
                session.setConfig("PreferredAuthentications", "publickey");
                session.setTimeout(30000);
    
                log.info("SSH μ„œλ²„ μ—°κ²° μ‹œλ„: {}@{}:{}", sshUsername, sshHost, sshPort);
                session.connect();
                log.info("SSH μ—°κ²° 성곡");
    
                // 포트 ν¬μ›Œλ”© μ„€μ •
                session.setPortForwardingL(localPort, remoteHost, remotePort);
    
                // ν¬νŠΈκ°€ 열릴 λ•ŒκΉŒμ§€ λŒ€κΈ°
                waitForPortToOpen(localPort, 10000);
    
                log.info("SSH 터널이 μ„±κ³΅μ μœΌλ‘œ μƒμ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€: localhost:{} -> {}:{}:{}",
                    localPort, sshHost, remoteHost, remotePort);
    
            } catch (JSchException e) {
                log.error("SSH 터널 생성 μ‹€νŒ¨: {}", e.getMessage(), e);
    
                // ꡬ체적인 μ—λŸ¬ λ©”μ‹œμ§€ 제곡
                if (e.getMessage().contains("invalid privatekey")) {
                    log.error("κ°œμΈν‚€ ν˜•μ‹μ΄ 잘λͺ»λ˜μ—ˆμŠ΅λ‹ˆλ‹€. RSA ν˜•μ‹ ν‚€λ₯Ό μ‚¬μš©ν•˜μ„Έμš”.");
                    log.error("μƒˆ RSA ν‚€ 생성: ssh-keygen -t rsa -b 4096 -m PEM -f ~/.ssh/level_up_rsa");
                } else if (e.getMessage().contains("Auth fail")) {
                    log.error("인증에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€. κ³΅κ°œν‚€κ°€ μ„œλ²„μ— λ“±λ‘λ˜μ–΄ μžˆλŠ”μ§€ ν™•μΈν•˜μ„Έμš”.");
                    log.error("κ³΅κ°œν‚€ 등둝: ssh-copy-id -i {} {}@{}", privateKeyPath + ".pub", sshUsername, sshHost);
                } else if (e.getMessage().contains("Connection refused")) {
                    log.error("μ„œλ²„ 연결이 κ±°λΆ€λ˜μ—ˆμŠ΅λ‹ˆλ‹€. ν˜ΈμŠ€νŠΈμ™€ 포트λ₯Ό ν™•μΈν•˜μ„Έμš”.");
                }
    
                cleanupResources();
                throw new RuntimeException("SSH 터널 연결에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€: " + e.getMessage(), e);
            } catch (Exception e) {
                log.error("μ˜ˆμƒμΉ˜ λͺ»ν•œ 였λ₯˜: {}", e.getMessage(), e);
                cleanupResources();
                throw new RuntimeException("SSH 터널 μ„€μ • 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€: " + e.getMessage(), e);
            }
        }
    
        @PreDestroy
        @EventListener(ContextClosedEvent.class)
        public void closeSshTunnel() {
            log.info("SSH 터널 μ’…λ£Œ μ‹œμž‘...");
            cleanupResources();
            log.info("SSH 터널 μ’…λ£Œ μ™„λ£Œ");
        }
    
        private void cleanupResources() {
            if (session != null && session.isConnected()) {
                try {
                    session.disconnect();
                    log.info("SSH μ„Έμ…˜μ΄ μ’…λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.");
                } catch (Exception e) {
                    log.warn("SSH μ„Έμ…˜ μ’…λ£Œ 쀑 였λ₯˜: {}", e.getMessage());
                }
            }
        }
    
        private boolean isPortAvailable(int port) {
            try (ServerSocket serverSocket = new ServerSocket(port)) {
                return true;
            } catch (Exception e) {
                return false;
            }
        }
    
        private void waitForPortToOpen(int port, long timeoutMillis) throws InterruptedException {
            long startTime = System.currentTimeMillis();
            while (System.currentTimeMillis() - startTime < timeoutMillis) {
                try (java.net.Socket socket = new java.net.Socket()) {
                    socket.connect(new java.net.InetSocketAddress("localhost", port), 1000);
                    log.info("포트 {}κ°€ μ„±κ³΅μ μœΌλ‘œ μ—΄λ ΈμŠ΅λ‹ˆλ‹€.", port);
                    return;
                } catch (Exception e) {
                    // ν¬νŠΈκ°€ 아직 열리지 μ•ŠμŒ, μž¬μ‹œλ„
                    Thread.sleep(500);
                }
            }
            log.warn("포트 {}κ°€ {}ms 내에 열리지 μ•Šμ•˜μŠ΅λ‹ˆλ‹€.", port, timeoutMillis);
        }
    }
    
    Created Thu, 03 Jul 2025 08:44:24 +0900
  • **Property-Based Testing(PBT)**은 μž…λ ₯κ³Ό κ²°κ³Ό κ°„μ˜ **일반적인 속성(Property)**을 μ •μ˜ν•˜κ³ , ν…ŒμŠ€νŠΈ ν”„λ ˆμž„μ›Œν¬κ°€ κ·Έ 속성을 μΆ©μ‘±ν•˜λŠ”μ§€λ₯Ό λ¬΄μž‘μœ„λ‘œ μƒμ„±λœ λ‹€μ–‘ν•œ μž…λ ₯값에 λŒ€ν•΄ ν™•μΈν•˜λŠ” λ°©μ‹μ˜ ν…ŒμŠ€νŠΈμž…λ‹ˆλ‹€. λ‹¨μœ„ ν…ŒμŠ€νŠΈμ²˜λŸΌ νŠΉμ • μž…λ ₯κ°’λ§Œ ν™•μΈν•˜λŠ” 것이 μ•„λ‹ˆλΌ, 수천 개의 μž…λ ₯ 쑰합을 μžλ™μœΌλ‘œ 검사할 수 μžˆλ‹€λŠ” μ μ—μ„œ 맀우 κ°•λ ₯ν•©λ‹ˆλ‹€.


    πŸ” Property-Based Testingμ΄λž€?

    βœ… 핡심 κ°œλ…

    • 속성(Property): ν…ŒμŠ€νŠΈ λŒ€μƒ ν•¨μˆ˜λ‚˜ 둜직이 항상 λ§Œμ‘±ν•΄μ•Ό ν•˜λŠ” 일반적인 법칙 λ˜λŠ” κ·œμΉ™.
    • μž…λ ₯κ°’ μžλ™ 생성: PBT ν”„λ ˆμž„μ›Œν¬κ°€ λ‹€μ–‘ν•œ μž…λ ₯값을 λ¬΄μž‘μœ„λ‘œ 생성.
    • Fail case μΆ•μ†Œ(Shrinking): μ‹€νŒ¨ν•œ μž…λ ₯값이 λ°œμƒν–ˆμ„ λ•Œ, μ‹€νŒ¨λ₯Ό μž¬ν˜„ν•  수 μžˆλŠ” μ΅œμ†Œ μž…λ ₯κ°’μœΌλ‘œ μ€„μ—¬μ„œ 디버깅을 μ‰½κ²Œ 함.

    πŸ†š λ‹¨μœ„ ν…ŒμŠ€νŠΈμ™€μ˜ 비ꡐ

    ν•­λͺ© λ‹¨μœ„ ν…ŒμŠ€νŠΈ (Example-based) 속성 기반 ν…ŒμŠ€νŠΈ (Property-based)
    μž…λ ₯κ°’ κ³ μ •λœ 사둀 μˆ˜λ™ μ§€μ • μžλ™ μƒμ„±λœ λ‹€μ–‘ν•œ κ°’
    ν…ŒμŠ€νŠΈ λ²”μœ„ μ œν•œμ  맀우 κ΄‘λ²”μœ„
    μœ μ§€λ³΄μˆ˜ 둜직 λ³€κ²½ μ‹œ ν…ŒμŠ€νŠΈ μΆ”κ°€ ν•„μš” Property만 잘 μ •μ˜ν•˜λ©΄ μžλ™ 적용
    μ‹€νŒ¨ μ‹œ 디버깅 λͺ…ν™•ν•œ μž…λ ₯κ°’ μΆ•μ†Œ κ³Όμ • ν•„μš”

    πŸ§ͺ Spring Bootμ—μ„œ Property-Based Test μ‚¬μš©ν•˜λŠ” 방법

    Spring Boot μžμ²΄λŠ” JUnit을 기반으둜 ν•œ λ‹¨μœ„ ν…ŒμŠ€νŠΈκ°€ 기본이며, PBTλŠ” μ™ΈλΆ€ 라이브러리λ₯Ό μ‚¬μš©ν•΄μ„œ ν†΅ν•©ν•˜λŠ” 것이 μΌλ°˜μ μž…λ‹ˆλ‹€. κ°€μž₯ 널리 μ“°μ΄λŠ” Java 기반 λΌμ΄λΈŒλŸ¬λ¦¬λŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€:

    Created Fri, 27 Jun 2025 10:18:03 +0900