Search
🧪

현실 세상의 TDD - 이규원

1. Engineering

가. 공학적 접근

자원을 최적화하여 유용한 가치 생산(또는 문제해결)을 위해 이론(또는 기술)을 선택적으로 채택하는 것
1) 공학이란
engineering, the application of science to the optimum conversion of the resources of nature to the uses of humankind. - Britannica
2) 재정의
공학이란 과학적 이론을 토대로 자원을 최적화여 인류에게 유용한 것을 만드는 것
→ 컴퓨터 공학이란 컴퓨터 과학 위에 탄생한 이론 또는 기술을 토대로 컴퓨팅 자원을 최적화하여 유용한 컴퓨터 시스템을 만드는 것
키워드: 과학적 이론, 자원의 최적화, 유용성
→ 유용한 것: 공학의 목적
→ 자원의 최적화: 공학적 목적 달성의 평가 지표(?)
→ 과학적 이론: 공학적 목적 달성의 수단

나. 패턴

TDD 배경 이론: 고유한 문제를 작은 문제로 나눈다. 작은 문제를 해결할 수 있는 패턴을 적용한다.
1) 고유한 문제와 패턴
패턴이란 알려진 문제의 일반적이고 재사용할 수 있는 해결법 - 이규원
현업의 기술 문제는 모두 고유하다
현업의 기술 문제에 적용 가능한 패턴은 존재하지 않는다
2) 작은 문제
현업의 기술 문제는 작은 문제로 분해될 수 있다
작은 문제는 과거에 반복적으로 해결되었을 가능성이 높다
이미 반복적으로 해결된 작은 문제는 알려진 해결법(패턴)을 통해 효율적으로 해결할 수 있다

다. 문제해결 노하우

1) 연습과 근육기억
연습을 통해 반복적으로 발생하는 작은 문제를 효율적으로 해결할 수 있다
연습을 통해 작은 문제를 해결하다보면 적은 생각 비용으로 유사한 문제를 해결할 수 있다
→ 근육기억이 커진다
2) trade off
현실의 기술문제는 매우 심각한 문제를 덜 심각한 문제로 거래하여 해결하는 경우가 많다

2. TDD Basic

가. 기능 명세

프로그래머는 도메인 지식을 확실하게 이해하기 위해 기능명세 기반으로 불충분한 지식에 대해 능동적으로 요청할 수 있어야 한다
1) 도메인
소프트웨어가 풀어야 할 문제가 정의되는 공간 - 이규원
비지니스 프로그램의 도메인은 비지니스적 문제
2) 도메인 지식의 흐름
비지니스 영역의 전문가 → 기획자 → 프로그래머 → 컴퓨터
비지니스 영역의 전문가에 가까울 수록 도메인에 대해 추상적이고 목적 중심적인 지식을 가짐
→ 해결하고 싶은 비지니스 문제에 대해 현실적인 수단을 고려하지 않고 최상의 결과를 고려
컴퓨터에 가까울 수록 도메인에 대해 구체적이고 수단 중심적인 지식을 가짐
→ 해결하려는 비지니스 문제에 대해 현실적인 해결방법을 기반으로 최선의 결과를 고려
3) 지식의 흐름에서 프로그래머의 역할
기획자가 작성한 기능 명세를 바탕으로 프로그램을 설계하고 코드로 구현
→ 기능 명세는 도메인 지식에 대해 구체적인 표현으로 번역된 작업물(주요 산출식 등)
→ 프로그래머는 기능 명세를 바탕으로 도메인에 대해 구체적인 지식을 가져야 함
도메인 지식에 대해 명확한 이해가 없다면 기획자나 전문가에게 지식의 보강을 요청해야 함
→ 도메인 지식에 대해 프로그래머가 스스로 결정하는 것은 무책임한 행동
4) 기능명세와 문제
지식의 저주에 따라 충분한 도메인 지식이 기능명세 내 전달되지 않았을 경우 프로그램에 문제 발생 가능
→ 지식의 저주: 전문가 입장에서 전제로 알고 있는 지식을 상대방에게 전달하지 않음으로써 발생하는 문제
ex) 양돈업에서 돼지의 성별은 암컷, 거세 수컷, 비거세 수컷으로 나뉨. 하지만 프로그래머에게 해당 성별 지식이 전달되지 않은 상태에서 프로그램이 개발된다면 추후 문제 발생

