자동화 시스템을 이해하고 통제하려면 무엇부터 살펴야 할까요? 무엇을 읽을 수 있는지, 어디까지 판단하는지, 어떤 조건에서 행동하는지 살펴야 합니다. 자동화가 개입하면 이 경계가 기능의 개수보다 시스템을 이해하고 통제하는 데 중요한 기준이 됩니다. 자동화 기능을 늘릴 때는 사용자가 그 동작을 이해할 기준도 필요합니다. 배포 상황을 묻는 일과 과거 기록을 찾는 일 SkillsMP의 discord-clawd 설명에서는 전송 전의 대상 확인과 실행 후의 보고가 짝을 이룹니다. 보내기 전에는 누구에게 전달할지 확인합니다. 실행한 뒤에는 대상과 전달 모드를 알려 줍니다. 이렇게 하면 대상 선택과 전달 방식, 실행 결과를 각각 살펴볼 수 있습니다. ( https://skillsmp.com/skill/openclaw-openclaw-agents-skills-discord-clawd-skill-md ) 배포 상황에 관한 두 요청은 작업을 구별하기 위한 설명용 예시입니다. 유지보수 담당자에게 묻는 요청에서는 새로운 대화를 시작해야 합니다. 예전에 논의한 내용을 찾는 요청에서는 이미 남은 기록을 읽어야 합니다. 다루는 주제가 같아도 사용자가 원하는 결과에 따라 필요한 작업이 달라집니다. 문서에 대상 확인을 요구하는 것과 코드가 잘못된 대상으로의 전송을 막는 것은 서로 다른 문제입니다. 사용자에게 명시적인 게시 요청을 받지 않은 강제 전송을 거부하는지도 별도로 살펴야 합니다. 스킬 설명만으로는 이 두 조건을 코드가 강제하는지 알 수 없습니다. OHTTP가 나누는 정보와 보호의 조건 GeekNews의 Cloudflare OHTTP 게이트웨이 소개에서 릴레이가 받는 요청 본문은 암호문입니다. 릴레이에는 클라이언트 IP 같은 연결 정보가 보입니다. 반면 게이트웨이는 요청의 암호화를 풀어 앱 서버에 전달합니다. 요청을 전달하는 과정에서 각 역할이 접하는 정보가 달라지는 구조입니다. ( https://news.hada.io/topic?id=34719 ) 이 정보 분리에는 운영 조건이 붙습니다. 기사에서는 릴레이와 앱 서버를 서로 공모하지 않는 별도 주체가 운영해야 한다고 설명합니다. 앱 서버가 Cloudflare 뒤에 있다면 외부 릴레이와 Cloudflare OHTTP Gateway를 조합하는 방식을 제시합니다. 암호화 방식뿐 아니라 누가 각 역할을 운영하는지도 설계에 포함됩니다. 기사에 따르면 Workers나 Cloudflare 프록시 호스트에서 들어온 요청은 복호화를 거부합니다. 같은 사업자 안에서 역할이 겹치는 상황을 막기 위한 조건입니다. 그러나 개발자가 요청 본문에 이메일이나 사용자 이름을 넣으면 앱 서버는 그 내용을 받습니다. 연결 정보의 분리와 본문에 담을 식별 정보의 선택은 각각 살펴야 합니다. GeekNews 소개 당시 게이트웨이는 비공개 베타 상태였습니다. 가을에 유료 부가 기능으로 내놓겠다는 내용은 당시의 출시 계획입니다. 현재 정식 출시 여부는 이 소개만으로 알 수 없으므로, 제품 상태를 읽을 때는 소개 시점과 계획의 성격을 함께 보아야 합니다. 창 배치를 제안하는 모델과 변경하는 코드 GeekNews의 tmux를 OS로 만들자에 소개된 구상은 데스크톱을 끝없이 펼쳐지는 작업 공간으로 봅니다. 물리 모니터는 그 공간의 일부를 보여 줍니다. 모니터 연결이 달라져도 창의 순서와 어떤 작업에 속한 창인지를 유지하려는 생각입니다. ( https://news.hada.io/topic?id=34720 ) 이 구상은 tmux에서 세션을 유지하고 작업을 중심으로 사용하는 경험을 일반 사용자에게 확장하려는 Linux 실험입니다. 로컬 언어 모델인 LLM은 창 배치안을 만들고 미리보기를 보여 주는 역할을 맡습니다. 실제 변경은 사용자 승인을 거친 뒤 결정론적 코드가 맡도록 설계합니다. 모델이 창을 직접 조작하지 못하게 하고, 고정해 둔 창도 변경하지 못하게 합니다. 초기 실험에서는 행동 데이터 수집이 프라이버시와 충돌했고, 캔버스가 끝없이 쌓이는 문제도 나타났습니다. 입력이 없는 유휴 상태를 근거로 창을 숨기자 읽기를 방해했습니다. 사용자가 키보드나 마우스를 움직이지 않아도 읽기는 계속될 수 있습니다. 따라서 입력이 없다는 신호를 작업 종료로 해석해도 되는지 판단해야 합니다. 새 도구의 설명에서 실행 조건까지 점검하기 세 사례를 참고해 새 에이전트 도구를 어떤 순서로 점검할 수 있을까요? 먼저 설명문에 기록 읽기와 새 대화 중 도구의 역할, 대상 확정 시점, 결과에 남길 전달 정보를 명시합니다. 이어서 넘길 데이터가 필요한 범위인지 확인하고, 중요한 실행 조건이 문서 밖에서도 검사되는지 점검합니다. 점검할 대상은 사례마다 다릅니다. 새 도구에 적용할 때도 어떤 경계를 정하려는지 먼저 분명히 해야 합니다. 도구 설명을 구체화합니다 — 작업 역할과 대상 확정 시점, 결과에 남길 전달 정보를 명시합니다. 넘길 데이터를 살핍니다 — 도구에 전달할 데이터가 작업에 필요한 범위인지 확인합니다. 실행 단계의 검사를 살핍니다 — 중요한 실행 조건이 문서 밖에서도 검사되는지 점검합니다. 무엇을 근거로 누가 실행하는지 드러내기 적용할 기준은 기능이 다루는 대상에 맞춰 잡아야 합니다. 메시지를 보내는 도구에는 전달 조건이, 요청을 중계하는 구조에는 정보의 분리가 필요합니다. 창 배치를 제안하는 모델에는 실제 변경으로 이어지는 조건이 필요합니다. 각 사례에서 나누는 대상을 구체적으로 짚는 것이 설계 점검의 출발점입니다. 원문: webi 기술 블로그 참고한 자료: SkillsMP Agent Skill: discord-clawd (openclaw) Cloudflare OHTTP 게이트웨이 tmux를 OS로 만들자
60일차 1. 배운 내용 멀티 에이전트 시스템의 평가 및 피드백 멀티 에이전트 시스템에서도 기준에 따른 평가와 피드백 루프가 시스템 유지 보수 및 개선에 필요하다. 적절한 정책에 따른 기준들이 존재하면 예외 사항에 대한 대처와 성능에 대한 개선을 자동적으로 이뤄지게 할 수 있다. 2. 실제 작업한 내용 실습 평가 및 피드백에 대한 간단한 실습으로 평가에 대한 기준, 평가 에이전트, 피드백 루프, 실패 시 대응, 부분 복구, 트레이스의 간단한 예제를 살펴보았다. 팀 프로젝트 팀장으로서 3일 프로젝트를 앞두고 주제를 정하고 협업이 잘 이뤄지도록 회의를 주도했다. 1,2차와 다르게 시간이 별로 없지만 지금까지 잘 배운 것들을 사용한다면 괜찮을 거라 생각하고 있다. 3. 소감 프로젝트 회의를 하는 데에 팀원들의 의욕 넘치는 모습이 좋았던 것 같다. 그리고 회의를 잘 따라와 줘서 감사한 마음이 들었다. 내 자신이 보기에는 좀 어려운 주제인데 팀장으로서 크게 벗어나는 부분만 조율하면 팀원들의 역량으로 할 수 있을 것 같다.
오늘은 평소보다 거지 같은 세상을 살아가야 한다는 사실이 너무나 화가 났다. 길거리에서 시끄럽고 짜증 나게 구는 이름 모를 사람 네 명 정도도 패고 싶었는데, 감옥에 갈 수도 있다는 걸 떠올리며 패지 않았다. 감옥에는 절대 가고 싶지 않다. 내가 싫어하는 주거 환경의 특징은 역겨운 냄새가 남 4인 이상의 인원이 한 방을 쓰는 기숙사 형태 기숙사 사감과 빡센 규칙이 있음 인데, 감옥은 내가 싫어하는 모든 주거 환경의 특징을 모두 갖추었을 듯하기 때문이다. 열심히 치료해서 간헐적 폭발 장애에서 벗어나고 싶다.
김영한님의 자바 ORM 표준 JPA 프로그래밍 강의를 듣고 정리한 글입니다. 실습 환경: PostgreSQL + Hibernate 6(jakarta) 1. 프록시 문제 제기: Member를 조회할 때 Team도 함께 조회해야 할까? // 1 회원과 팀을 함께 출력 public void printUserAndTeam(Long memberId) { Member member = em.find(Member.class, memberId); Team team = member.getTeam(); System.out.println("회원 이름: " + member.getUsername()); System.out.println("소속팀: " + team.getName()); } // 2 회원만 출력 public void printUser(Long memberId) { Member member = em.find(Member.class, memberId); System.out.println("회원 이름: " + member.getUsername()); } 1은 Team이 필요하니 같이 가져오면 이득 입니다. 2는 Team을 쓰지도 않는데 같이 가져오면 손해 입니다. 비즈니스 로직마다 필요한 데이터가 다릅니다. JPA는 이 문제를 프록시 와 지연 로딩 으로 해결합니다. 지연 로딩을 이해하려면 먼저 프록시를 알아야 합니다. em.find() vs em.getReference() 메서드 동작 em.find() DB를 통해서 실제 엔티티 객체 를 조회 (바로 SELECT) em.getReference() DB 조회를 미루는 가짜(프록시) 엔티티 객체 를 조회 (SELECT 안 함) Member findMember = em.getReference(Member.class, member.getId()); System.out.println("findMember = " + findMember.getClass()); // findMember = class hellojpa.Member$HibernateProxy$a1b2c3 ← 가짜! System.out.println("findMember.id = " + findMember.getId()); // SELECT 안 나감 (id는 이미 알고 있으니까) System.out.println("findMember.username = " + findMember.getUsername()); // 이 시점에 SELECT 쿼리 실행! 💡 강의 자료에는 프록시 클래스명이 javassist 로 나오지만, Hibernate 6은 ByteBuddy 를 써서 Member$HibernateProxy$... 형태로 출력됩니다. 프록시의 구조 Member (실제 클래스) - id, name - getId(), getName() △ │ 상속 MemberProxy (가짜) - Member target = null ← 실제 객체를 가리킬 참조 - getId(), getName() 실제 클래스를 상속받아서 만들어집니다. (Hibernate가 런타임에 자동 생성) 실제 클래스와 겉모양이 같습니다. 사용하는 입장에서는 진짜 객체인지 프록시인지 구분하지 않고 쓰면 됩니다. (이론상) 프록시는 실제 객체의 참조(target)를 보관 합니다. 프록시의 메서드를 호출하면 실제 객체의 메서드를 대신 호출(위임, delegate) 합니다. 프록시 객체의 초기화 Member member = em.getReference(Member.class, 1L); member.getName(); 1. Client → member.getName() 호출 2. 프록시: "target이 null이네?" → 영속성 컨텍스트에 초기화 요청 3. 영속성 컨텍스트 → DB 조회 4. 실제 Member 엔티티 생성 5. 프록시의 target에 실제 엔티티 연결 → target.getName() 호출 💡 비유: 프록시는 택배 송장 과 같습니다. 송장(프록시)만 받았을 때는 상자를 열어 보지 않습니다. 송장에 적힌 운송장 번호(id) 는 바로 알 수 있습니다. 실제 내용물(name)이 궁금해지는 순간 그제야 창고(DB)에 가서 물건을 가져옵니다. 2. 프록시의 특징 (중요! 실무에서 헷갈리는 부분) 1 프록시 객체는 처음 사용할 때 한 번만 초기화된다 member.getName(); // SELECT 실행 member.getName(); // 이미 초기화됨 → SELECT 없음 2 초기화되어도 프록시가 실제 엔티티로 바뀌는 게 아니다 초기화되면 프록시를 통해서 실제 엔티티에 접근 할 수 있게 될 뿐입니다. getClass() 를 찍어 보면 초기화 후에도 여전히 Member$HibernateProxy$... 입니다. 3 타입 체크 시 == 대신 instanceof 를 써야 한다 프록시는 원본 엔티티를 상속 받은 다른 클래스입니다. Member m1 = em.find(Member.class, member1.getId()); // 실제 객체 Member m2 = em.getReference(Member.class, member2.getId()); // 프록시 System.out.println(m1.getClass() == m2.getClass()); // false 😱 System.out.println(m2 instanceof Member); // true ✅ ⚠️ 실무에서는 내가 받은 객체가 프록시인지 아닌지 모르는 경우가 많습니다. 그래서 엔티티 타입 비교는 항상 instanceof 로 해야 합니다. equals() 를 직접 구현할 때도 getClass() 비교 대신 instanceof 를 쓰고, 필드 대신 getter 로 비교해야 프록시에서도 안전합니다. 4 영속성 컨텍스트에 이미 있으면 getReference()를 해도 실제 엔티티를 반환한다 Member m1 = em.find(Member.class, member.getId()); // 1차 캐시에 실제 객체 저장 Member ref = em.getReference(Member.class, member.getId()); // 실제 객체 반환! System.out.println(ref.getClass()); // class hellojpa.Member System.out.println(m1 == ref); // true 이미 1차 캐시에 실제 객체가 있는데 굳이 프록시를 만들 이유가 없습니다. 💡 반대 상황도 있습니다. Member ref = em.getReference(Member.class, member.getId()); // 프록시 Member find = em.find(Member.class, member.getId()); // 프록시 반환! System.out.println(ref == find); // true 먼저 프록시가 만들어졌다면 em.find() 도 같은 프록시를 반환 합니다. 왜일까요? JPA는 같은 트랜잭션 안에서 같은 엔티티의 == 비교가 항상 true임을 보장 해야 하기 때문입니다. (영속성 컨텍스트의 동일성 보장) 5 준영속 상태에서 프록시를 초기화하면 예외가 발생한다 Member ref = em.getReference(Member.class, member.getId()); em.detach(ref); // 또는 em.close(), em.clear() ref.getUsername(); // 💥 LazyInitializationException org.hibernate.LazyInitializationException: could not initialize proxy - no Session 프록시 초기화는 영속성 컨텍스트의 도움 을 받아서 DB를 조회하는 건데, 영속성 컨텍스트에서 분리됐으니 조회할 방법이 없습니다. 💡 실무에서 정말 자주 만나는 에러입니다. 예: 서비스 계층에서 트랜잭션이 끝난 뒤, 컨트롤러나 뷰에서 member.getTeam().getName() 을 호출하는 경우 트랜잭션이 끝나면 영속성 컨텍스트도 닫히기 때문에 이 에러가 납니다. 해결책은 뒤에서 배울 페치 조인 , 또는 트랜잭션 안에서 필요한 데이터를 미리 DTO로 변환하는 것입니다. 3. 프록시 확인 방법 목적 방법 초기화 여부 확인 emf.getPersistenceUnitUtil().isLoaded(entity) 프록시 클래스 확인 entity.getClass().getName() 출력 → $HibernateProxy$ 포함 여부 강제 초기화 Hibernate.initialize(entity); (Hibernate 제공) 강제 초기화 (JPA 표준) 표준에는 없음 → member.getName() 처럼 메서드를 직접 호출 Member ref = em.getReference(Member.class, member.getId()); System.out.println(emf.getPersistenceUnitUtil().isLoaded(ref)); // false Hibernate.initialize(ref); // 강제 초기화 System.out.println(emf.getPersistenceUnitUtil().isLoaded(ref)); // true 💡 em.getReference() 는 실무에서 직접 쓸 일이 거의 없습니다. 하지만 지연 로딩이 내부적으로 이 프록시를 사용 하기 때문에 원리를 꼭 알아야 합니다. 4. 즉시 로딩과 지연 로딩 지연 로딩 (LAZY) - 프록시로 조회 "Member만 쓰는 로직이 대부분이라면?" @Entity public class Member { @Id @GeneratedValue private Long id; @Column(name = "USERNAME") private String name; @ManyToOne(fetch = FetchType.LAZY) // ★ @JoinColumn(name = "TEAM_ID") private Team team; } Member member = em.find(Member.class, 1L); // SELECT * FROM member WHERE id = 1 ← Team은 조회 안 함 Team team = member.getTeam(); System.out.println(team.getClass()); // Team$HibernateProxy$... (프록시) team.getName(); // SELECT * FROM team WHERE id = ? ← 실제 Team을 사용하는 시점에 조회 1. 로딩 → member1 (실제 엔티티) 2. 지연 로딩 LAZY → team1 (프록시 엔티티) Member를 조회할 때 team 필드에는 프록시를 넣어 둡니다. team.getName() 처럼 실제로 Team의 데이터를 사용하는 시점에 초기화(DB 조회)됩니다. ⚠️ member.getTeam() 만으로는 초기화되지 않습니다. 프록시를 꺼낸 것뿐입니다. getTeam().getName() 처럼 프록시의 메서드를 호출해야 쿼리가 나갑니다. 즉시 로딩 (EAGER) - 함께 조회 "Member와 Team을 항상 같이 쓴다면?" @ManyToOne(fetch = FetchType.EAGER) // ★ @JoinColumn(name = "TEAM_ID") private Team team; Member member = em.find(Member.class, 1L); select m.*, t.* from member m left join team t on m.team_id = t.team_id where m.id = ? Member를 조회할 때 항상 Team도 함께 조회합니다. JPA 구현체는 가능하면 조인을 사용해서 SQL 한 번에 함께 조회합니다. team 에는 프록시가 아니라 실제 엔티티 가 들어갑니다. 5. 프록시와 즉시 로딩 주의 ⚠️ ✅ 가급적 지연 로딩만 사용하세요. (특히 실무에서) 이유 1 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생한다 연관관계가 2~3개일 때는 괜찮지만, 실무에서는 엔티티 하나에 연관관계가 10개 넘게 있기도 합니다. 전부 EAGER면 Member 하나 조회했는데 테이블 10개가 조인 되는 쿼리가 나갑니다. em.find(Member.class, 1L); // → member join team join locker join order join ... 😱 이유 2 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다 (가장 중요!) // Member.team이 EAGER일 때 List<Member> members = em.createQuery("select m from Member m", Member.class) .getResultList(); 실제로 나가는 쿼리: select * from member -- 1번: JPQL 그대로 SQL로 번역 (회원 10명 조회) select * from team where team_id = 1 -- ↓ 그런데 Team이 EAGER네? 채워야 함 select * from team where team_id = 2 select * from team where team_id = 3 ... -- N번: 회원 수만큼 추가 쿼리! 왜 이런 일이 생길까? em.find() 는 JPA가 내부에서 최적화(조인)를 할 수 있습니다. 하지만 JPQL은 작성한 그대로 SQL로 번역 됩니다. select m from Member m 이면 select * from member 만 나갑니다. 결과를 받아 보니 Team이 EAGER 로 설정되어 있습니다. EAGER는 "반환 시점에 반드시 채워져 있어야 한다"는 뜻입니다. 그래서 JPA가 회원마다 Team을 조회하는 쿼리를 추가로 날립니다. 최초 쿼리 1번 + 추가 쿼리 N번 = N+1 문제 💡 회원 1,000명이면 쿼리가 1,001번 나갑니다. 서비스 장애로 이어질 수 있는 대표적인 성능 문제입니다. LAZY로 바꾸면 해결될까? List<Member> members = em.createQuery("select m from Member m", Member.class) .getResultList(); // 쿼리 1번 (Team은 프록시) for (Member m : members) { m.getTeam().getName(); // 루프 돌면서 Team 조회... 결국 N번 } LAZY로 바꾸면 당장은 쿼리 1번 만 나가지만, 나중에 Team을 꺼내 쓰면 결국 N번이 나갑니다. ✅ 진짜 해결책: JPQL 페치 조인 em.createQuery("select m from Member m join fetch m.team", Member.class) 필요한 시점에 딱 필요한 연관관계만 조인해서 한 번에 가져옵니다. 그 밖에 엔티티 그래프 , 배치 사이즈 같은 방법도 있습니다. (객체지향 쿼리 장에서 자세히 다룸) 기본값 정리 → ~ToOne 은 직접 LAZY로 바꿔야 한다 어노테이션 기본값 조치 @ManyToOne EAGER 👉 fetch = FetchType.LAZY 로 변경 @OneToOne EAGER 👉 fetch = FetchType.LAZY 로 변경 @OneToMany LAZY 그대로 @ManyToMany LAZY 그대로 (애초에 안 씀) 💡 외우는 법: ~ToOne 은 하나라서 같이 가져와도 부담이 없을 것 같아 EAGER , ~ToMany 는 많아서 부담되니 LAZY 가 기본값입니다. 하지만 실무에서는 전부 LAZY 로 통일합니다. 6. 지연 로딩 활용 이론적인 활용법 관계 사용 빈도 전략 Member - Team 자주 함께 사용 즉시 로딩 Member - Order 가끔 사용 지연 로딩 Order - Product 자주 함께 사용 즉시 로딩 member1 ──(EAGER) team──→ teamA (실제 엔티티) │ └─(LAZY) orders──→ 주문내역 List (프록시 컬렉션) │ └─(EAGER) product──→ 상품A Member 조회 시 → Team은 같이, 주문 목록은 프록시로 주문 목록을 실제로 사용하는 시점에 → 주문 조회 + 주문의 Product는 즉시 로딩으로 같이 💡 컬렉션도 지연 로딩이 됩니다. Hibernate는 컬렉션을 PersistentBag 같은 래퍼 컬렉션 으로 감싸서 관리합니다. 🚨 실무에서는 이렇게! 모든 연관관계에 지연 로딩을 사용해라! 실무에서 즉시 로딩을 사용하지 마라! 함께 조회가 필요하면 JPQL 페치 조인 이나 엔티티 그래프 기능을 사용해라! 즉시 로딩은 상상하지 못한 쿼리 가 나간다. "Member와 Team을 자주 같이 쓴다"는 판단은 지금 기준 일 뿐입니다. 화면이 늘어나면 Team이 필요 없는 조회도 생깁니다. 기본은 LAZY 로 깔아 두고, 같이 필요한 곳에서만 페치 조인으로 골라서 가져오는 것이 안전합니다. 7. 영속성 전이: CASCADE 언제 쓰나? 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때 예: 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장 Parent 1 ──── * Child CASCADE 없이 저장하면 @Entity public class Parent { @Id @GeneratedValue private Long id; private String name; @OneToMany(mappedBy = "parent") private List<Child> childList = new ArrayList<>(); public void addChild(Child child) { // 연관관계 편의 메서드 childList.add(child); child.setParent(this); } } @Entity public class Child { @Id @GeneratedValue private Long id; private String name; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "parent_id") private Parent parent; } Child child1 = new Child(); Child child2 = new Child(); Parent parent = new Parent(); parent.addChild(child1); parent.addChild(child2); em.persist(parent); em.persist(child1); // 😩 자식도 하나하나 persist 해야 함 em.persist(child2); CASCADE를 적용하면 @OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST) private List<Child> childList = new ArrayList<>(); Parent parent = new Parent(); parent.addChild(child1); parent.addChild(child2); em.persist(parent); // ✅ 부모만 persist하면 자식도 함께 저장 parent ── 영속화 ──→ (Cascade.PERSIST, 영속성 전이) ──→ children [child1, child2] ⚠️ CASCADE 주의 영속성 전이는 연관관계 매핑과 아무 관련이 없습니다. 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐 입니다. cascade 옵션은 "부모에게 한 persist() / remove() 를 자식에게도 똑같이 해 줘"라는 뜻입니다. FK나 연관관계의 주인과는 별개입니다. CASCADE 종류 옵션 설명 ALL 모두 적용 ← 자주 씀 PERSIST 영속 (저장할 때만) ← 자주 씀 REMOVE 삭제 MERGE 병합 REFRESH REFRESH DETACH DETACH 언제 써야 할까? ✅ 자식의 소유자가 하나일 때만 쓰세요. (단일 소유자) 상황 CASCADE 게시글 ↔ 첨부파일 (첨부파일은 그 게시글에서만 관리) ✅ OK 주문 ↔ 주문상품 (주문상품은 주문에서만 관리) ✅ OK 상품 ↔ 주문상품 (주문상품을 주문도, 상품도 참조) ❌ 쓰면 안 됨 회원 ↔ 팀 (팀은 독립적으로 관리) ❌ 쓰면 안 됨 조건 2가지: 부모와 자식의 라이프사이클이 거의 같을 때 (같이 생성, 같이 삭제) 자식을 다른 엔티티가 참조하지 않을 때 ⚠️ 왜 위험할까? Child를 다른 엔티티(예: Other )도 참조하고 있는데 Parent에 CascadeType.REMOVE 를 걸었다면, Parent를 삭제할 때 Child도 같이 삭제되어 Other가 참조하던 데이터가 사라집니다. (FK 에러 또는 데이터
AI 에이전트가 테스트를 통과했다고 보고하면 무엇을 더 확인해야 할까요? 로컬 컨테이너와 원격 호스트의 성공은 서로 다른 실행 조건을 증명합니다. 사람이 결과를 검토하려면 성공 여부와 함께 어떤 코드가 어떤 환경에서 실행됐는지 확인해야 합니다. 자동화 결과는 사람이 다음 작업을 결정할 때 근거가 됩니다. 로컬 컨테이너에서 얻은 성공을 읽는 경우와 원격 호스트에서 얻은 성공을 읽는 경우에는 판단의 출발점이 달라집니다. 각각의 결과가 보여 주는 실행 조건이 다르기 때문입니다. 결과를 다음 판단에 쓰려면 이 차이가 보고에서도 드러나야 합니다. 코드의 출처가 실행 장소를 가릅니다 테스트 명령이 읽는 저장소의 래퍼와 설정 파일도 신뢰 판단에 포함됩니다. SkillsMP의 crabbox 설명은 자격 증명이 들어 있는 Testbox에서 이 파일들을 예외로 두지 않습니다. 신뢰하는 소스를 로컬에서 검사하는 기준에는 테스트가 작고 집중되어 있다는 조건이 붙습니다. 필요한 의존성도 로컬에 준비되어 있어야 합니다. 검증이 무거울 때 선택하는 Blacksmith Testbox와 구분하려면, 소스의 신뢰도에 더해 작업 규모와 준비 상태를 함께 봅니다. 외부 기여자나 포크의 코드를 다룰 때는 환경에 비밀정보가 있는지가 중요합니다. 포크 CI에는 비밀정보가 없어야 하고, 직접 AWS를 사용하는 환경에는 별도 정제가 요구됩니다. 관련 설명은 https://skillsmp.com/skill/openclaw-openclaw-agents-skills-crabbox-skill-md 에서 확인합니다. 실행 경로마다 검증하는 환경이 다릅니다 실행 경로를 보고할 때는 이름도 정확하게 구분합니다. crabbox 문서는 Testbox를 AWS Crabbox라고 부르지 않도록 명시합니다. 서로 다른 경로를 비슷한 이름으로 묶으면, 선택한 환경의 차이를 전달하기 어려워집니다. blacksmith-testbox에는 신뢰하는 유지보수자의 소스와 준비된 CI라는 조건이 붙습니다. aws는 신규 PR을 다루거나, 소스를 별도로 동기화하고 환경을 설정한 뒤 결과를 수집하는 직접 실행 경로입니다. local-container를 선택하면 Docker를 사용하는 로컬 대체 경로로 처리합니다. 이 경로의 결과는 원격 검증으로 세지 않습니다. ssh는 운영자가 이미 사용하는 호스트로 연결되는 경로입니다. 여기에 언급된 환경에는 macOS, Windows, WSL2가 포함됩니다. 성공 보고에 실행 조건을 함께 남깁니다 성공한 결과에도 적용 범위가 있습니다. 결과를 읽는 사람이 이 범위를 구별할 수 있도록, 보고에는 성공이 나온 환경이 드러나야 합니다. 제공자와 실행 식별자, 실행 URL로 어느 실행을 가리키는지 남깁니다. 여기에 명령과 결과를 붙여 무엇을 수행했고 어떻게 끝났는지 전달합니다. 환경을 점검하고 필요한 때 확보해 정리합니다 원격 자원은 어떤 순서로 점검하고 사용해야 할까요? crabbox 안내는 바이너리·버전·PATH와 기본 제공자를 확인하고, 무거운 첫 명령이 준비된 뒤 자원을 확보해 실행 식별자를 재사용하고 중지하도록 합니다. 다만 검토한 공개 구간은 빌드 절차 도중 끝나므로, 그 범위만으로 설치 명령이나 운영 절차를 확정할 수는 없습니다. SkillsMP에 표시된 마지막 소스 활동 시각은 2026년 7월 15일 07:52입니다. 검토한 공개 구간은 실행 파일이 없을 때 진행하는 빌드 절차의 중간까지 이어집니다. 설치 명령과 운영 절차를 정할 때는 이 구간에 더해 OpenClaw 저장소의 crabbox 소스와 함께 제공되는 파일들을 살핍니다. 확인할 위치는 https://github.com/openclaw/openclaw/tree/main/.agents/skills/crabbox 입니다. 현재 실행 환경을 점검합니다 — 실행 파일·버전과 PATH의 오래된 복사본 가능성을 살피고, 설정의 기본 제공자를 확인합니다. 첫 명령을 준비한 뒤 확보합니다 — 무거운 첫 명령이 준비된 시점에 자원을 확보하며, 추측에 따른 사전 예열은 하지 않습니다. 확보한 실행을 재사용합니다 — 이후 작업에는 이미 확보한 실행 식별자를 재사용합니다. 사용을 마치면 중지합니다 — 검증이 끝나면 사용한 원격 실행 환경을 중지합니다. 대량으로 만드는 자원에도 종료 조건이 필요합니다 자원 관리에서는 새로 만드는 양과 계속 가동하는 양을 구분해서 읽어야 합니다. GeekNews의 Supabase, Turso 인수 추진 보도에 나온 매주 100만 개 이상은 Supabase의 데이터베이스 생성량입니다. 실제 가동 수를 뜻하는 수치는 아닙니다. 해당 보도는 https://news.hada.io/topic?id=34676 에서 확인합니다. 같은 보도가 소개하는 Turso의 운영 방식에서는 사용 여부에 따라 데이터베이스 상태가 달라집니다. 필요해지면 로드하고, 사용하지 않으면 중단합니다. 생성량을 읽는 일과 별개로, 만들어진 자원을 언제 사용하고 언제 멈추는지 살피게 하는 사례입니다. 이 운영 방식에서 얻는 설계 관점은 원격 테스트 환경에도 연결됩니다. 에이전트가 자원을 쉽게 만들수록 어떤 범위에서 사용할지, 무엇을 기준으로 종료할지 분명하게 정합니다. 검토 기준은 성공 여부와 실행 경계입니다 검토할 때는 실행 장소를 선택한 이유가 코드의 출처와 연결되는지 살핍니다. 이어서 다음 작업에 필요한 실행 조건을 이번 성공이 뒷받침하는지 판단합니다. 원문: webi 기술 블로그 참고한 자료: SkillsMP Agent Skill: crabbox (openclaw) 법원, EFF에 동의: 유타주 VPN 법은 기술적으로 불가능한 일을 요구함 Supabase, Turso 인수 추진
정보처리기사 테스트 기법 블랙박스 화이트박스 커버리지 암기법 정보처리기사에서 테스트 문제는 같은 단원 안에서도 무엇을 기준으로 묻는지 가 다릅니다. 예를 들어 입력 구간의 대표값 → 동치 분할 경계 주변 값 → 경곗값 분석 프로그램 내부에서 어떤 명령을 실행하고 어느 갈림길을 지났는지 확인 → 화이트박스 테스트 처럼 문제에 나온 단서에 따라 답이 달라집니다. 2026년 2회 실기 1번의 답은 동치 분할 테스트 였지만, 이것 하나만 외우면 비슷한 문제를 구분하기 어렵습니다. 따라서 테스트 설계 기법을 블랙박스 테스트 화이트박스 테스트 경험 기반 테스트 로 나누고, 문제에 나온 단서만 보고 답을 찾을 수 있도록 정리해보겠습니다. 💡 안내 이 글은 정보처리기사 실기에서 반복해서 등장하는 테스트 기법 개념을 정리한 학습 자료입니다. 1. 테스트 기법은 기준을 어디에 두는지부터 나눈다 테스트 기법은 테스트 케이스를 어떤 근거로 만드는지 에 따라 크게 세 부류로 나눌 수 있습니다. 먼저 테스트 케이스(Test Case) 는 입력값 실행 조건 예상 결과 를 하나의 묶음으로 정리한 시험 사례입니다. 예를 들어 등급 계산 화면에서 75점을 입력하면 C가 나와야 한다. 라는 내용이 하나의 테스트 케이스가 될 수 있습니다. 문제에서는 내부 코드를 보는가? 요구사항과 결과를 보는가? 테스터의 경험을 사용하는가? 를 먼저 구분해야 합니다. 2. 테스트 기법의 세 가지 분류 분류 무엇을 기준으로 보는가 블랙박스 테스트 명세를 기준으로 입력과 출력 결과를 확인한다. 내부 코드는 보지 않는다. 화이트박스 테스트 프로그램 내부 구조와 코드의 실행 순서, 갈림길 등을 확인한다. 경험 기반 테스트 테스터의 경험, 결함에 대한 지식, 점검 목록 등을 활용한다. 💡 먼저 세 기준으로 구분하세요 명세의 입력과 출력 → 블랙박스 코드 안의 실행 순서와 갈림길 → 화이트박스 과거에 자주 발생한 오류와 테스터의 판단 → 경험 기반 3. 블랙박스 테스트는 입력과 출력의 규칙을 본다 블랙박스 테스트(Black Box Test) 는 프로그램 내부가 어떻게 구현되어 있는지 몰라도 수행할 수 있습니다. 요구사항에 적힌 입력 조건 예상 결과 를 보고 테스트 케이스를 설계하기 때문에 명세 기반 테스트 라고도 합니다. 즉, 프로그램 내부 코드 → 보지 않음 입력과 출력 → 확인 입니다. 4. 블랙박스 테스트 주요 기법 대표적인 블랙박스 테스트 기법은 다음과 같습니다. 동치 분할 테스트 경곗값 분석 결정 테이블 테스트 상태 전이 테스트 원인 결과 그래프 테스트 페어와이즈 테스트 유스케이스 테스트 분류 트리 테스트 비교 테스트 오류 추정 각각 어떤 단서를 보고 구분하는지가 중요합니다. 5. 동치 분할 테스트 동치 분할 테스트(Equivalence Partitioning) 는 같은 방식으로 처리될 것으로 예상되는 입력값들을 하나의 묶음으로 나누는 기법입니다. 이러한 입력값의 묶음을 동치 클래스 라고 합니다. 그리고 각 동치 클래스에서 대표값 하나 를 골라 테스트합니다. 예를 들어 점수 구간이 0점 미만 0 ~ 69점 70 ~ 79점 80 ~ 89점 90 ~ 100점 100점 초과 로 나누어진다면 각각 하나의 동치 클래스로 볼 수 있습니다. 각 구간에서 -10 30 75 85 95 110 처럼 대표값 하나씩을 선택합니다. 핵심 같은 방식으로 처리될 입력을 묶음으로 나눈다. ↓ 각 묶음에서 대표값 하나를 선택한다. ↓ 동치 분할 테스트 6. 경곗값 분석 경곗값 분석(Boundary Value Analysis) 은 입력 범위의 경계 경계 바로 안쪽 경계 바로 바깥쪽 을 집중적으로 테스트하는 기법입니다. 예를 들어 입력 가능 범위가 1 ~ 10 이라면 0 1 2 9 10 11 처럼 경계 주변 값을 테스트합니다. 핵심 최솟값 최댓값 경계 바로 앞 경계 바로 뒤 → 경곗값 분석 7. 결정 테이블 테스트 결정 테이블 테스트(Decision Table Test) 는 여러 조건의 조합과 그에 따른 동작을 표 형태 로 정리하여 테스트합니다. 여기서 조건은 회원인가? 구매 금액이 일정 금액 이상인가? 처럼 참/거짓 또는 여러 값으로 판단할 수 있는 항목입니다. 예를 들어 회원 여부 구매 금액 할인 O 기준 이상 할인 적용 O 기준 미만 일부 할인 X 기준 이상 다른 할인 X 기준 미만 할인 없음 처럼 조건 조합마다 수행할 동작을 표로 표현합니다. 핵심 조건 조합 + 수행 동작 + 표 → 결정 테이블 테스트 8. 상태 전이 테스트 상태 전이 테스트(State Transition Test) 는 시스템이 현재 어떤 상태에 있는지와 특정 사건이 발생했을 때 상태가 어떻게 변하는지를 확인하는 기법입니다. 여기서 상태는 로그인 전 로그인 후 계정 잠금 처럼 현재 처한 상황입니다. 전이는 로그인 전 ↓ 로그인 성공 로그인 후 처럼 상태가 다른 상태로 바뀌는 것을 의미합니다. 핵심 현재 상태 + 사건 + 다음 상태 → 상태 전이 테스트 9. 원인 결과 그래프 테스트 원인 결과 그래프 테스트(Cause-Effect Graphing) 는 입력 조건과 출력 결과의 관계를 그래프 로 표현하는 기법입니다. 입력 조건 = 원인 출력 결과 = 결과 원인과 결과를 선으로 연결하여 AND OR NOT 등의 논리 관계를 표현합니다. 핵심 입력 조건 + 출력 결과 + 논리 관계 + 그래프 → 원인 결과 그래프 테스트 10. 페어와이즈 테스트 페어와이즈 테스트(Pairwise Testing) 는 여러 입력 요인이 있을 때 모든 전체 조합을 시험하는 대신 서로 다른 두 요인의 가능한 값 조합이 최소 한 번씩 나타나도록 테스트 케이스를 구성 하는 방법입니다. 입력 요인은 예를 들어 운영체제 브라우저 언어 네트워크 처럼 조합할 수 있는 항목입니다. 핵심 모든 조합 X 두 요인의 값 쌍을 빠짐없이 → 페어와이즈 테스트 11. 유스케이스 테스트 유스케이스 테스트(Use Case Test) 는 사용자가 시스템을 사용하여 목표를 이루는 전체 흐름을 기준으로 테스트합니다. 여기서 액터(Actor) 는 시스템을 사용하는 사람 또는 외부 시스템 을 의미합니다. 예를 들어 상품 주문 기능이라면 상품 선택 ↓ 장바구니 ↓ 주문 ↓ 결제 같은 기본 흐름을 확인합니다. 또한 결제 실패처럼 대안 흐름 예외 흐름 도 함께 테스트할 수 있습니다. 핵심 사용자 목표 + 기본 흐름 + 대안 흐름 → 유스케이스 테스트 12. 분류 트리 테스트 분류 트리 테스트(Classification Tree Test) 는 입력 조건을 나무의 가지처럼 나누어 분류하는 방법입니다. 예를 들어 운영체제 ├─ Windows ├─ Linux └─ macOS 브라우저 ├─ Chrome ├─ Edge └─ Firefox 처럼 입력 조건을 분류합니다. 그리고 마지막 잎에 위치한 선택값들을 조합하여 테스트 케이스를 만듭니다. 핵심 입력 조건을 트리 형태로 분류 → 분류 트리 테스트 13. 비교 테스트 비교 테스트(Comparison Test) 는 같은 기능을 수행하는 여러 버전 여러 시스템 여러 구현 에 같은 입력값을 넣고 결과를 서로 비교하는 방법입니다. 핵심 같은 입력 ↓ 여러 시스템에 입력 ↓ 결과 비교 → 비교 테스트 14. 오류 추정 오류 추정(Error Guessing) 은 경험상 자주 발생하는 오류를 예상하여 해당 상황을 집중적으로 테스트하는 방법입니다. 예를 들어 이전에 빈 문자열 처리 NULL 처리 0으로 나누기 최댓값 입력 같은 부분에서 오류가 자주 발생했다면 비슷한 상황을 집중적으로 테스트합니다. 핵심 과거 경험 + 자주 발생하는 오류 예상 → 오류 추정 오류 추정의 분류 주의 오류 추정은 최근 테스트 분류에서는 경험 기반 테스트 기법 으로 다루지만, 정보처리기사 기존 교재나 문제 풀이에서는 블랙박스 테스트 기법 목록에 함께 포함하는 경우도 있습니다. 따라서 시험에서는 문제의 보기 지문의 분류 기준 을 따르는 것이 중요합니다. 다만 핵심 의미는 경험을 바탕으로 오류가 날 만한 곳을 예상한다. 라고 기억하면 됩니다. 15. 2026년 2회 실기 1번은 동치 분할 테스트다 2026년 2회 실기 1번에서는 점수에 따라 등급을 출력하는 프로그램과 여러 시험값이 제시되었습니다. 입력 영역을 나누면 다음과 같습니다. 입력 점수 예상 결과 대표값 클래스 0점 미만 오류 -10 무효 0 ~ 69점 D 30 유효 70 ~ 79점 C 75 유효 80 ~ 89점 B 85 유효 90 ~ 100점 A 95 유효 100점 초과 오류 110 무효 대표값은 -10 30 75 85 95 110 입니다. 각 값은 오류 D C B A 오류 라는 서로 다른 결과가 나오는 입력 묶음에서 하나씩 선택한 대표값 입니다. 경계 주변에 값이 몰려 있는 것이 아니라 각 구간을 대표하는 값이 하나씩 있기 때문에 동치 분할 테스트 입니다. 교재에 따라 동등 분할 테스트 동등 클래스 분할 이라고 표현하기도 합니다. 16. 동치 분할과 경곗값 분석은 값의 위치로 구분한다 두 기법은 시험에서 매우 자주 헷갈립니다. 구분 동치 분할 경곗값 분석 관심 대상 같은 결과가 나오는 입력 묶음 결과가 바뀌는 경계 값을 고르는 위치 각 묶음을 대표하는 값 경계와 바로 안쪽·바깥쪽 값 예시 -10, 30, 75, 85, 95, 110 -1, 0, 1, 69, 70, 71 등 기억할 문장 묶음마다 대표 하나 문턱의 앞과 뒤 동치 분할 구간마다 대표값 하나 를 선택합니다. 또한 정상 범위뿐만 아니라 정상 범위 아래 정상 범위 위 에 해당하는 무효 동치 클래스 도 나눕니다. 경곗값 분석 점수 등급을 예로 들면 0 69 70 79 80 89 90 100 과 그 바로 주변 값을 집중적으로 확인합니다. 💡 이렇게 외워보세요 같은 결과끼리 묶고, 묶음마다 대표 하나. 묶음 안에서 대표를 고르면 동치 분할 묶음이 갈리는 문턱을 고르면 경곗값 분석 입니다. 17. 시험에서 자주 틀리는 테스트 기법 조합 용어가 비슷해도 답을 구분하는 결정적인 단서가 있습니다. 문제에서 값의 위치 표현 형식 시험 대상 을 먼저 확인합니다. 동치 분할 vs 경곗값 분석 동치 분할 → 구간마다 대표값 경곗값 분석 → 구간이 바뀌는 경계 주변 원인 결과 그래프 vs 결정 테이블 원인 결과 그래프 → 조건과 결과의 논리 관계를 그래프로 표현 결정 테이블 → 조건 조합과 동작을 표로 표현 즉, 그래프 → 원인 결과 그래프 표 → 결정 테이블 로 구분할 수 있습니다. 상태 전이 vs 유스케이스 상태 전이 → 사건에 따른 상태 변화를 확인 유스케이스 → 사용자가 목표를 이루는 전체 흐름 확인 비교 테스트 vs 오류 추정 비교 테스트 → 여러 시스템의 결과를 비교 오류 추정 → 경험을 바탕으로 오류가 날 상황 예상 18. 화이트박스 테스트는 코드 내부의 흐름을 본다 화이트박스 테스트(White Box Test) 는 프로그램의 소스 코드나 설계의 내부 구조를 기준으로 테스트 케이스를 만듭니다. 블랙박스가 입력과 출력의 명세를 본다면, 화이트박스는 명령이 어떤 순서로 실행되는가? 어떤 갈림길을 지나는가? 어떤 반복문을 실행하는가? 를 확인합니다. 19. 제어 흐름과 실행 경로 화이트박스 테스트를 이해하려면 몇 가지 용어를 알아두면 좋습니다. 제어 흐름 프로그램의 명령이 실행되는 순서를 의미합니다. 시작 ↓ 조건문 ↓ 실행 ↓ 종료 같은 흐름입니다. 제어 흐름도 제어 흐름을 점 화살표 등으로 표현한 그래프입니다. 실행 경로 프로그램의 시작부터 끝까지 실제로 지나가는 하나의 길입니다. 제어 구조 실행 순서를 결정하는 조건문 분기 반복문 같은 프로그램 구조입니다. 20. 화이트박스 테스트 대표 기법 대표적인 화이트박스 테스트 기법은 기초 경로 테스트 조건 테스트 루프 테스트 데이터 흐름 테스트 입니다. 21. 기초 경로 테스트 기초 경로 테스트(Basis Path Test) 는 프로그램의 제어 흐름을 분석하고 독립 경로 를 찾아 테스트하는 방법입니다. 독립 경로는 이미 선택한 다른 경로들의 조합만으로 표현할 수 없는 새로운 실행 경로입니다. 기초 경로 테스트에서는 제어 흐름 그래프와 순환 복잡도 를 이용하여 필요한 독립 경로의 수를 구할 수 있습니다. 핵심 제어 흐름 그래프 독립 경로 순환 복잡도 → 기초 경로 테스트 22. 조건 테스트 조건 테스트(Condition Test) 는 프로그램의 조건문에 들어 있는 개별 조건들을 확인하는 방법입니다. 예를 들어 회원이고 성인이면 허용 이라는 조건이 있다면 회원 여부 성인 여부 등 각각의 조건이 참과 거짓이 되는 경우를 확인합니다. 핵심 조건식 참 / 거짓 → 조건 테스트 23. 루프 테스트 루프 테스트(Loop Test) 는 반복문을 대상으로 테스트하는 기법입니다. 반복문이 0회 1회 여러 번 최대 횟수 등으로 실행될 때 정상적으로 동작하는지 확인합니다. 또한 중첩 반복문 등도 확인합니다. 핵심 반복문 반복 횟수 중첩 → 루프 테스트 24. 데이터 흐름 테스트 데이터 흐름 테스트(Data Flow Test) 는 프로그램의 변수에 값이 어떻게 저장되고 사용되는지를 확인하는 기법입니다. 변수에는 크게 정의 사용 소멸 등의 흐름이 존재합니다. 예를 들어 값을 저장했지만 사용하지 않고 다시 덮어씀 값을 넣기 전에 먼저 읽음 같은 비정상적인 흐름을 확인할 수 있습니다. 핵심 변수 정의 변수 사용 데이터 흐름 → 데이터 흐름 테스트 25. 화이트박스 테스트 핵심 정리 기초 경로 테스트 → 독립 경로 / 순환 복잡도 조건 테스트 → 조건의 참과 거짓 루프 테스트 → 반복문 데이터 흐름 테스트 → 변수의 정의와 사용 이러한 기법은 화이트박스 테스트의 대표적인 설계 방법입니다. 실제 정보처리기사에서는 기법 이름 자체뿐만 아니라 코드의 어느 부분을 얼마나 실행했는가? 를 묻는 커버리지 문제도 자주 등장합니다. 26. 화이트박스 커버리지는 확인한 범위를 나타낸다 커버리지(Coverage) 는 테스트 대상으로 정한 항목 가운데 실제로 테스트한 항목의 비율입니다. 무엇을 기준으로 하느냐에 따라 문장 분기 개별 조건 실행 경로 등 확인해야 하는 범위가 달라집니다. 대표적인 커버리지에는 문장 커버리지 결정 커버리지 조건 커버리지 조건/결정 커버리지 다중 조건 커버리지 MC/DC 등이 있습니다. 이들 커버리지의 차이는 별도로 자세히 비교해서 익히는 것이 좋습니다. 27. 경험 기반 테스트는 이미 아는 위험을 활용한다 경험 기반 테스트는 테스터의 경험 과거 결함 개발자의 흔한 실수 점검 목록 등을 활용해서 테스트합니다. 대표적인 기법은 오류 추정 탐색적 테스트 체크리스트 기반 테스트 입니다. 28. 오류 추정 오류 추정은 과거 결함 개발자의 실수 를 바탕으로 오류가 발생할 만한 곳을 예상하는 방법입니다. 결정 단서 경험 직관 자주 발생한 오류 29. 탐색적 테스트 탐색적 테스트(Exploratory Testing) 는 학습 테스트 설계 테스트 실행 을 동시에 진행하면서 다음 테스트를 조정하는 방법입니다. 즉, 테스트 실행 ↓ 결과 관찰 ↓ 다음 테스트 설계 ↓ 즉시 실행 하는 방식입니다. 또한 테스트 차터(Test Charter) 라는 짧은 안내를 사용할 수 있습니다. 테스트 차터는 이번 탐색에서 무엇을 어떤 목표로 테스트할 것인가 를 정리한 간단한 문서입니다. 핵심 단서 동시에 학습 설계 실행 테스트 차터 → 탐색적 테스트 30. 체크리스트 기반 테스트 체크리스트 기반 테스트(Checklist-based Testing) 는 경험을 통해 만든 점검 목록을 이용하여 하나씩 확인하는 방법입니다. 결정 단서 점검표 확인 목록 경험으로 만든 항목 핵심 점검 목록을 따라 확인 → 체크리스트 기반 테스트 31. 같은 테스트 단원이라도 설계 기법과 범위가 다른 용어 정보처리기사에서는 단위 테스트 통합 테스트 시스템 테스트 인수 테스트 도 자주 등장합니다. 하지만 이것들은 테스트 케이스를 어떻게 설계할 것인가? 보다는 언제, 어떤 범위를 테스트할 것인가? 에 가까운 개념입니다. 따라서 블랙박스·화이트박스 같은 테스트 설계 기법 과 구분해야 합니다. 32. 테스트 수준 테스트 수준 의미 단위 테스트 개별 모듈이나 기능을 따로 시험 통합 테스트 여러 모듈을 연결하여 상호 작용을 시험 시스템 테스트 제품 전체를 시험 인수 테스트 사용자 요구와 사용 목적을 충족하는지 확인 33. 통합 테스트 방식과 도구 통합 테스트에서는 하향식 상향식 스텁 드라이버 등이 자주 등장합니다. 하향식 통합 테스트 상위 기능 ↓ 하위 기능 순서로 모듈을 연결합니다. 즉, 위에서 아래 → 하향식 입니다. 상향식 통합 테스트 하위 기능 ↓ 상위 기능 순서로 모듈을 연결합니다. 즉, 아래에서 위 → 상향식 입니다. 스텁(Stub) 상위 모듈이 호출하는 하위 모듈이 아직 준비되지 않았을 때 그 역할을 대신하는 가짜 모듈입니다. 하향식 → Stub 으로 연결해서 기억하면 쉽습니다. 드라이버(Driver) 하위 모듈을 호출해줄 상위 모듈이 아직 준비되지 않았을 때 대신 호출해주는 가짜 모듈입니다. 상향식 → Driver 로 기억하면 됩니다. 34. 목적과 시점에 따른 테스트 테스트는 수행 목적이나 시점에 따라 다른 이름으로 분류하기도 합니다. 대표적으로 회귀 테스트 알파 테스트 등이 있습니다. 회귀 테스트 회귀 테스트(Regression Test) 는 프로그램을 수정한 후 기존 기능에 새로운 문제가 발생하지 않았는지 다시 확인하는 테스트입니다. 수정 후 기존 기능 재확인 → 회귀 테스트 알파 테스트 알파 테스트(Alpha Test) 는 개발 환경에서 대표 사용자가 시스템을 시험하는 방식입니다. 개발 환경 대표 사용자 → 알파 테스트 35. 결과 판정 - 테스트 오라클 테스트 결과가 맞는지 틀리는지를 판단하기 위해 사용하는 기준을 테스트 오라클(Test Oracle) 이라고 합니다. 즉, 실행 결과가 정답인지 판단하는 기준 입니다. 대표적으로 참 오라클 샘플링 오라클 등이 있습니다. 참 오라클 모든 테스트 대상 입력에 대해 정확한 결과를 알고 있는 방식입니다. 모든 입력의 정답을 알고 있음 → 참 오라클 샘플링 오라클 전체 입력 중 일부 선택된 입력에 대해서만 정확한 결과를 알고 판정합니다. 선택된 입력만 정답을 알고 있음 → 샘플링 오라클 36. 테스트 원리 - 살충제 패러독스 정보처리기사에서 자주 등장하는 테스트 원리 중 하나가 살충제 패러독스(Pesticide Paradox) 입니다. 같은 테스트만 계속 반복하면 새로운 결함을 발견하는 효과가 점점 떨어진다. 는 원리입니다. 즉, 같은 테스트 반복 ↓ 새로운 결함 발견 능력 감소 입니다. 따라서 테스트 케이스도 계속 수정하거나 추가해야 합니다. 37. 문제 단서에서 정확한 답안으로 옮기는 순서 테스트 문제를 풀 때 문제의 모든 문장을 외우려고 하기보다는 무엇을 했는가? 를 먼저 찾는 것이 중요합니다. 예를 들어 대표값을 골랐는가? 경계를 시험했는가? 표로 나타냈는가? 그래프로 나타냈는가? 코드의 참과 거짓을 실행했는가? 를 확인하면 됩니다. 대표 문제 단서 문제의 결정 단서 먼저 판단할 기준 정확한 답안 같은 결과가 나오는 입력 묶음에서 대표값을 고른다 입력 묶음 동치 분할 테스트 최솟값과 최댓값의 바로 안쪽과 바깥쪽을 시험한다 경계 주변 경곗값 분석 조건 조합과 수행 동작을 표로 나타낸다 명세의 조건 조합 결정 테이블 테스트 📌 블랙박스 테스트 최종 정리 동치 분할 → 입력을 같은 처리 결과의 묶음으로 나누고 대표값 선택 경곗값 분석 → 경계와 바로 안쪽/바깥쪽 결정 테이블 → 조건 조합 + 동작 + 표 상태 전이 → 현재 상태 + 사건 + 다음 상태 원인 결과 그래프 → 원인과 결과의 논리 관계를 그래프로 표현 페어와이즈 → 두 요인의 가능한 값 쌍을 최소 한 번씩 유스케이스 → 사용자 목표와 전체 흐름 분류 트리 → 입력 조건을 나무 형태로 분류 비교 테스트 → 같은 입력을 여러 시스템에 넣어 결과 비교 오류 추정 → 경험으로 오류가 날 상황 예상 📌 화이트박스 테스트 최종 정리 기초 경로 테스트 → 독립 경로 / 순환 복잡도 조건 테스트 → 조건의 참/거짓 루프 테스트 → 반복문과 반복 횟수 데이터 흐름 테스트 → 변수 정의와 사용 화이트박스 테스트의 핵심은 코드 내부 구조 실행 순서 갈림길 반복 변수 흐름 입니다. 📌 경험 기반 테스트 최종 정리 오류 추정 → 과거 결함과 경험 탐색적 테스트 → 학습 + 설계 + 실행을 동시에 체크리스트 기반 테스트 → 점검 목록 🧠 가장 먼저 구분할 것 입력 / 출력 / 요구사항 → 블랙박스 코드 내부 / 실행 경로 / 조건 / 반복 → 화이트박스 경험 / 직관 /
Log Friends의 SDK를 고민하면서 지원할 수 있는 환경은 더 많이 보였다. 하지만 고려한 환경을 지금 모두 지원해야 하는 것은 아니었다. 백엔드, 웹 프론트엔드, 모바일까지 구분해 생각하되, 실제 지원 범위는 웹과 백엔드로 정했다. 모바일이 중요하지 않아서가 아니라, 사용할 수 있는 시간과 인력으로 어디까지 구현하고 검증하며 유지할 수 있는지를 먼저 생각했기 때문이다. 이번 글에서 남기고 싶은 것은 테이블을 몇 개 나눴는지가 아니다. 설계에서 고려할 범위와 현재 개발에서 책임질 범위를 구분한 과정 이다. 처음에는 어떤 언어의 SDK를 만들지 생각했다 처음에는 Kotlin SDK, TypeScript SDK처럼 언어를 중심으로 바라봤다. 어떤 언어로 만든 서비스에서 Log Friends를 사용할 수 있게 할지가 먼저였다. 그런데 실행 환경까지 생각하니 언어만으로는 구분이 충분하지 않았다. Kotlin은 Spring Boot 서버와 Android에서 모두 쓰이고, TypeScript는 Node.js 서버와 브라우저에서 모두 쓰인다. 같은 언어라도 수집할 정보와 실행 중 신경 써야 할 일이 다르다. 환경 구분해서 생각한 내용 백엔드 요청·함수 실행·업무 이벤트와 서버 종료 시 전송 웹 프론트엔드 페이지·컴포넌트·사용자 행동과 페이지 종료 시 전송 모바일 앱 화면·앱 생명주기·네트워크 상태에 따른 전송 서버 이벤트에 웹의 page_path 나 component_name 이 필요한지 살펴본 것도 이 구분의 한 계기였다. 공통 이벤트 계약이 있다고 해서 환경별 정보까지 모두 공통으로 취급할 필요는 없었다. 지원 환경이 늘면 유지해야 할 일도 늘어난다 백엔드·웹·모바일을 모두 고려하는 것과 실제로 지원하는 것은 다른 일이었다. SDK 코드를 추가하는 것만으로 지원이 끝나지는 않는다. 각 환경에서 이벤트가 제대로 수집되는지, 전송이 실패했을 때 어떻게 동작하는지 확인해야 한다. 사용법을 문서화하고, 공통 계약을 바꿀 때 기존 동작이 깨지지 않는지도 계속 확인해야 한다. 모바일까지 포함하면 앱 생명주기와 네트워크 변화 같은 조건도 별도로 다뤄야 한다. 공통 전송 코드를 재사용할 수 있어도 환경별 검증과 유지보수까지 없어지는 것은 아니다. 제한된 시간과 인력으로 이 범위를 동시에 가져가면, 지원 목록은 늘어나지만 각 환경을 충분히 확인하는 데 쓸 시간은 줄어들 수 있다고 판단했다. 유지보수 비용을 수치로 측정한 것은 아니지만, 범위를 정할 때 빼놓을 수 없는 조건이었다. 그래서 모바일 지원까지 늘리기보다 웹과 백엔드에서 수집부터 저장·조회까지 이어지는 흐름을 먼저 검증하는 것 을 우선순위로 정했다. 지금 책임질 범위를 코드와 문서에 맞췄다 현재 지원 범위는 다음과 같다. 구분 SDK 업무 이벤트 저장 서버 백엔드 Kotlin/JVM, TypeScript Node backend_custom_events 웹 프론트엔드 TypeScript Browser frontend_custom_events 이벤트 형식과 배치 전송 계약은 공유하고, 웹 UI 전용 컬럼은 프론트엔드 테이블에만 둔다. 저장 위치는 SDK가 아니라 Console이 등록된 Agent의 출처를 보고 결정한다. 모바일도 문서에서만 제외하지 않았다. 현행 TypeScript 패키지의 모바일 공개 진입점을 제거하고 빌드·배포·지원 테스트 대상에서 제외했다. Console도 새 모바일 등록과 수집을 거절하도록 변경했다. 기존 모바일 코드와 과거 데이터는 삭제하지 않았다. 과거 레코드를 읽기 위한 MOBILE 값은 남겨뒀지만, 이를 현재 지원하는 기능으로 안내하지는 않는다. 모바일 앱을 제외하는 대신 당장 지원 환경을 늘릴 수 있는 선택지는 내려놓았다. 그 시간은 웹·백엔드의 동작 확인과 문서 정리에 쓰기로 했다. 모바일은 필요성과 유지 여력을 다시 확인할 때 검토할 수 있지만, 지금 지원을 약속한 상태는 아니다. 휴대전화의 웹 브라우저는 웹 지원 범위에 포함한다. 모바일 앱 SDK 제외와 반응형 웹 지원은 별개다. 어디까지 확인했는가 2026-10-08 로컬 변경 기준으로 다음을 확인했다. TypeScript SDK의 npm run prepublishOnly : lint, 지원 범위 테스트 67개, ESM·CommonJS 빌드, 패키지 검증 통과. npm pack --dry-run : 배포 대상 파일에 모바일 빌드 결과가 없는 것을 확인. Console: 별도 빌드 경로와 Kotlin incremental 비활성화 설정으로 전체 build 통과. 모바일 등록·수집 거절과 서버·브라우저 저장 분기 테스트 포함. 모바일 테스트는 지원 범위에서 제외했으므로 위 결과를 모바일 동작 검증으로 해석하면 안 된다. 저장 분기 테스트도 mock DB 기반이다. 실제 TimescaleDB에서 V15를 적용해 양쪽 SDK의 등록부터 저장까지 확인하는 작업은 남아 있다. npm 배포와 NAS 적용은 하지 않았다. 모바일 공개 API 제거는 하위 호환성이 깨지는 변경이므로, 실제 배포 전에 버전 정책과 제거 안내를 결정해야 한다. 배운 점 처음에는 SDK를 어떤 언어로 제공할지 생각했다. 그다음에는 백엔드·프론트엔드·모바일이라는 실행 환경의 차이가 보였고, 마지막에는 이를 유지할 시간과 인력까지 생각하게 됐다. 설계할 때 여러 환경을 고려하는 것은 필요했다. 하지만 현재 지원 범위까지 전부 넓혀야 하는 것은 아니었다. 고려는 넓게 하되, 지금 검증하고 유지할 수 있는 범위를 선택하는 것 도 개발의 일부였다. 다음은 웹과 서버 이벤트를 실제 테스트 DB에 보내 등록·수집·분리 저장까지 확인하는 것이다. 지금 선택한 두 환경부터 끝까지 확인하려고 한다.