진소희
1. 📌 핵심 개념 정리
✅ 요약하기
시스템에 들어가는 모든 소프트웨어를 직접 개발하는 경우는 드물다. 때로는 패키지를 사고, 때로는 오픈소스를 이용한다. 어떤 식으로든 이 외부 코드를 우리 코드에 깔끔하게 통합해야만 한다.
- 외부 코드 사용하기
- 패키지 제공자와 인터페이스 사용자 사이에는 특유의 긴장이 존재한다. 패키지 제공자나 프레임워크 제공자는 적용성을 최대한 넓히려 애쓴다.
- 반면, 사용자는 자신의 요구에 집중하는 인터페이스를 바란다. 이런 긴장으로 인해 시스템 경계에서 문제가 생길 소지가 많다.
- 경계 살피고 익히기
- 외부 코드를 사용하면 적은 시간에 더 많은 기능을 출시하기 쉬워진다. 외부 패키지 테스트가 우리 책임은 아니지만, 우리 자신을 위해 우리가 사용할 코드를 테스트하는 편이 바람직하다.
- 타사 라이브러리를 가져왔으나 사용법이 분명치 않다고 가정.
- 대개는 하루나 이틀 문서를 읽으며 사용법을 결정한다. 그런다음 우리쪽 코드를 작성해 라이브러리가 예상대로 동작하는지 확인한다. 때로는 우리 버그인지 라이브러리 버그인지 찾아내느라 오랜 디버깅으로 골치를 앓는다.
- 외부 코드를 익히기는 어렵다. 외부 코드를 통합하기도 어렵다. 두가지를 동시에 하기는 두 배나 어렵다.
- 곧바로 우리쪽 코드를 작성해 외부 코드를 호출하는 대신 먼저 간단한 테스트 케이스를 작성해 외부 코드를 익히는 것을 학습 테스트라 부른다.
- 학습 테스트는 프로그램에서 사용하려는 방식대로 외부 API를 사용하려는 목적에 초점을 맞춘다.
- 외부 코드를 사용하면 적은 시간에 더 많은 기능을 출시하기 쉬워진다. 외부 패키지 테스트가 우리 책임은 아니지만, 우리 자신을 위해 우리가 사용할 코드를 테스트하는 편이 바람직하다.
- log4j 익히기
- 로깅 기능을 직접 구현하는 대신 아파치의 log4j 패키지를 사용하려 한다고 가정하자.
- 문서를 자세히 읽기 전에 첫 번째 테스트 케이스를 작성한다. 화면에 hello를 출력하는 테스트 케이스다.
@Test public void testLogCreate(){ Logger logger = Logger.getLogger("MyLogger"); logger.info("hello"); }
- 테스트 케이스를 돌리면 Appender라는 뭔가가 필요하다는 오류가 발생한다. 문서를 좀 더 읽어보니 ConsoleAppender라는 클래스가 있다. 그래서 ConsoleAppender를 생성한 후 테스트 케이스를 다시 돌린다.
@Test public void testLogAddAppender(){ Logger logger = Logger.getLogger("MyLogger"); ConsoleAppender appender = new ConsoleAppender(); logger.addAppender(appender); logger.info("hello"); }
- 이번에는 Appender에 출력 스트림이 없다는 사실을 발견한다.
@Test public void testLogAddAppender(){ Logger logger = Logger.getLogger("MyLogger"); logger.removeAllAppenders(); logger.addAppender(new ConsoleAppender( new PatternLayout("%p %t %m%n"), ConsoleAppender.SYSTEM_OUT )); logger.info("hello"); }
- 제대로 동작하지만, ConsoleAppender.SYSTEM_OUT 인수를 제거했더니 문제가 없다. 하지만 PatternLayout을 제거했더니 또 다시 출력 스트림이 없다는 오류가 뜬다.
- 문서를 좀 더 자세히 읽어보니 기본 ConsoleAppender생성자는 설정되지 않은 상태이다.
public class LogTest { private Logger logger; @Before public void initialize () { logger = Logger.getLogger("logger") ; logger.removeAllAppenders(); Logger.getRootLogger().removeAllAppenders(); } @Test public void basicLogger () { BasicConfigurator.configure(); logger.info("basicLogger"); } @Test public void addAppenderWithStream) { logger.addAppender(new ConsoleAppender( new PatternLayout ("%p %t %m%n"), ConsoleAppender. SYSTEM_OUT)); logger, info("addAppenderWithStream"); } @Test public void addAppenderWithoutStream () { logger.addAppender(new ConsoleAppender ( new PatternLayout ("%p %t %m%n") )); logger.info("addAppenderWithoutStream") ; } }
- 최종 코드이다.
- 학습 테스트는 공짜 이상이다
- 패키지 새 버전이 나온다면 학습 테스트를 돌려 차이가 있는지 확인한다.
- 학습 테스트는 패키지가 예상대로 도는지 검증한다. 일단 통합한 이후라고 하더라도 패키지가 우리 코드와 호환되리라는 보장은 없다.
- 패키지 새 버전이 나올 때마다 새로운 위험이 생긴다. 새 버전이 우리 코드와 호환되지 않으면 학습 테스트가 이 사실을 곧바로 밝혀낸다.
- 학습 테스트를 이용한 학습이 필요하든 그렇지 않든, 실제 코드와 동일한 방식으로 인터페이스를 사용하는 테스트 케이스가 필요하다.
- 이런 경계 테스트가 있다면 패키지의 새 버전으로 이전하기 쉬워진다.
- 아직 존재하지 않는 코드를 사용하기
- 상대 팀이 아직 API를 설계하지 않았을 때, 우리가 바라는 인터페이스를 구현하면 우리가 인터페이스를 전적으로 통제한다는 장점이 생긴다. 또한 코드 가독성도 높아지고 코드 의도도 분명해진다.
- API 인터페이스가 나온 다음 경계 테스트 케이스를 생성해 우리가 API를 올바로 사용하는지 테스트할 수도 있다.
- 깨끗한 경계
- 경계의 대표적인 예: "변경"
- 소프트웨어 설계가 우수하다면 변경하는데 많은 투자와 재작업이 필요하지 않다.
- 통제하지 못하는 코드를 사용할 때는 너무 많은 투자를 하거나 향후 변경 비용이 지나치게 커지지 않도록 각별히 주의해야 한다.
- 경계에 위치하는 코드는 깔끔히 분리한다. 또한 기대치를 정의하는 테스트 케이스도 작성한다.
- 통제가 불가능한 외부 패키지에 의존하는 대신 통제가 가능한 우리 코드에 의존하는 편이 훨씬 좋다.
- 외부 패키지를 호출하는 코드를 가능한 줄여 경계를 관리하자.
- 경계의 대표적인 예: "변경"
2. 🤔 이해가 어려운 부분
🔍 질문하기
책을 읽으며 이해하기 어려웠던 개념이나 명확하지 않았던 내용을 정리합니다.
- 개념 또는 원칙의 이름
- 어려웠던 부분
경계 테스트 - 이해한 점
입력 값의 경계에서 오류가 발생할 가능성이 높다는 점을 이용하여, 최소값, 최대값, 경계 바로 안쪽과 바깥쪽 값을 테스트하는 기법.
- 어려웠던 부분
3. 📚 참고 사항
📢 논의하기
관련된 자료가 있다면 공유하고, 더 깊이 논의하고 싶은 아이디어나 의견을 정리합니다.
- 관련 자료 공유
- 추가 자료
log4j 취약점
- 추가 자료