나. 테스트 종류

비용과 성능면에서 테스트 자동화가 수동 테스트 보다 우월하다.
1) 수동 테스트
수동 테스트란 품질 담당자(QA)가 UI로 기능 검증하는 것
→ 프로그램 외부의 기능에 대해 검증 가능
상대적으로 테스트 실행 비용이 높음
→ 프로그램이 고도화될수록 실행 비용이 가하급수적으로 높아짐
→ 지속적인 코드의 갱신에 따라 기존의 기능에도 영향을 주므로 모든 기능을 사람이 테스트하는 것은 높은 비용을 유발함
테스트 결과의 품질이 상대적으로 낮음
→ 테스트의 품질이 QA의 역량에 영향을 받음
2) 테스트 자동화
테스트 자동화란 프로그래머가 작성한 테스트 코드로 기능 검증
→ 프로그램 내외부 기능에 대해 검증 가능
상대적으로 테스트 실행 비용이 낮음
→ 테스트 코드 작성 비용이 발생하지만 전체적인 테스트 실행 비용은 수동테스트에 비해 낮음
테스트 결과의 품질이 상대적으로 높음
→ 테스트의 품질이 프로그래머가 작성한 코드에 영향을 받음

다. 코드 분해

사고 한계와 개발 효율성을 감안하면 프로그램을 작은 단위로 분해해야 한다. 적절히 분할하기 위해 프로그램을 인터페이스와 모듈로 나누어 설계해야 한다. 하나의 인터페이스에 여러 작업자가 개발한 모듈을 조립하기 위해 단위테스트를 통해 모듈에 대한 신뢰성을 보장해야 한다.
1) 목적
인간의 사고 한계
→ 한 사람의 사고력으로 일정 수준 이상의 프로그램의 모든 논리적 관계를 이해하고 관리할 수 없음
→ 일정 수준 이상의 프로그램을 개발하고 관리할 때 여러 사람이 프로그램을 분할하여 개발하고 관리하게 됨
개발 효율성
→ 분할된 작은 문제에 대한 패턴이 존재할 가능성이 높으므로 상대적으로 빠르게 해결 가능
2) 방법
인터페이스, 모듈 설계
→ 인터페이스가 잘 설계되면 프로그램 전체를 읽기가 쉬워짐
→ 인터페이스에 연결된 모듈(구현부)에서는 다른 사람을 통해 자유롭게 작업 가능
단위테스트
→ 구현부(모듈)에 대해 신뢰성이 보장이 되어야 인터페이스에 붙여서 사용 가능
→ 모듈에 대한 신뢰성은 단위테스트를 통해 보장 가능
3) 유의할 점
가독성 고려
→ 일정 수준 규모의 서비스에서 코드 쓰는 것 보다 읽는 것이 더 많음
→ 기능 추가 시 기존 연관 코드 읽기
→ 디버깅 시 기존 연관 코드 읽기
→ 성능 개선 시 기존 연관 코드 읽기
재사용 고려
→ 재사용하는 코드의 수정은 최소화, 기존 코드에 영향을 줄 수 있으므로

라. 단위 테스트

1) 주요 고려 사항
반복되는 테스트 코드 제거
테스트 결과의 품질 보장
2) 방법
하나의 단위테스트에 존재하는 여러 테스트 케이스 중 하나의 케이스에서 실패하더라도 다른 케이스에서 실패한 케이스와 독립적으로 테스트 케이스의 성공여부를 확인할 수 있어야 함
→ 테스트 코드의 반복되는 부분을 줄이다 보면 결과의 품질을 낮출 수 있음

