2023년 기말고사
1. 다음 요구사항과 컨셉 모델을 보고 물음에 답하시오. (25점)
당신은 주문형 인쇄 서비스를 개발하고자 한다.
주문형 인쇄 서비스는 고객에게 포스터, 전단지 또는 책을 주문형으로 인쇄할 수 있는 기능을 제공한다. 고객은 제품 유형(포스터, 전단지, 책), 원하는 수량, 용지 종류를 선택할 수 있어야 한다. 책을 인쇄해야 하는 경우 고객은 하드커버와 소프트커버 중에서 선택할 수 있다. 고객은 원하는 콘텐츠가 포함된 PDF 파일을 제공해야 한다.
고객은 인쇄 주문을 하기 위한 계정이 있어야 한다. 고객은 사용자 이름/비밀번호 조합을 선택하여 계정을 만들 수 있다. 또한 주문 시 필수 정보인 주소와 신용 카드 번호를 계정에 연결할 수 있다.
고객이 주문에 필요한 정보를 제공하면 시스템은 주문서(제품 유형, 수량 등) 또는 계정(주소 및 결제 정보)에 모든 필수 정보가 입력되어 있는지 확인한다. 정보가 누락된 경우 주문이 완료되기 전에 추가해야 한다고 고객에게 알려준다. 모든 정보가 준비되면 주문이 접수되고 신용 카드 정보가 승인을 위해 카드사로 전송된다. 승인이 완료되면 주문이 완료된다.
실제 인쇄는 인쇄 에이전트가 담당한다. 인쇄 에이전트는 주문이 완료된 PDF 파일을 검사한다. 파일이 품질 요구 사항을 충족하지 못하면 고객에게 이 사실을 알리고 고객이 새 PDF 파일을 제공할 때까지 주문이 일시적으로 보류된다.
마지막으로 관리자는 항상 충분한 용지와 잉크 재고가 있는지 모니터링한다. 용지나 잉크의 양이 부족할 때마다 적절한 공급업체(용지 또는 잉크 공급업체)에 주문해야 한다.
- 주문형 인쇄 서비스의 UML Use Case Diagram을 작성하시오. (5점)

