티스토리 뷰

728x90

 

 

역자 서문 & 이 책에 대해

선후배 동료 개발자들에게 느낀 아쉬움 3가지

  • 개발 프로세스에 대한 관심 부족
  • 미흡한 툴 활용력
  • 마지막으로 단위 테스트의 중요성에 대한 인식 부족

인터페이스가 직관적이지 않다, (사용법을 오해하기 쉽다.)

이책의 목표는 필요한 기본 지식 그 이상을 제공 하는데 있음

  • 목 방식 & in-container 방식
  • JUnit 서드파티 확장 툴
  • 최신JUnit으로의 이동과 선호하는 IDE로의 통합

1장

소프트웨어 개발의 역사에서 이토록 많은 사람이 이렇게 짧은 코드로부터 이토록 큰 도움을 받은 적이 없었다. - 마틴 파울러

개발자 인수 테스트(acceptance test)

  • 코딩하고, 컴파일하고, 실행하고 프로그램 실행은 자연스럽게 테스트를 동반함
  • 간단한 패턴: 데이터를 넣고, 확인하고, 수정하고, 삭제함

역사

  • 1997년 에릭 감마와 켄트 백은 자바를 위한 간단하고도 효율적인 단위 테스트 프레임워크를 만들고, JUnit이라 이름지음
  • 여기에는 켄트 벡이 스몰토크(Smalltalk)용으로 먼저 만들었던 SUnit이라는 프레임워크의 설계 방식이 차용됨
  • 에릭 감마는 디자인 패턴 Gang of Four 중 한명임

단위 테스트

  • 한 단위(Unit)의 동작을 검사하는 테스트
  • 품질 보증(Quality Assurance) 테스트
  • 고객 (Customer) 테스트
런타임에도 잘 작동하는지는 알 길이 없음
자동화된 테스트만 갖추면됨
구현이 아무리 단순하더라도, 프로그램 기능 검증에는 종종 자동 기능(functional) 테스트나 자동 인수 테스트가 활용되기도 한다.
스택 추정 정보를 출력하고, 마지막에는 발생한 오류들을 요약한 예외를 던지게끔 수정

독립적으로 실행
오류를 식별하고 보고
어떤 테스트를 실행할지 선택하기 쉬워야 한다.

자바 리플렉션과 인트로스펙션 기능
test로 시작하는 메서드 정도

JUni 설계 목표

  • 유용한 테스트를 작성하는 데 보탬이 되어야 함
  • 시간이 지나도 가치가 변치 않는 테스트를 작성하는 데 보탬이 되어야 함
  • 코드 재사용을 통해 테스트 작성 비용을 낮추는 데 보탬이 되어야 함
// Maven

public static void main (String[] args) {
    junit.textui.TestRunner.run(suit());
}
import static org.junit.Assert.*;
import org.junit.Test;

assertEquals(60, result, 0)
  • class
    • 반드시 public
    • Test 끝나는 이름
  • @Test
  • 예상 값이 무한대라면 오차는 무시된다.
  • NaN끼리는 같은 값으로 간주한다.

2장

실수는 발견으로 들어서는 관문이다. - 제이므 조이스(James Joyce)
  • 테스트 클래스와 테스트 스위트, 테스트 러너의 개념
  • class
    • public
    • 파라미터를 받지 않는 생성자 있어야함
  • method
    • @Test
    • public
    • 파라미터도 받아서는 안됨
    • 마지막 반환형은 void
  • 클래스 인스턴스에서 실행되므로, 인스턴스 변수는 공유될 수 없음

assert 메서드에 접근하기 위해 정적 임포트 (static import) 활용

assertArrayEquals
assertEquals: A.equals(B); value
assertSame: A == B; reference
assertTrue
assertNotNull

Suite: 여러 개의 테스트 클래스를 동시에 실행해야 할 때

  • 특수한 형태의 테스트 러너
  • 척추에 비유
  • 테스트들의 집합
  • 함께 묶는 수단
  • 패키지 내의 여러 테스트 클래스를 묶는데 사용

Runner: 다양한 러너