마. 테스트 우선 개발

1) 테스트 코드 특징
모듈(구현부)의 출력부분에 대해 명확하게 확인 가능
테스트 통과 여부에 따라 모듈의 신뢰성에 대해 자가검증 가능
테스트 코드가 운영코드에 대한 클라이언트가 되므로 운영코드의 사용성에 대해 확인 가능
2) 방법
개발자가 상상할 수 있는 테스트 케이스를 모두 통과할 수 있는 테스트 코드 선 작성
→ 테스트 케이스를 하나씩 늘리는 방식은 한계 존재하므로 랜덤 입력 기반의 테스트 코드 작성 권장
운영코드 작성
작성된 운영코드가 테스트 케이스 중 일부를 통과하지 못하면 운영코드 수정
작성된 운영코드가 테스트 케이스를 통과하면 운영코드 대상 리팩토링 작업 수행
테스트 코드 작성 후 해당 테스트 케이스를 만족시키는 운영코드 작성

바. 정리된 코드

코드 가독성 향상에 따른 개발 생산성 향상을 위해 리팩터링을 해야한다. 리팩터링에 따른 코드의 기능 변화여부를 확인하기 위해 테스트가 반드시 필요하다.
1) 목적
생산성: 정리된 코드는 가독성이 높아짐. 가독성이 높으면 협업 시 타인의 코드를 빠르게 이해하고 작업할 수 있으므로 개발 생산성 향상됨
2) 리팩터링
factoring: 수학적 객체에 대해 유사하거나 더 작은 요소의 조합으로 재작성하는 것
→ 내부적으로 요소의 조합은 변하지만 수학적 객체의 결과값은 변하지 않음
re-factoring: 코드 내부의 동작방식이나 구성에 변화를 주는 것
→ 코드의 결과값에는 변화가 없음
코드의 결과값에 변화 여부를 판단하기 위해 해당 코드에 대한 테스트 필요

사. 테스트 주도 개발

1) TDD 전체 흐름
2) 실패 테스트 추가
테스트의 실패여부 확인하여 테스트의 정상동작여부 확인
→ 테스트 코드의 오류로 인한 실패가 아니라 운영코드에 의한 실패인지 확인 필요
3) 테스트 성공
모든 테스트 케이스를 통과하도록 운영코드 수정
→ 운영 코드가 모든 테스트 케이스를 통과하도록 구성한다는 것이 해당 기능의 모든 요구사항을 만족할 수 있도록 테스트 구성
운영코드의 수정은 반드시 테스트 성공을 위한 부분만 반영되어야 함
→ 테스트 케이스에 반영되어 있지 않는 기능에 대한 수정이 운영코드에 반영되면 테스트를 통해 해당 운영코드에 대해 검증할 수 없으므로 운영코드에 대한 신뢰성 저하
4) 구현 설계 개선
구현 설계 개선
→ 인터페이스에 대한 설계는 변경되면 전체적인 테스트가 깨지므로 구현부에 대한 설계만 수정
→ 구현 설계 개선 시 운영코드의 가독성, 모듈로써의 적응성, 기능으로써의 성능에 대해 중점을 두고 작업
켄트 벡의 설계 개선 규칙
→ Passes the tests: 설계 개선 시 기존 작성된 테스트 케이스를 모두 통과하는 것을 전제해야 한다
→ Reveals intention: 설계를 개선하여 해당 기능의 의도가 보다 잘 드러날 수 있도록 한다.
→ No duplication: 코드의 중복을 제거한다.
→ Fewest elements: 테스트를 통해 검증 가능한 것 외의 코드를 허용하지 않는다

3. TDD Intermediate

Reference

이규원, 현실 세상의 TDD: 안정감을 주는 코드 작성 방법, https://fastcampus.co.kr/dev_red_ygw