- 다음 주문하기 유즈케이스를 보고 UML Sequence Diagram을 작성하시오. (10점)
- Precondition: 사용자는 계정을 갖고 있다.
- Postcondition: 주문이 접수된다.
- Main success scenario
- a. 주문을 보낸다.
- b. 주문 정보를 검사한다.
- c. 주문이 접수된다.
- d. 신용카드 정보가 카드사로 전송된다.
- e. 주문이 완료된다
- Extensions
- c-1. 만약 정보가 잘못됐으면 사용자에게 정보를 다시 요청하고 b단계부터 다시 수행한다.
- e-1. 만약 신용카드 결제가 승인되지 않으면 사용자에게 새로운 신용카드 정보를 요청하고 d단계부터 다시 수행한다.
3. 다음 설명을 읽고 설명에 맞게 UML Swimlane Diagram을 그리시오. (10점) - 주문이 확정되면 온라인 영업, 회계, 배송, 인쇄 4개의 팀이 주문을 처리한다. 온라인 영업팀에서 주문을 인쇄팀으로 보내면 인쇄팀에서 관련 PDF 파일을 검사한다. 파일이 적합하지 않은 경우 고객에게 새 파일을 요청한다. 파일이 적합하면 회계팀에 신용 카드로 청구하라는 알림이 전송된다. 이 작업이 완료되는 동안 인쇄팀은 실제 인쇄를 수행하고 결과를 배송팀으로 보낸다. 배송팀에서 인쇄된 제품을 수령하고 회계팀으로부터 결제가 성공적으로 완료되었다는 확인을 받으면 제품이 고객에게 배송된다. 
2. 당신은 경쟁사의 제품을 역공학으로 분석하여 다음과 같은 코드를 작성할 수 있었다. 어떤 디자인 패턴을 사용하고 있고 어떤 class와 method가 해당 디자인 패턴과 관련되어 있는지 설명하시오. (총 2개) (15점)
public interface I1 {
public I2 method1();
}
public interface I2 {
public void method2(I3 i3);
}
public interface I3 {
public void method3();
}
public final class C1 implements I1 {
private static C1 c1 = new C1();
private C1() {}
public static C1 method4() { return c1; }
public I2 method1() { return new C2(); }
private static class C2 implements I2 {
C2() {}
public void method2(I3 i3) {
i3.method3();
}
}
}
public class C3 implements I3 {
private final String s;
public C3(String s) {
this.s = s;
}
public void method3() {
System.out.print(s);
}
}
public class C4 implements I3 {
private final I3 i3;
public C4(I3 i3) {
this.i3 = i3;
}
public void method3() {
System.out.print("*");
i3.method3();
}
}
public class Main {
public static final void main(String[] args) {
final I3 x1 = new C4(new C3("*"));
final I2 x2 = C1.method4().method1();
for(int i = 0; i < 100; i++) {
x2.method2(x1);
}
x2.method2(new C4(new C3("")));
}
}
- Singleton 패턴은
C1클래스에 적용되어 있다.C1클래스는 생성자를private으로 선언하여 외부에서 인스턴스를 직접 생성하지 못하게 막고, 클래스 내부에서static필드인c1을 통해 유일한 인스턴스를 생성해 관리한다. 외부에서는 오직static메서드인method4()를 통해서만 이 유일한 인스턴스에 접근할 수 있으므로, 이는 전형적인 singleton 패턴의 구현이다. - 데코레이터 패턴은
C4클래스와I3인터페이스의 관계에서 확인할 수 있다.C4클래스는I3인터페이스를 구현하면서 동시에 생성자를 통해I3타입의 다른 객체(i3)를 주입받는다.C4의method3()는 주입받은 객체의method3()를 호출하기 전후에System.out.print("*")와 같은 추가적인 기능을 수행한다. 이는 기존 객체(C3)의 코드를 수정하지 않고도 동적으로 책임을 추가하거나 기능을 확장하는 데코레이터 패턴의 특징을 보여준다.
(참고:
C1클래스의method1()이I2인터페이스의 구현체인C2인스턴스를 생성하여 반환하는 구조는 factory method 패턴에도 해당함.)
3. Formal inspection에 대한 다음 질문에 답하시오. (20점)
- Formal inspection의 절차와 각 참여자들의 역할에 대해 서술하시오. (5점)
- Formal Inspection의 절차는 일반적으로 계획(Planning), 시작 회의(Kick-off), 개별 준비(Preparation), 검토 회의(Inspection Meeting), 수정(Rework), 후속 조치(Follow-up)의 6단계로 진행된다. 참여자의 역할로는 검토 과정을 주관하고 조정하는 중재자(Moderator), 검토 대상 코드나 문서를 작성한 작성자(Author), 회의 중 코드를 소리 내어 읽거나 해석하는 낭독자(Reader), 발견된 결함과 이슈를 기록하는 기록자(Recorder), 그리고 사전에 결함을 찾아 회의에 참석하는 검토자(Inspector)가 있다.
- Formal inspection과 modern code review의 차이점을 설명하시오. (5점)
- Formal Inspection은 엄격하게 정의된 절차와 역할(중재자, 낭독자 등)이 존재하며, 반드시 대면 회의나 동기적인(Synchronous) 모임을 통해 결함 발견 자체에 집중하는 무거운 프로세스이다. 반면 Modern Code Review(예: GitHub의 Pull Request)는 도구를 활용하여 비동기적(Asynchronous)으로 진행되며, 절차가 비교적 가볍고 결함 발견뿐만 아니라 코드 품질 개선, 지식 공유, 팀 내 합의 형성에 더 큰 비중을 둔다는 점에서 차이가 있다.
- 현재 다수의 소프트웨어 개발 팀이 code review는 일상적으로 수행하고 있으나 formal inspection은 일상적으로 수행하지 않고 있다. 왜 그런가? (5점)
- Formal Inspection은 회의를 위해 다수의 인원이 동시에 시간을 비워야 하므로 일정 조율이 어렵고 시간적, 비용적 비용이 매우 높다. 또한, 준비와 회의 진행에 드는 노력이 크기 때문에, 빠른 배포와 지속적인 통합을 추구하는 현대의 애자일(Agile) 개발 환경이나 빠른 개발 주기에 매번 적용하기에는 부담스럽고 비효율적이기 때문이다.
- Formal inspection과 테스팅을 비교해서 각각의 QA 기술이 상대방이 갖지 못한 고유의 장점을 1개 이상 제시하시오. (5점)
- 테스팅은 코드가 실행 가능한 상태여야 결함을 찾을 수 있지만, Formal Inspection은 코드를 실행하지 않고도 요구사항 정의서나 설계 문서 단계부터 결함을 조기에 발견할 수 있다는 장점이 있다. 또한, 테스팅은 주로 오류의 증상(Symptom)을 발견하는 반면, Inspection은 오류의 원인(Cause) 위치를 코드 레벨에서 직접 지적할 수 있어 수정 비용을 절감할 수 있다.
4. 다음 테스팅에 대한 질문에 답하시오. (40점)
- 2개의 test suite A, B가 있다. A는 B보다 분기 커버리지가 더 높고 B는 A보다 이전 버전의 프로그램에서 더 많은 버그를 찾았다. 만약 두 test suite 중에 1개를 선택해서 사용해야 한다면 어떤 걸 선택해서 사용하겠는가? 그 이유는 무엇인가? (5점)
- 두 Test Suite 중 B를 선택해서 사용해야 한다. 그 이유는 테스트 커버리지가 높다는 것이 반드시 높은 결함 검출 능력을 보장하지 않기 때문이다. 커버리지는 테스트의 충분성을 나타내는 대리 지표일 뿐이며, Suite B가 이전 버전에서 더 많은 버그를 찾았다는 사실은 해당 테스트 케이스들이 프로그램의 복잡한 로직이나 경계값 등 오류가 발생하기 쉬운 부분을 더 효과적으로 공략하고 있음을 시사한다. 따라서 결함 검출 효율성(Defect Detection Efficiency) 측면에서 검증된 B가 더 신뢰할 수 있다.
- 테스트를 통해 100% 분기 커버리지를 달성했을 때 버그가 없다고 보장할 수 있는가? 보장할 수 있다면 간단하게 증명 과정을 설명하고 보장할 수 없다면 왜 보장할 수 없는지 구체적인 사례를 들어 설명하시오 (10점)
- 100% 분기 커버리지를 달성했다고 해서 버그가 없다고 보장할 수는 없다. 분기 커버리지는 제어 흐름의 모든 분기를 한 번씩 실행했는지만 확인할 뿐, 요구사항 자체가 구현되지 않은 경우(Missing Logic)나 데이터 값에 따른 오류는 잡아내지 못하기 때문이다. 구체적인 사례로,
if (x > 0) { y = 10 / x; }라는 코드가 있을 때x=5인 케이스만 테스트하면 분기 커버리지는 100%가 되지만,x=0일 때 발생할 수 있는 'Division by Zero' 오류나, 로직 자체가 잘못 설계된 논리 오류는 발견할 수 없다.
- 100% 분기 커버리지를 달성했다고 해서 버그가 없다고 보장할 수는 없다. 분기 커버리지는 제어 흐름의 모든 분기를 한 번씩 실행했는지만 확인할 뿐, 요구사항 자체가 구현되지 않은 경우(Missing Logic)나 데이터 값에 따른 오류는 잡아내지 못하기 때문이다. 구체적인 사례로,
- 100% 분기 커버리지를 달성하는 게 현실적으로 어려운 이유를 설명하시오. (5점)
- 현실적으로 100% 분기 커버리지가 어려운 이유는 '도달 불가능한 코드(Dead Code)'나 방어적 프로그래밍을 위해 작성된 예외 처리 구문 때문이다. 예를 들어, 시스템의 치명적인 메모리 부족 상황이나 하드웨어 결함을 처리하기 위한
catch블록 등은 일반적인 테스트 환경에서 인위적으로 조건을 만들어 진입하기가 매우 어렵거나 불가능할 수 있다.
- 현실적으로 100% 분기 커버리지가 어려운 이유는 '도달 불가능한 코드(Dead Code)'나 방어적 프로그래밍을 위해 작성된 예외 처리 구문 때문이다. 예를 들어, 시스템의 치명적인 메모리 부족 상황이나 하드웨어 결함을 처리하기 위한
- 소프트웨어 테스트의 품질을 측정하기 위한 방법으로는 변이 테스팅(mutation testing) 방법이 있다. 변이 테스팅이 어떻게 테스트 품질을 측정할 수 있는지 설명하시오. (5점)
- 변이 테스팅은 원본 소스 코드에 의도적으로 작은 문법적 변경(예:
+연산자를-로 변경)을 가한 '변이체(Mutant)'를 생성한 뒤, 기존 테스트 케이스를 실행하여 이 변이체들을 얼마나 잘 잡아내는지(테스트 실패를 유도하는지) 확인하는 방법이다. 전체 변이체 중 테스트에 의해 발견되어 제거된(Killed) 변이체의 비율인 '변이 점수(Mutation Score)'를 계산함으로써, 테스트 스위트가 코드의 미세한 변경에도 민감하게 반응하는지, 즉 테스트가 얼마나 촘촘하게 작성되었는지를 정량적으로 측정한다.
- 변이 테스팅은 원본 소스 코드에 의도적으로 작은 문법적 변경(예:
- 변이 테스팅이 성립하기 위한 가설 2가지에 대해 설명하시오. (10점)
- 첫째, 유능한 프로그래머 가설(Competent Programmer Hypothesis)이다. 이는 프로그래머가 작성한 코드는 대체로 올바르며, 발생하는 오류는 대부분 복잡한 설계 오류가 아니라 단순한 문법적 실수에서 비롯된다는 가정이다. 둘째, 결합 효과 가설(Coupling Effect Hypothesis)이다. 이는 단순한 오류(Simple Fault)를 잡아낼 수 있는 테스트 케이스라면, 이들이 복합적으로 결합된 복잡한 오류(Complex Fault) 또한 잡아낼 수 있다는 가정이다.
- 변이 테스팅을 우리가 실제 개발에 적용하기 위해서 가장 시급하게 해결해야 할 문제점이 무엇인지 설명하시오. (5점)
- 가장 시급한 문제는 높은 계산 비용(Computational Cost)과 실행 시간 문제이다. 작은 프로그램이라도 가능한 변이의 수가 수천, 수만 개에 달할 수 있는데, 각 변이체마다 전체 테스트 스위트를 컴파일하고 실행해야 하므로 시간이 너무 오래 걸린다. 또한, 문법적으로는 다르지만 기능적으로는 원본과 동일하게 동작하여 테스트로 잡아낼 수 없는 '동등한 변이체(Equivalent Mutant)'를 자동으로 판별하기 어렵다는 점도 해결해야 할 난제이다.