파라미터화(parameterized)

  • 하나의 테스트를 여러 번 반복 실행하는 기능을 제공
  • 각종 데이터를 파라미터로 뽑아내고, 뽑아낸 파라미터의 값을 매번 바꿔가며 테스트를 호출
  • @RunWith(value=Parameterized.class) public class ParameterizedTest { private double expected; private double valueOne; private double valueTwo; @Parameters public static Collection<Integer[]> getTestParameters() { return Arrays.asList(new Integer[][] { {2, 1, 1}, //expected, valueOne, valueTwo {3, 2, 1}, //expected, valueOne, valueTwo {4, 3, 1}, //expected, valueOne, valueTwo }); } public ParameterizedTest(double expected, double valueOne, double valueTwo) { this.expected = expected; this.valueOne = valueOne; this.valueTwo = valueTwo; } @Test public void sum() { Calculator calc = new Calculator(); assertEquals(expected, calc.add(valueOne, valueTwo), 0); } }
  • @RunWith
  • Parameterized 클래스 사용
  • 대신 파라미터를 받는 생성자가 사용됨
  • 결과를 단언

Runner

  • 코딩-수행-테스트-코딩
  • 테스트 주도(test-first) 방식
  • Junit 4는 3.8.x 버전과 호환
  • @RunWith 이용해 원하는 테스트 러너의 클래스를 지정해주면 된다.

퍼사드(Facade, org.junit.runner.JUnitCore)

  • 하위 시스템이 제공하는 다수의 인터페이스를 통합해 하나의 인터페이스로 제공하는 디자인 패턴
  • 상호작용이 복잡하고, 사용자가 그 모두를 알 필요가 없을 때 활용
  • https://lktprogrammer.tistory.com/42
  • 코드를 깨끗이 유지하려면 막대를 녹색으로 유지하라 (JUnit 정신의 모태가 된 러너)
    java org.junit.runner.JUnitCoe CalculatorTest

Suite

  • 사용자가 스위트를 따로 제공하지 않으면 테스트 러나가 자동으로 하나를 만듦
    @RunWith(value = Suite.class)
    @SuiteClasses(value = { TestSuiteA.class, TestSuiteB.class })
    

@RunWith(value = Suite.class)
@SuiteClasses(value = { TestCaseA.class })
public class TestSuiteA {}

...


---

# 3장

테스트는 두려움을 지루함으로 변화시켜주는 프로그래머의 돌이다. - 켄트 벡(Kent Beck)


### 컨트롤러(Controller) 디자인 패턴
- 클라이언트와 상호작용하며, 각 요청 처리를 제어하고 관리
- 표현 계층(presentation-tier)
- 비즈니스 계층(business-tier)
- 컨트롤러는 다양한 애플리케이션에서 유용하게 활용됨
- HTTP 요청
- HTTP 세션
- 데이터베이스(영구 데이터 참고)

### 테스트 
- 다른 클래스와 구분이 쉬워지므로 (`suffix` vs `prefix`)
- 조직하거나 필터링하기 쉬워짐
- 실행할 테스트 케이스가 하나도 없으면 안됨

public, 메서드 호출 (전/후)

  • @Before
  • @After

public static, 클래스 (전/후)

  • @BeforeClass
  • @AfterClass
    ```
  • public
  • 여러개 정의 가능, 실행 순서 정의 안됨

한 번에 하나의 객체만 테스트

  • 문제의 원인이 컨트롤러인지 아니면 요청의 객체인지 단정하기 어렵기 때문
  • 서로 간에 어떤 영향을 줄지 예측하기 어려움
  • 복잡한 객체와 상호작용한다면, 복잡한 객체 대신 예측 가능한 테스트용 도우미 객체들로 감싸서 테스트
  • 더 많은 로직을 담을 수록, 동작하지도 않는데다가 디버깅까지 해야할 확률이 높아짐
  • 문제의 원인을 정확히 짚어내기가 그만큼 어려워짐
  • 실패한 테스트가 픽스쳐를 예상치 못한 상태로 남겨 놓기도 함
  • 모든 테스트 메서드는 같은 픽스쳐를 공유
  • 단위 테스트당 하나의 메서드를 할당하는 것은 식은 죽 먹기
  • assert 문을 전혀 사용하지 않는 것도 우리가 저지르는 실수
  • assert 문을 호출하도록 해야한다.

여러분의 테스트 메서드는 도메인 메서드만큼이나 간결하고 하나의 목표에만 집중해야 한다.

테스트 메서드에 의미있는 이름을 부여

  • 이름이 길어지거나 지저분해진다고 걱정할 필요는 없음
  • 메서드만으론 테스트 목적을 명확히 전달하기에 부족함
  • 메서드 이름은 설명을 담아 짓고 필요한 곳에 주석을 추가하라.

테스트 클래스 위치

  • 동일한 패키지
  • inner class
  • 복잡해질 일은 일어나지 않을 것 같다면, 내부 클래스로 선언하는 것이 가장 손쉬운 방법

테스트 순서

  • test fixture (초기화된 환경)
    • 초기화 코드 @Before
  • 대상 메서드 호출
  • 하나이상의 assert

assert 호출 시에는 실패 원인을 기술

  • 항상 첫 파라미터로 String 을 받도록, 의미있는 설명
  • 테스트를 합치지 말자.

assertSame vs assertEquals

  • 두 참조(reference)가 같은 객체를 가리키는 가를 검사
  • Object 클래스로부터 물려받은 equals를 사용해 객체를 비교

잊지말자.

  • 테스트는 코드에서 에러가 발생하는 것으로부터 우리를 보호할 목적으로 사용됨
  • 가능한 모든 실행 조건들을 빠짐없이 테스트 할 필요가 있음

예외처리

  • 커넥션 풀(connection pool) 고갈
  • 데이터베이스 서버 다운
  • 데이터베이스 서버가 제대로 설정
  • 필요한 모든 자원이 갖춰져 있다면 절대 일어나지 않음
  • 모든 자원은 한계가 있고, 언젠가 커넥션 대신 예외를 건네받을 날도 분명히 올것임
  • 사실 잘못될 가능성이 있는 일은 결국 터지게 마련이기 때문 (머피의 법칙)
  • 오류 상황을 강제로 만들어내는 것 (훌륭한 방법임)
  • 수동으로는 쉽게 만들어낼 수 없는 오류도 수없이 많음
@Test(expected=RuntimeException.class

테스트를 통해 코드를 개선하라.

  • 테스트 도중 사용하기 불편한 점이 발견되면, 주저하지 말고 더 사용하기 쉬운 코드로 리팩터링
  • 단, 중복이 발생하기 전까지 코드를 미리 바꿔 두고픈 충동을 억누르고 일단 놔두도록...
  • 익스트림 프로그래밍 규칙: 미리부터 기능을 추가하지 말라.. (띠용..)

예외 테스트도 읽기 쉽게 만들어라.

  • 직관적인 테스트 메서드명
  • 코드 라인에 주석을 달아두면, 예외가 발생되는 지점까지 훨씬 쉽게 파악할 수 있음

테스트를 통해 코드를 개선하라.

  • 모든 가능성을 다 테스트하려면 너무 큰 고통을 감내해야 하는 경우를 종종 마주하게 됨
  • 좋지 않은 설계를 발견하면, 하던일을 멈추고 도메인 코드 리팩터링에 착수
  • 큰 메서드를 몇 개으 ㅣ작은 메서드로 쪼개서 해결할 수 있다.
  • 상황에 맞는 적절한 리패터링을 활용

타임아웃 테스트

  • 예상된 행동과 예상된 결과
    @Test(timeout=130) // ms
  • 밀리초 단위의 제한 시간 설정
  • 일부 테스트들은 건너뛰는 것이 좋을 때도 있음 @Ignore(value="설명")

테스트를 건너뛸 때는 반드시 그 이유를 명시하라.

  • 여러분 스스로가 무엇을 하는 테스트인지 알고 있고, 단지 실패를 숨기기 위해 건너뛰는 것이 아님을 증명
  • 성공/실패 개수와 함께 건너뛴 테스트도 포함된 결과 통계

Hamcrest

  • 통계상, 쉽게 테스트의 철학에 빠져든다.
  • 큰 마음의 안정
  • 테스트 표현식을 만드는데 활용할 수 있는 매처 라이브러리
  • 유용한 매처(matcher)
  • 확장성 또한 아주 뛰어남
  • 인터페이스를 구현하고 적절한 이름의 팩토리 메서드를 채워 넣는 것이 전부
    assertTaht(values, hasItem(anyOf(eqaulTo("a"), eqaulTo("b"), eqaulTo("c"));

같은 패키지, 다른 디렉터리

  • 서로 독립된 디렉터리에 보관
  • src/main/java
  • src/test/java
  • 이 두 폴더에서부터 패키지 구조가 시작됨
  • 분리-평등(separate-but-equal)
  • 테스트 클래스는 접미어(?)를 사용
  • 런타임 JAR를 배포하거나 모든 테스트를 자동화시키는 것이 더 쉬워진다.
  • protected, default 멤버도 테스트 가능

4장

경쟁자의 프로그램이 죽으면 크래시(crash)되었다 말하고, 
여러분의 프로그램이 죽으면 '개성'이라 한다.
크래시는 보통 'ID 02' 같은 메시지를 수반한다.
ID는 '개성'을 뜻하는 약어이고,
뒤에 숫자는 앞으로 몇 달이나 더 테스트해야 하는지를 의미한다.
- 가이 가와사키
  • 단위 테스트가 어느 순간 즉홍적으로 수행하는 일이 아니기 때문
  • 단위 테스트를 기능 테스트, 통합 테스트 등의 다른 테스트와 구분해서 이해하고 있어야 함

단위 테스트가 필요한 이유

  • 기대한 대로 잘 동작함
  • 버그를 조기에 잡아내는 것
  • 기능 테스트보다 훨씬 높은 테스트 커버리지 달성
  • 팀 생산성
  • 회귀 테스트(regression test) 디버깅 시간 감소
  • 리팩터링과 코드 수정 시 올바로 하고 있다는 확신
  • 구현 품질 향상
  • 기대하는 행위를 문서화
  • 코드 커버리지 등 각종 측정을 가능하게 함 (정량적)

높은 커버리지 달성

  • 기능 테스트는 애플리케이션 코드의 약 70% 커버할 수 있다.

팀 생산성 향상

  • 검증된 코드 납품

회귀 테스트 수행 & 디버깅 감소

  • 코드가 정상 동작함을 증명해주고
  • 새 기능을 추가하기 위해 코드를 수정해도 좋다는 확신
  • 잘못 수행할 때마다 바로바로 알려주는 누군가가 곁에 있어주는 것만큼 마음 든든한 일도 없음
  • 디버깅할 필요성을 줄여줌
  • 몇 시간씩 헤매던 일에서 해방

확신에 찬 리팩터링

  • 코드 수정은 언제나 다른 어딘가를 망가뜨릴 수 있는 위험을 내포하기 때문에
  • 단위 테스트가 없다면 리팩터링의 정당성을 주장하기가 쉽지 않음
  • 리팩터링을 해도 좋다는 확신을 주는 안전망
  • 사전 계획 단계를 중요시했지만, 생산성은 낮을 수밖에 없다.
  • 수직적 코딩 선호
  • 재정비하는 리패터링을 장려함
  • 단위 테스트
  • 짧은 반복 & YAGNI(You Ain't Gonna Need it, 이건 필요하지 않을 거 아냐)
  • 동작 가능한 가장 단순한 것(The Simplest Thing That Could Possibly Work)

구현 품질 향상(by Refactoring)

  • 만약 테스트 하기 어려운 코드를 마주한다면,
  • 대부분의 경우 단위 테스트가 가능하도록 리팩터링해야한다.
  • 나쁜 냄새(bad smell)

기대 행위 문서화

  • 생산 코드(production code)
  • 다른 형태의 문서와 달리 항상 최신 상태로 유지된다는 이점도 제공
    @Test(expected=Asdf.Exception.class)
  • 어노테이션 테스트 메서드 명시
  • 발생하는 예외 명시

테스트의 종류

  • unit (단위)
  • integration (통합)
  • functional (기능)
  • stress/load (스트레스/부하)
  • acceptance (인수)

통합 테스트

  • 클래스를 다른 메서드나 서비스와 연동
  • 통합 테스트를 정의
  • 올바로 동작하는 객체를 작성하는 능력은 눈부시게 향상됨
  • 상호작용
    • 객체 간
    • 서비스 간: 서블릿, 외부자원
    • 서브 시스템 간: 다른 서버 요청

기능 테스트

  • API의 가장 바깥쪽에 해당하는 코드
  • 유스케이스 단위
    ```
  1. 보안 페이지에 접근
  2. 페이지로 돌려보내야 함
  3. HTTP status302

스트레스/부하 테스트

  • 동시다발적으로 사용할 때 애플리케이션은 과연 얼마나 버틸 수 있는가?
  • 대부분의 스트레스 테스트는 주어진 단위 시간 동안 애플리케이션이 얼마나 많은 요청을 처리할 수 있는가?
  • 측정한 처리량(throughput)
  • 최적화를 위한 제 1 규칙이 '하지 말라(Don't do it)
  • 최적화에 시간을 허비하지 말라는 것
  • 프로파일러(profile)는 최적화가 필요한 병목지점을 찾아줌 (병목을 제거해야함..!)
  • 하드웨어/운영체제
  • 가상화(virtualization) 시스템 도입 등으로 테스트 환경이 변화될 때

인수 테스트

  • 이해관계자(stakekholder)
  • 모든 목적에 부합되는지 확인해보고자 인수 테스트 수행
  • 사용성 & 룩앤필(look and feel)
  • 품질보증(QA, Quality Assurance)

단위 테스트

  • 논리: 메서드
  • 통합: 클래스
  • 기능: 컨트롤러 (순수한 단위테스트 아님...ㅠ)

블랙박스

  • 대상 시스템의 내부 상태와 동작에 대한 아무런 정보가 없음
  • 테스트는 순전히 시스템의 외부 인터페이스에 의존해 정확성 검증
  • 누구든 테스트에 참여

화이트 박스

  • 유리상자 테스트
  • 테스트를 작성할 때 구현에 대한 상세 지식들을 모두 활용

장단점

  • 사용자 중심 접근법 (빠른 & 빈번한 릴리스): 화이트
    • 테스트 스크립트 필요
    • 생각 유도하여, 명확하게 정의
  • 테스트 난이도: 화이트
    • 블랙박스가 더 난해하다.
    • 반드시 개발자가 직접 구현
  • 테스트 커버리지: 화이트, 블랙
    • 화이트 박스가 커버리지는 더 높음
    • 블랙 박스가 가치는 더 높음

결론

  • 점점 빨라지는 변화의 속도와 짧아지는 제품 릴리스 주기에 대응하려면,
  • 우리도 변화에 빠르게 대처해야만 함
  • 개발 프로세스 자체도 변화하고 있음
  • 예술적인 코드 작성만으론 충분하던 시대는 이제 끝남
  • 개발은 이제 기능적으로 완성되고(동작 가능하고) 테스트된 솔루션이어야 함
  • 논리 & 통합 & 기능 (삼위일체)
    • 유익하며 상호보완적
    • 개발자가 아니라 누구나 이용가능

5장: 테스트 커버리지와 개발

대상이 무엇이든 테스트를 충분히 하는 사람은 없을 것이다. - 제임스 고슬링(James Gosling)

  • 테스트 주도개발 TDD
  • 단위 테스트를 잘 작성해두면, 애플리케이션을 수행할 때나 리팩터링을 할 때 자신감이 생김

블랙박스 테스트

  • 가이드 문서뿐이므로,
  • 파라미터 값을 사용해야만 발생하는 특정 상황을 검사하는 테스트는 작성하지 못함

화이트박스 테스트 (블랙보다 더 좋음)

  • 100% 커버리지 달성가능
  • 더 많은 메서드에 접근
  • 메서드의 입력 & 보조객체를 앎
  • protected or package 접근 권한의 메서드에 대한 단위 테스트도 작성할 수 있어,
  • 코드 커버리지는 더욱 높아짐.

블랙박스 & 화이트박스 혼용

  • 블랙박스 테스트를 수행할 필요가 있을까?
  • 블랙박스 테스트는 객체 간 상호작용을 검사
  • 화이트박스 테스트는 객체 내부 검사

Cobertura (코버튜라)

  • JUnit 과 통합된 코드 커버리지 툴
  • Maven
  • HTML, XML 형태 결과물
  • instrument 코드: 인스트루먼테이션(byte-code instrumentation) 추가 바이트 코드
  • http://cobertura.sourceforget.net
    javac -cp junit-4.6.jar -d uninstrumented src\*.java
    

covertura-instrument --destination instrumented uninstrumented\Calculator.class

java -cp junit-4.6.jar:$COBERTURA_HOME
cobertura.jar;instrumented;uninstrumented;
-Dnet.sourceforge.cobertura.datafile=cobertura.ser org.junit.runner.JUnitCore TestCalculator

cobertura-report --format html --datafile cobertura.ser -- destination reports src


## 코드 커버리지를 100%까지 끌어 올릴지 여부는 개발을 진행하면서 논의해 결정할 사항임

---

### 테스트 가능한 코드 작성
- 테스트하기 쉬운 코드 작성하기
- 테스트 케이스를 하나 작성하는 것도 쉬울 때도 있지만, 때론 그렇지 않을 때도 있음
- 애플리케이션의 복잡도
- 가독성 높고 테스트 가능한 코드를 작성하라는 모범 사례를 잘 따른다면 복잡도를 최대한 낮출 수 있음
- 기존 코드를 리패거링해서 테스트하기 쉽게 고치는 것보다,
- 아예 처음부터 테스트하기 쉬운 코드를 작성하는 것이 항상 더 쉽다는 것을 기억하자!

### 공개 API는 계약이다.
- 공개(public) API의 시그너처는 절대 변경하지 않음
- 공개 API를 호출하는 것임을 알 수 있음
- 따라서 공개 메서드의 시그너처를 변경하면, 애플리케이션에서 이를 호출하던 모든 코드와 단위 테스트까지 수정해야 함
- 항상 상당한 주의를 기울여야 하는 작업
- 하위 호환성을 유지하려면 더 주의해서 수정해야 하기 때문
- 반드시 모든 공개 메서드를 테스트 해야 하는 이유
- 비공개 메서드의 경우에는, 좀 더 깊이 들어가 화이트박스 테스트를 사용해야 함

### 종속성을 줄여라.
- 단위 테스트는 코드를 고립시켜 검증함을 기억
```java
// X
class Vehicle {
    Driver d = new Driver();

// O
class Vehicle {
    private Driver d;
    Vehicle(Driver d) {
        this.d = d
  • 생성자는 간단하게
  • 최소 지식의 원칙
    • 디미터 법칙(The law of Demeter)
    • Don't talk Stranger
    • 객체를 요구하되 객체를 검색 X
    • 필요한 객체만 요청
  • 전역 상태 피하기
    만약 여러분이 관계를 만들어낸(코딩한) 사람이라면,
    여러분은 모든 종속성을 정확히 알고 있지만,
    여러분 이후에 합류한 사람들은 모두 당황하게 될 것이다.
    거짓으로 가득 찬 사회에 살게 된 것이다.

싱글톤(singleton)의 장단점

  • 인스턴스가 오직 하나 뿐임을 보장하는 유용한 디자인 패턴
  • private 생성자 & 정적 변수 (INSTANCE)
  • 지연 초기화 (lazy initialization)
    public class Singleton {
      private static Singleton INSTANCE;
      private Singleton() {}
      public static Singleton getInstance() {
          if (INSTANCE == null) {
              INSTANCE = new Singleton();
          }
          return INSTANCE;
      }
    }
  • 객체가 단 한번만 인스턴스화도미을 보장
  • 생성자를 private로 만들어 외부로부터 숨김
  • 싱글톤은 애플리케이션의 전역상태를 만들어 낸다는 명백한 취약점이 존재 (static)

제너릭 메서드

  • 유틸리티 성격의 정적 메서드가 늘어나면 문제를 야기
  • 연결점들은 다형성(Polymorphism) 활용
  • 컴파일 시간에 결정
  • 정적 모든 메서드는 테스트가 난해해짐
    • 이는 우리가 그토록 회피하고자 노력하던 코드 중복 생성으로 ...

상속보다는 컴포지션

  • 재활용 메커니즘: 상속
  • 하지만 테스트에는 컴포지션이 더 용이하다
  • 필요할 때에만 Credentials 변수가 생성되므로 테스트가 수월함

조건 분기보다는 다형성

  • switch, if 사용을 피하는 것은 복잡도 감소시키는 주요한 해결법 중 하나
  • 코드의 복잡도는 낮춰주고 가독성은 향상시켜줌

TDD (Test-driven development; 테스트 주도 개발)

  • 작동하는 깨끗한 코드(clean code that works)
  • 생명주기를 살짝 조정
  • 테스트는 바로 그 메서드 API 고객
  • 계약을 만족시킴
  • 설계 -> 문서화 -> 단위 테스트
  • 2가지 규칙
    • 실패하는 자동화 테스트 작성
    • 중복 제거
  • 유지보수성을 높이는 방향
  • 오늘의 문제는 오늘 해결되고, 내일의 문제는 내일 해결된다는 확신을 심어줌 (현재에 충실하라!)
실패하는 테스트를 먼저 작성해야 함
성공케 하는 코드를 아직 작성하지 못했기 때문임
테스트가 통과되면, 그 일은 멈추고 다음 문제 해결로 넘어가게 됨

개발 주기

  • 개발 플랫폼(development platform)
    • 항상 커밋(체크인)할 것을 강력히 권장
    • 코드를 커밋하면 다른 동료들도 변경된 코드를 볼 수 있음
    • 개발자 IDE에서 바로 실행되는 것이 보통
  • 통합 플랫폼(integration platform)
    • dev, stage, real
    • 지속적 통합(Continuous Integration)
    • CI: 애플리케이션의 패키징부터 배포와 단위/기능 테스트까지 자동으로 수행되도록 하는 것이 보통임
  • 인수 플랫폼(acceptance platform)/스트레스 테스트 플랫폼(stress test platform)
    • stage
    • 피드백을 얻으려면, 시스템을 가능한 자주 인수 플랫폼으로 배포
  • 프로덕션 플랫폼(pre-, production platform)
    • real

회귀 테스트

  • 하나씩 하나씩 새 기능을 덧붙여 나간다.
  • 새로운 메서드를 추가하기 보단 기존 메서드를 재활용하여 비용을 절약
  • 하나는 테스트 케이스 수행을 쉽게 자동화시킬 수 있다는 점이다.
  • 이전 테스트를 재활용하는 것을 회귀 테스트(regression test)
  • 수정이 발생할 때마다 그 즉시 단위 테스트를 첫번째로 돌려주는 것이 가장 효율적인 방어책
  • 역시 자동화가 최선이다.

6장: 스텁을 활용한 포괄적인 테스트

그래도 지구는 돈다. - 갈릴레오(Galileo)

  • 다른 클래스에 종속
  • 특정 런타임 화경에 의존적인 애플리케이션을 단위 테스트하는 것은 꽤나 고된 일
  • 테스트는 안정적이어야 하고,
  • 반복적으로 수행해도 매번 동일한 결과
  • 외부 서버: 서버를 시뮬레이션하는 방법 필요
  • 가짜를 만들어서 미처 준비되지 못한 부분을 시뮬레이션: 스텁 & 목 객체

스텁?

  • 아직 준비되지 못한 코드의 행동으로 가장하는 매커니즘을 말함
  • 대상 코드에 손을 대지 않으면서 매끄럽게 통합하는 장점
  • 호출자를 실제 구현물로부터 격리시킬 목적으로 런타임에 실제 코드 대신 삽입하는 코드 조각
  • 사용 예시
    • 깨지기 쉬워 수정이 불가
    • 포괄적인 테스트
  • 스텁을 활용한 테스트의 신뢰도는 높은 편
  • 제작은 녹록하지 않음
    • 흉내내야 할 시스템이 복잡하다면 더욱 그러함
    • 스텁 자체를 디버깅하는 상황도 종종 발생
    • 유지보수가 어려울 수 있음
    • 상세한 테스트에는 적합하지 못함
  • 스텁 정책 요구
  • 코드 블록을 대체
  • 시스템 전체를 대체

Jetty

public class JettySample
    public static void main(String[] args) throws Exception {
        Server server = new Server(8080);

        Context root = new Context(server, "/");
        root.setResourceBase("./pom.xml");
        root.setHandler(new ResourceHandler());

        server.start();
    }
}

example

@Before
public void setUp()

@After
public void tearDown()

7장: 목 객체를 활용한 테스트

더 거대하고 더 이해하기 쉬운 프로그램을 만드려는 엔지니어 vs 더 크고 더 우둔한 바보를 만들려는 천지만물 힘겨루기 - 리치쿡

  • JMock
  • EasyMock
  • 환경에 구애받지 않도록 하겠다는 것은 분명 훌륭한 목표

목 객체

  • 격리된 테스트
  • 코드 일부를 단위 테스트할 수 있도록 해줌
  • 단독으로 테스트
  • 작은 것은 아름답다고 했던가
  • 무자비한 리팩터링을 해도 좋다는 욕기의 제공이라는 것을 기억

목 객체 안에는 비즈니스 로직을 구현하지 X

  • 절대 비즈니스 로직을 가져서는 안 된다.
  • 목은 테스트가 시키는 대로만 수행하는 바보 객체(dumb object)
  • 스텁과는 정확히 상반되는 특성

제어 역전(Inversion of Control, IoC)

  • 비즈니스 로직과 관계되지 않은 객체는 모두 외부에서 생성해서 넘겨주는 것이 효과적인 설계
  • 호출자가 책임짐
  • 외부에서 완벽히 제어할 수 있게 됨
  • 이제 더 유연하고 어떤한 로깅이나 설정 구현체도 사용

상식

  • 문제가 될 가능성이 있는 것만 테스트!
  • final Class 상속 불가능!
  • 목객체 트로이 목마!

EasyMock

  • createMock (static method)
    // createMock(String name, Class claz);
    

private AccountManager mockAccount;

@Before
public void setUp() {
mockAccountManager = createMock("mockAccountManager", AccountManager.class );
}

- expect & replay
```java
expect( mockAccountManager.findAccountForUser( "1" ) ).andReturn( senderAccount );
expect( mockAccountManager.findAccountForUser( "2" ) ).andReturn( beneficiaryAccount );
replay( mockAccountManager );
  • verify
    @After
    public void tearDown() {
      verify( mockAccountManager );
    }

JMock

private Mockery context = new JUnit4Mockery();

private Mockery context = new JUnit4Mockery() {
    {
        setImposteriser( ClassImposteriser.INSTANCE );
    }
};
@Before
public void setUp() {
    mockAccountManager = context.mock( AccountManager.class );
}
@Test
public void testTransferOk() {
    final Account senderAccount = new Account("1", 200);

    final Account beneficiaryAccount = new Account("2", 100);

    context.checking(new Expectations() {
        {
            oneOf(mockAccountManager).findAccountForUser("1");
            will(returnValue(senderAccount));
            oneOf(mockAccountManager).findAccountForUser("2");
            will(returnValue(beneficiaryAccount));

            oneOf(mockAccountManager).updateAccount(senderAccount);
            oneOf(mockAccountManager).updateAccount(beneficiaryAccount);
        }
    });

    AccountService accountService = new AccountService();
    accountService.setAccountManager(mockAccountManager);
    accountService.transfer("1", "2", 50);

    assertEquals(150, senderAccount.getBalance());
    assertEquals(150, beneficiaryAccount.getBalance());
}
  • 동적 임포트를 사용
  • JUnit 러너로, 우수한 사용성
  • @Before context.mock

인터페이스와 제어 역전(IoC)과 같은 더 좋은 디자인 패턴을 적용하자!


8장: In-container 테스트

성공의 비밀은 성실함이다. 이를 흉내 낼 수만 있다면, 여러분은 성공할 것이다. - 장 지로두(Jean Giraudoux)

컴포넌트와 컨테이너

  • 생명주기 관리, 보안, 트랜잭션, 분산 등
  • 컴포넌트를 운영하기 위한 서비스를 제공

In-container 테스트의 전형적인 생명주기

스텁의 장단점

  • 외부(out-of-container) 테스트
  • 격리됨
  • 요청이 몇번 들어 왔고, 서버의 상태, 어떤 요청
  • 이해하기 쉽다.
  • 훨씬 적은 코드만으로 클래스를 격리시켜줌
  • 장점
    • 빠르고 가벼움
    • 만들고 이해하기 쉬움
    • 강력함
    • 큰 단위의 테스트
  • 단점
    • 전용 메서드 구현
    • 동작은 테스트하지 못함
    • 복잡한 상호작용
    • 노력이 필요
    • 유지보수 비용이 큼

목 객체의 장단점

  • 컨테이너 없이도 테스트
  • 컴포넌트 간 상호작용은 테스트 불가능
  • 통합테스트 필요
  • API 동작 방식을 정확히 알고 있어야만 테스트를 올바로 설정
  • 버그, 트릭, 임시방편 등을 감수
  • 컴포넌트 배포 작업 역시 테스트되지 않음
  • API에 대한 충분한 이해가 뒷받침
  • 외부 라이브러리의 경우 특히 어려울 수 있음
  • 스텁처럼 코드가 변경되면 유지보수

In-container 테스트의 장단점

  • 특화된 툴
  • 미흡한 IDE
  • 긴 수행 시간
  • 복잡한 설정
    • 애플리케이션 패키징
    • 컨테이너를 구동시키고 테스트
  • 초기부터 프로세스를 자동화하도록 이끌어, 지속적인 통합을 촉진
    • Maven 같은 빌드 툴 지원
    • 빌드 과정에서 만들어지는 다양한 산출물과 테스트 수행, 리포트 취합과 같은 작업의 복잡성을 숨겨주는 데 도움

결론

  • In-container 테스트는 목 객체 대비 설정에 많은 노력을 요구하지만, 그만큼 값어치를 한다.
  • taglib 같은 컴포넌트의 테스트
  • in-container 테스트와 목객체를 조합하면 어렵지 않게 해낼 수 있다.
  • 컨테이너 내에서의 동작을 검증하기 위해서는 In-container 테스트라는 아키텍처적 관점에서부터 테스트에 접근하는 기법 필요

9장: Ant로 JUnit 테스트 실행하기

분명 자동화되어 있을 거라 생각했는데, 여전히 버튼을 눌러줘야 한다. - 존 부루너(John Brunner)

Ant (Apache Ant)

  • 무료 오픈소스
  • 자바 프로젝트 빌드
  • JAR 파일 종속성 관리
  • JUnit 테스트 수행
  • Ant는 자바 애플리케이션 빌드의 산업 표준 툴이고, JUnit 테스트 관리와 자동화에도 훌륭한 툴
  • 해결
    • 컴파일러 클래스패스
    • 매번 수동으로 JUnit 테스트

개발자의 하루

  • 테스트를 개발 주기의 일부로 녹여내야 함
  • 모든 단위 테스트를 돌려본다. (규칙)
  • 안정적 시작점에서 출발하는지 항상 확인해야함
  • 테스트를 자동으로 쉽게 실행할 수 있는 수단이 필요

Ant 요소

  • 클린 빌드(clean build)
  • 빌드 파일(build file):
  • 타깃(target): path
  • 속성(property): 불변

Ivy

  • 유명한 오픈소스 종속성 관리(기록, 추적, 해결, 보고)
  • 설치가 직관적이다.
  • 네임스페이스를 선언

JUnit Report

  • XML/HTML 페이지로 추출 가능

테스트 일괄 수행

  • 와일드카드(wildcard)
    <batchtest todir="${target.report.dir}">
    <fileset dir="${src.test.dir}">
      <include name="**/${tests}.java"/>
      <exclude name="**/Test*All.java"/>
    </fileset>
    </batchtest>

자동화된 단위 테스트는 만병통치약이 아님

  • 회귀 테스트는 15~30%의 버그를 찾아주며,
  • 나머지 70~85%의 버그는 수동 테스트에 의해 발견됨

정리

  • Ant: 최고의 자바 소프트웨어 빌드 툴 중 하나
  • javac, junit, junitreport
  • Ivy: JAR 파일의 종속성 관리를 쉽게 도와주는 도구

10장: Maven2로 JUnit 테스트 실행하기

전통적 견해는 생각하는 고통으로부터 우리를 보호해준다. - 존 케네스 갤브레이스(John Kenneth Galbraith)

Maven 특성

  • Ant 빌드 파일이 잘 동작하게 하려면 매 프로젝트마다 약간씩의 소란을 겪어야 함
    • 자신만의 Ant 빌드 파일이 필요
  • Maven은 Ant와 비슷한 업무를 수행하는 툴이지만, 툴의 재활용성을 한 차원 끌어 올릴 목적으로 설계
  • 소스 빌드 환경
  • 소프트웨어 엔지니어가 빌드 시스템 구축에 많은 시간을 허비하면 안됨
  • 프로젝트를 처음 시작할 때 빌드 시스템을 설계하고 구현하는데 많은 시간을 들이지 않고,
  • 곧장 소프트웨어 개발에 뛰어들 수 있을 만큼 쉬워야 함

관례가 설정보다 먼저다.

  • 관례가 설정에 우선한다.
  • 소프트웨어 개발자들이 결정해야 하는 수 많은 설정의 수를 줄이기 위한 설계 원칙
  • 설정 대신 다양한 관례적인 개발자에게 따르도록 유도한다.
  • 이를 통해 개발자들이 매 프로젝트마다 고민해야 했던 지루한 설정들을 생략하고
  • 더 중요한 실제 업무에 집중할 수 있게 됨
  • target.dir 속성
    src/main/java/*
    src/test/java/*
  • ~/.m2/repository/

Maven 생명주기

  • Default: 프로젝트 산출물
  • Clean: 프로젝트 청소 (target dir remove)
  • Site: 프로젝트 문서
  • Validate: 올바른지 검사
  • Compile: 컴파일
  • Test: 테스트
  • Package: 컴파일된 코드를 배포 가능한 포맷으로 패키징 JAR
  • Integration test: 필요하다면, 통합 테스트가 가능한 환경으로 배포하고 테스트
  • Verify: 패키지의 유효성과 품질 기준 충족 여부
  • Install: 패키지를 로컬 저장소에 저장하여 종속된 다른 프로젝트에서 사용할 수 있도록 함
  • Deploy: 통합 혹은 릴리스 환경에서, 최종 패키지를 원격 저장소에 복사하여 다른 개발자나 프로젝트와 공유
mvn compile

Plugins

  • 플러그인 환경이자, 실행 & 소스 빌드 환경
  • 많은 플러그인을 부착할 수 있는 아키텍처
  • 플러그인 수가 상당히 많다. (제작 쉬움)
  • <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> </plugins> </build>
  • groupId
  • artifactId
  • version
    • 자동 업데이트나 재현 불가능한 빌드 문제를 예방하기 위해선 플러그인 버전을 고정해두는 것이 좋음

POM (Project Object Model)

  • Task
  • Target
  • pom.xml (build descriptor)
    • 상속이 가능
    • 간략한 pom.xml 파일은 대부분 Super POM으로 상속 받음
      <modelVersion>4.0.0</modelVersion>
      <groupId>
      <artifactId>
      <version>
      <packaging>jar</packaging>
      <name>
      <url>
      <dependencies>
      <developers>
      <developer>
          <name>
          <id>
          <organization>
          <roles>
              <role>
<parent>
    <groupId>
    <artifactId>
    <version>
</parent>

Maven 프로젝트

  • M2_HOME 환경변수
  • M2_HOME/bin PATH 환경변수에 추가
    • 어느 위치에서든지 mvn 가능
  • JAR들을 웹에서 다운 받음
mvn eclipse:eclipse -DdownloadSources=true -DdownloadJavadocs=true
mvn clean test

Maven Surefire Plugin

<artifactId>maven-surefire-plugin</artifactId>
<configuration>
    <includes>**/Test*.java</includes>
</configuration>

Maven HTML JUnit

maven-surefire-report-plugin

mvn surefire-report:report

부정적 측면

  • 소프트웨어 빌드 툴의 산업 표준은 여전히 Ant
  • Maven 훌륭한 틀, 틀 안에서 생각하도록 강요
    • 강요가 나쁜것일까?
    • 생각 & 행동 (Maven 방식)
  • 다른 곳에 파일 복사하는 플러그인이 없음
    • 복사할 필요가 없다는 결론에 도달
  • 일정한 틀 속에서 제대로 일하는 방식을 제시
  • 충분한 지식을 가지고 있으므로 Ant 위험하지 않다(?)

10장: 지속적 통합 툴

삶은 창의적 문제 해결의 연속이다. - 마이클 겔브(Michael J. Gelb)

  • 모듈 기반 아키텍처
  • 각 개발자는 자신이 맡은 모듈의 개발과 단위 테스트를 책임
  • 각 모듈은 서로 상호작용하므로, 이들이 잘 어우러지는지 확인하려면 모두를 한곳에 두어야
  • 서로 다른 모듈의 상호작용을 검증
  • 많은 시간을 필요로 하고
  • 모든 모듈이 다 갖춰져 있지 않은 경우
  • 각 개발자가 매번 모든 통합 테스트를 빠짐없이 수행하는 것은 이치에 맞지 않음
  • 매번 단위, 통합, 기능 테스트를 모두 수행한다면 개발 속도를 헤아릴 수 없을 만큼 지연

지속적인 통합 테스트

  • 가장 좋은 방법은 일정 간격(?) 15분
  • 이렇게 해서 어딘가 잘못된다면, 15분 이내에 통지를 받고, 고칠 기회
  • 지속적 통합 (Continuous Integration, CI)
    • 작업을 자주 통합하자
  • 자동화 도구가 필요하다.
    체크아웃
    모듈 빌드
    모든 단위 테스트 수행 (각 모듈이 고립된 상태)
    테스트 결과를 발행
  • 혼자서도 잘 동작하지 못한다면, 다른 모듈과의 통합성을 검사할 이유가 없음

성공 & 실패 알람은 중요하다, 성공에 대한 알람은 스팸이 될 수 있다.

Hudson (Jenkins)

  • 툴이 중요한게 아니라 지속적 통합을 따르기로 결정했다는 사실에서 더 큰 향상을 이룬다는 것
  • Winstone 서블릿 컨테이너와 함께 배포되어 다음 명령어로 바로 시작 가능
    java -jar hudson.war --httpPort=8888
    config.xml : 기본 설정 파일
    fingerprints: Fingerprint 기록을 저장
    plugins: 플러그인들 저장
    

jobs
{job_name}
config.xml: 잡 설정
workspace: 작업 디렉터리
latest: 가장 최근에 성공한 빌드로의 심볼릭

builds
{build_id}
build.xml: 빌드 결과 요약
log: 로그
changelog.xml 변경 로그


### develop 머지시 배포?

### 이점
- 오류 검출을 도울 목적
- 시스템에 결함을 심을 때마다 곁에서 알려줄 든든한 지원자
- 어쨌든 여전히, 통합을 깨뜨리는 변경이 발생하면 통보해줄 것
- CI 툴: 공짜다.
- 설치와 설정: 한두 시간이면 끝난다.
- 빌드가 문제없이 통합됐음을 아는 것: 값을 매길 수 없다.

## 지속적 통합이 오늘날 소프트웨어 개발 주기에서 중요한 개념

---

# 12. 표현 계층 테스트하기

> 디버깅이 소프트웨어 버그를 제거하는 과저이라면, 프로그래밍은 분명 버그를 심는 과정이다. - 에츠허르 데이크스트라(Edsger Dijkstra)

- 표현 계층, `GUI`의 결함을 찾는 것
- 나쁜 사용자 경험은 고객들을 고통스럽게 하고 웹사이트를 다시 방문하길 꺼리게 만듦
- 오동작으로 이루어질 수 있다.

### JUnit 객체의 `equals`가 어떻게 구현되어 있는지 스스로 잘 이해하고 있는지 확인 필요

---

### 테스트 프레임 워크 선택하기
- `HtmlUnit`
  - 100% 자바로 구현된 UI 없는 (headless) 브라우저 프레임워크
  - 테스트 케이스와 같은 가상 머신에서 동작
  - JS, DOM, CSS 등 HtmlUnit이 다루는 기술과 관련해서, 브라우저와 독립적인 테스트가 가능
- `Selenium`
  - 웹브라우저를 프로그램적으로 구동시켜,
  - 간단한 IDE 제공
  - 특정 브라우저나 운영체제 상에서의 검증이 필요하다면 사용

### HtmlUnit

WebClient
HtmlPage
HtmlParagraph

- 코드 중복은 우리의 적임을 명심하라
- 브라우저 버전
```java
WebClient webClient = new WebClient(BrowserVersion.FIREFOX_2);
WebClient webClient = new WebClient(BrowserVersion.FIREFOX_3);

WebClient webClient = new WebClient(BrowserVersion.INTERNET_EXPLORER_6);
WebClient webClient = new WebClient(BrowserVersion.INTERNET_EXPLORER_7);
WebClient webClient = new WebClient(BrowserVersion.INTERNET_EXPLORER_8);
private BrowserVersion browserVersion;

@Parameters
public static Collection<BrowserVersion[]> getBrowserVersions() {
    return Arrays.asList(new BrowserVersion[][]{
        {BrowserVersion.FIREFOX_2},
        {BrowserVersion.FIREFOX_3},
        {BrowserVersion.INTERNET_EXPLORER_6},
        {BrowserVersion.INTERNET_EXPLORER_7},
        {BrowserVersion.INTERNET_EXPLORER_8}});
}

public 생성자(BrowserVersion browserVersion) {
    this.browserVersion = browserVersion;
}

@Test
public void 테스트() throws Exception {
    WebClient webClient = new WebClient(this.browserVersion);
}
WebClient webClient = new WebClient();
// 마지막 슬래시(/)는 생략하지 말라.
final URL pageUrl = new URL("http://page1/");

MockWebConnection conn = new MockWebConnection();
conn.setResponse(pageUrl, "<html><head><title>Hello 1!</title></head></html>");

webClient.setWebConnection(conn);

HtmlPage page = webClient.getPage(pageUrl);
Assert.assertEquals("Hello 1!", page.getTitleText());

접근

  • javascript DOM API스크린샷 2021-06-16 오후 3 54 27
  • getAnchorByName
  • getAnchors
  • getBody
  • getFormByName
  • getForms
  • getFrameByName
  • getFrames
  • getElementById
  • getElementsByName
  • getElementBy
  • 원하는 원소를 찾을 때까지 예제를 횡단

XPath

  • 테스트 코드의 복잡성을 낮출 수 있음
  • XML 문서의 노드를 질의하기 위해 W3C에서 재정의함
  • XPath Checker, Firebug 무료 오픈소스 Firefoxx 애드온: 추출 가능
    /html/body/center/form/table/tbody/tr/td[2]/input[2]

Javascript

  • Document.write()
  • 활성화 디폴트
  • setJavascriptEnabled

CSS

  • setCssEnabled
  • 기본적으로 활성화
  • CSS 문제를 감지했을 때는 예외를 던지지 않는다.
  • DefaultCssErrorHandler 기본 핸들러로 사용되며 모든 CSS 문제를 로깅
  • SilentCssErrorHandler는 모든 CSS 문제 무시
    webClient.setCssErrorHandler(new SilentCssErrorHandler());

SSL

  • setUseInsecureSSL(true): 모두를 신뢰함

Cactus

  • 데이터 계층을 테스트
  • Cactus JUnit 확장하여 비즈니스 중간 계층
  • HtmlUnit 표현계층 담당
    beginFoo
    endFoo
    

종료 메서드 이름 없으면
end


---

## Selenium
- 테스트용 무료 오픈소스 툴
- 실제 브라우저를 활용한 테스트를 수행
- Selenium Remote Control(RC)
- IDE
- 클라이언트 드라이버 API
- 모든 테스트 클래스가 한 번씩 Selenium 서버를 시작/종료한다는 것이 단점
- 더 단순하다..!

---

# 13. Ajax 테스트 하기

> 배열 인덱스의 시작은 0이어야 하나, 1이어야 하나?
내가 제시한 절충안인 0.5는 짐작컨대 충분한 고려 없이 거절당했다.
- 스탠 켈리 부틀(Stan Kelly-Bootle)

### Google Web Toolkit(GWT)
- RPC(Remote Process Call)

Ajax는 종종 대문자로만 이루어진 AJAX와 연관되는데,
사실 오늘날의 Ajax는 Asynchronous 자바스크립트 + XML 그 이상이다.
Ajax 애플리케이션 구현에는 CSS, DOM, 자바스크립트, 서버단 스크립트, HTML, HTTP, 웹 리모팅(XMLHttpRequest) 등의
기술이 종합적으로 활용된다.


### 이벤트
- 콜백함수
- 드래그 & 드롭
- 폼 검증과 제출(submission)
- 이벤트 처리
- 백(back)
- 갱신(refresh)
- 실행취소(undo)
- 다시실행(redo)
- 네비게이션
- 상태관리
- 캐싱
- UX
- 타임아웃
- 중복클릭

### 용어
- 정상성 검사(sanity check)
- JSON(Javascript Object Notation)
- 인스트루먼트 데이터(instrumentation data)

---

# 14. Cactus를 이용한 서버단 자바 테스트 하기

> 좋은 설계도 때가 있다.
일단 동작하게 만든 후, 올바르게 동작하게 만들라. 
- 켄트 백


### In-container 테스트
- 객체의 생명주기를 관장하는 유일한 주체가 바로 컨테이너
- 반드시 컨테이너 안으로 배포시켜야하는 결론에 도달
- 러너가 테스트를 시작함
- 프록시 리다이렉터(proxy redirector)

### 서비스 수준 계약(service level agreement, SLA)
- HTTP request
- HTTP response

### 코드가 잘못되었다면, 테스트를 실패하게 만드는 것은 훌륭한 실천법

실패해야 할 때 테스트가 항상 실패하는지 검증하라.


### TDD 방식에 따라 동작하는 가장 간단한 행태로 구현하라

과도한 설계를 경계해야 한다는 의미를 내포한다.
인정사정없이 리팩터링하라고 권한다.


### 구현되지 않은 메서드에서는 예외를 던져라

null 반환하는 것보다
예외를 던지는 것이 낫다.
구현이 끝나지 않았음을 더욱 명확히 알려준다는 점
호출되면 항상 예외가 발생하므로 정상적인 동작 결과인지 헷갈릴 일이 없다는 점


### 아카이브
- EAR
- WAR

### 용어
- JavaBean(DynaBean)
- JSP(Java Server Page)
- JNDI(Java Naming And Directory Interface) look up
- MOJO(Maven POJO: Maven Plain old Java object)

mvn package

플러그인 실행 순서는 중요하다.


---

# 15. JSP 애플리케이션 테스트하기
> 90%의 코드를 작성하는 데는 전체 개발 기간의 10%밖에 소요되지 않는다.
허나 나머지 10%의 코드를 완성하는 데에 개발 기간의 90%가 소요된다. 
- 톰 카길(Tom Cargill)

### 문제점
- POJO(매니지드 빈)
- 메서드 문제
- 오타
- 부적절한 인터페이스
- getter/setter
- 매니지드 빈 중복 선언
- 다양한 JSF 태그
- 잘못된 내비게이션

### 테스트
- 직렬화
- 누락된?
- 올바른?
- 중복된?

### 블랙박스 방식
- 성공하더라도 추후 코드를 약간 수정하면, 테스트가 실패한다는 것이다.

---

# 16. OSGi 컴포넌트 테스트하기
> 머리로는 이해되지만 동작하지 않는 것을 이론이라 한다.
동작은 하지만, 왜 그런지 이해하지 못하는 것은 실천(practice)이라 한다.
프로그래머는 이론과 실천을 조합해 사용한다. 즉, 동작도 이해도 되지 않는다.
- 미상(anonymouse)

### OSGi의 모듈 시스템
- `mvn clean install`
- `ant`
- `번들` 과 서비스라는 핵심 개념 & 테스트의 대상

---

# 17. 데이터베이스 엑세스 테스트하기

소프트웨어 프로젝트는 그 규모에 상관없이 종속성이 가장 큰 문제다.
프로그램에서 중복을 제거하면 종속성도 함께 제거된다. (dependency)

  • 켄트 백
  • 영속 계층(대략, 데이터베이스 엑세스 코드)은 의심의 여지없이 기업용 제품에서 가장 중요한 부분임
  • 단위 테스트는 코드를 격리된 환경에서 실행해야함
  • 데이터 베이스와 상호작용해야 함
  • 단위 테스트는 쉽게 작성하고 쉽게 돌릴 수 있어야 함
  • 데이터베이스 엑세스 코드는 복잡하고 다루기 어려움
  • 단위 테스트는 빨라야 하는데, 데이터베이스 엑세스는 상대적으로 느림

단위 테스트는 격리된 환경에서 수행해야 함

  • 절대적 규칙은 없음
  • 적절한 SQL 문을 사용하는지, 올바른 객체를 얻어내는지 같은 기능을 검증
  • 종대종(end-to-end) 시나리오에서만 발생하는 상황
    • JPA 애플리케이션에서 빈번히 발생하는 초기화 지연 오류

설명

  • 단위 테스트는 쉽게 작성하고 쉽게 실행할 수 있어야 함
  • 단위 테스트는 빠르게 테스트되어야 함
  • 커넥션은 값비산 작업, 완료된 후 닫아주는 것이 좋음
  • 값을 하드코딩하지 말라
    • 단 한 번밖에 사용되지 않을지라도, 가능하면 모든 곳에 변수 혹은 상수를 사용
  • 개발자마다 하나의 데이터베이스를 사용
  • 대상 데이터베이스가 테스트되는지 확인
    • 일일, 최종 데이터베이스 사용
    • 호환성 문제
    • 빨리 알아낼 수록 좋음
  • 데이터를 읽고 저장하는 보조적인 테스트
  • 읽기 테스트 케이스에서 모든 기본 시나리오를 검증
  • 데이터셋 용도를 계획

18. JPA 기반 애플리케이션 테스트

불행하게도 우리는 객체 관계(O/R) 임피던스 미스매치 문제를 해결해야 한다.
이를 위해선 두 가지를 이해해야 하는데, 
객체를 관계형 데이터베이스에 매핑하는 과정이 첫번째, 
매핑을 구현하는 방법이 두번째다.
- 스콧 엠버
  • 종교적 논쟁
  • 원초적인 SQL
  • 서드파티 툴
  • 툴에 데이터베이스 액세스 관련 전체를 위임
  • 트랜잭션을 시작하고 커밋하는 것이 가장 중요함
  • 객체가 어떻게 데이터베이스 테이블에 매핑되는지 정의해야 함
  • 트랜잭션 생명주기 관리
  • 문제는 당연히 더 빨리 발견하고 빨리 해결 할 수록 좋음

정리

  • ORM 툴을 이용하면 JAVA 개발을 크게 간소화 해줌
  • 하지만 기술이 아무리 훌륭하고 일이 얼마나 자동화 되는지와 관계없이,
  • 테스트 케이스 작성은 피할 수 없음
  • 비즈니스 계층: Facade
  • 영속 계층: DAO 사용

19. JUnit에 부스터를...

모든 것을 가능한 간단하게 만들어라.
하지만 그 이상 간단하게는 하지 마라.
- 앨버트 아인슈타인(Albert Einstein)
  • mvn clean test

assert는 일을 쉽게 만든다

  • 생산성, 명확성
  • assertGreaterThan(x, 42)
  • JUnit-addons
    assertTrue()
    assertFalse()
    assertEquals()
    ...

객체지향

  • 객체 내부 상태를 알면 안됨
  • 우수한 캡슐화와 테스트 용이성을 염두하여 클래스를 설계
  • 가끔 껍질을 뚫고 내부 정보에 접근해야 할 필요
  • 의존 객체 주입(Dependency Injection, DI)
  • 컨테이너의 등장으로 getter & setter 없는 속성을 갖는게 점점 일반적인 상황
  • 대신 @Resource, @Autowired 런타임에 종속 개체를 주입

리플렉션

  • 리플렉션 API 몇개로 풀어헤칠 수 있다는 것을 아는 순간 세상이 한순간에 무너져버릴 것이다.
  • 캡슐화 회피는 오직 JVM에게 그렇게 하라고 허락했을 때에만 가능
  • Field.setAccessible(true) 같은 메서드를 호출하면
  • JVM은 SecurityManager에게 호출자가 접근 규칙을 변경할 권한이 있는가 물어본다.
  • 기본 값은 허용으로 해두어서 필드가 쉽게 노출되게 해두었냐고 불평할 수도 있지만,
  • 사실 대부분의 경우 이렇게 하는 것이 훨씬 편리하다.

JUnit-addons

PrivateAccessor.setField(facade, "userDao", dao)

FEST-Reflect

User user = Reflection.constructor().in(User.class).newInstance();

가장 중요한 것은 이런 툴들이 존재함을 인지하고 프로젝트 초기에 평가해볼 수 있다는 점이다. 생산성을 향상시켜주는 툴이므로 더 빨리 적용할수록 더 많은 시간이 절약 가능하다.


인터셉터 패턴

가로채는 intercept 방법
인터셉터를 담을 Delegate 객체가 필요
  • 확장성과 유연성 향상
  • 관심사의 분리를 가능 (AOP)
  • 재사용성을 증대
  • 올바로 사용되지 않으면, 보안 문제를 야기
728x90

'읽은책' 카테고리의 다른 글

Mastering Ethereum  (0) 2022.01.04
Effective Java  (0) 2021.12.31
터틀 트레이딩 - 마이클 코벨  (0) 2021.11.08
생각이 너무 많은 서른 살에게 - 김은주  (0) 2021.09.28
사랑한다고 상처를 허락하지 말 것 - 김달  (0) 2021.06.12
댓글