책 오브젝트를 기반으로 하는 코드스피츠 강의 오브젝트2 - 6회차 를 정리한 내용입니다.
Testing
설계가 잘못되었다고 생각되어서 고쳤더니
고친 설계가 잘된건지 어떻게 알까?
고친 설계를 올바르다는 확신하는 Testing 도구가 필요하다.
UnitTest
알고리즘 로직의 단위 테스트를 검증한다.
예) CenterLayoutArrangerTest.swift
화면 정 가운데에 정상적으로 배치하는지 검증한다.
func testCenterArarange() {
let actual = arranger.arrange(viewSize: viewSize, childSizes: childSizes, interval: 20)
/*
|--------------------|
|------o---o---o-----|
|--------------------|
*/
let expect = [
CGRect(x: 157, y: 15.0, width: 20, height: 20),
CGRect(x: 197, y: 15.0, width: 20, height: 20),
CGRect(x: 237, y: 15.0, width: 20, height: 20)
]
XCTAssertEqual(actual, expect)
}
설계는 알고리즘의 코드를 배치하는 기술이고
배치하는 방법은 역할모델에 따라서 객체간 메시지를 전달하는 방법이다.
설계 관점에서는 협력 관계 사이에 객체 간 통신이 제대로 이루어지고 있는지? 를 테스트하고자 하는 것이다.
TDD 에서 UnitTest 는 "Getting Stuck" 이 발생하지 않도록 점진적인 개선이 되는 것이라고 잘못생각하고 있다.
Getting Stuck 이란?
만약 최초부터 어려운 문제를 정의하고 해결한다면 그 이후로 나오는 문제를 해결 할 수 없는 상황이 발생할 수 있습니다.
이러한 상황은 ”Getting stuck”이라고 정의하며, 이 경우 두 가지 탈출 방법이 있습니다.
- 처음부터 다시 짠다.
- Production Code가 잘 동작하도록 수정한다.
UnitTest 는 사실 그렇게 중요하지 않다.
잘못된 코드의 테스트코드를 작성해봤자 어차피 잘못되었기 떄문이다.
그러면 어떻게 해야할까?
UnitTest 보다 약간 더 큰 범위로 Test 를 알고있어야 한다.
그것을 IntegrationTest 라고 한다.
IntegrationTest
In-Memory 객체망 통신, 원격지나 이기종에서도 Test 를 하는 것을 말한다.
하지만 이렇게 한다고 하더라도 Test 의 범위는 좁다!!
httpTest, mockwebserver 와 같이 Local 환경에서 실제 Http 통신을 할 수 있는 Testing Framework 가 존재한다.
하지만 고객의 요구사항은 여전히 높다.
고객은 진짜 앱, 웹사이트에서 동작하는 것을 봐야만 한다.
이걸 AcceptanceTest 라고 한다.
SystemTest
시스템이 완전히 통합되어 구축된 상태에서 정보시스템의 기능을 총체적으로 검사하는 것이다.
통합된 각 모듈들이 원래 계획했던 대로 작동하는지, 시스템의 실제 동작과 원래 의도했던 요구사항과는 차이가 없는지 등을 판단하게 된다
시스템 테스트는 기능 측면보단 비기능적인 요구 사항도 만족되는지를 확인하게 됩니다.
비기능적 테스트는 사용성, 신뢰성, 견고성, 성능, 보안성, 유지 보수성 등을 테스트하게 됩니다.
https://osb0728.tistory.com/27
AcceptanceTest
고객은 실제 운영 환경에서 최종적으로 동작하는 것을 보고 싶어한다.
동영상에서는 Acceptance Test 와 e2e Test 라고 말하지만
Stackoverflow 에서는 다르다는 의견이 있다.
테스트 주도 주기 시작
켄트 벡이 말하는 TDD 에서는 Red, Green, Blue 3 cycle 의 순환주기가 있다는 것이 핵심이다.
Red (failling test): 실패하는 Test 를 작성한다. 요구사항을 파악하는 단계
Green: 가장 빠르게 실패 Test 를 통과하도록 만든다.
Refactor: 중복코드나 구조를 정리한다.
현실세계의 순환 주기
첫번째 주기
첫번째 주기에서는 구체적인 기능 코드를 얇게 만들고
반드시 등장인물들을 다 등장시켜서 골격을 만들고 배포를 해야한다.
결과적으로 실제 배포환경(AWS, DB, Jenkins 등) 설정을 하는 것이다.
테스트의 명확성보다 골격 구조에 집중
동작하는 골격과 불확실성
시스템 목적 이해
불확실성은 기능을 무엇을 해야알지? 알아가는 과정이다.
일반적으로 도메인 주도 개발과 순서가 다르다는 걸 눈치차리셨다면 고수!
일단 가장 중요한 것은 프로그램이 잘 동작하도록 만드는 것이다.
그래야만 시스템 제약사항을 파악하고 그에 맞춰 도메인을 설계할 수 있다.
상상속의 서버에서 상상속의 언어로 상상속의 Framework 를 쓰게 된다면 동작하지 않는 프로그램일뿐이다.
아키텍처
latency 를 빨라야 한다는 요구사항이 있다면 그것을 중점에 두고 설계를 할 필요가 있다. (항공, 원자로 관련 시스템)
경영진이 중요한 의사결정을 하기 위한 경영 지원 시스템을 만든다면 latency 가 느려도 정확성의 가치가 높다.
대부분 아키턱처를 설게한다고 하면 구현해야하는 기능을 기반으로 공통 요소를 뽑아낸다.
하지만 구현하는 기능을 아키턱처의 기본이 아니고 시스템의 요구사항이 가장 기본이다.
객체지향에 대한 기본원리(대체가능성, 내적동질성), Gof 디자인 패턴은 객체지향 설계에서 가장 밑바닥 수준 문법이다.
진정한 아키텍처로 성장할라면 아키텍처 레벨의 패턴을 배워야 한다.
책 - 엔터프라이즈 애플리케이션 아키텍처 패턴
(하아,,, 공부할 것들이 참 많다.)
피드백
다른 것이 중요한 것이 아니라 릴리즈하는데 성공해야한다.
개발 - 릴리즈 - 도메인 전문가 순환을 많이 도는 것이 프로젝트 성패에 달려있다.
아키텍처에 기본적으로 포함되는 내용은 부분적인 확인이 가능할 것이다.
(메뉴판, 멤버쉽) 일부 기능을 부분적으로 포함
린 사고방식의 핵심은 낭비를 줄이는 것이다. 린 스타트업 프로세스는 고객 개발(Customer Development)을 사용하여, 실제 고객과 접촉하는 빈도를 높여서 낭비를 줄인다. 이를 통해 시장에 대한 잘못된 가정을 최대한 빨리 검증하고 회피한다.
개발 외적 요소
코드를 Commit 을 Merge 하는 데 1일 이상 걸리는 경우가 많다.
이것을 설계자 입장에서는 비기능적 요구사항으로 본다.
이제서야 우리는 실제 기능 구현 일정을 예측할 수 있게 된다.
테스트 운영 요령
첫번째 주기에서는 bootstrap 구조를 베이스에 대해서 설명했다.
하지만 회귀테스트를 진행하면서 버그가 발생한다.
변화율에 따른 격리가 되지 않았기 때문이다.
이제와서 보니 켄트벡의 테스트 주도 개발 의미는?
"어차피 변화율에 따른 격리를 제대로 하지 못할 것이니
테스트 코드를 잘 짜서 격리있게 짜라"
시니어를 구준짓는 기준은 추가 기능을 구현하면 회귀테스트가 발생시키지 않는 것이다.
(드디어 동서남북, abcd 를 분간이 되는 사람)
왜? 변화율에 따른 격리되어 있기 때문이다.
테스트 운영요령
단위테스트
장점: 복잡한 알고리즘을 검증하는데 가장 좋다.
단점: 단위테스트에 몰입해서 통합을 실패하면 문제가 된다.
Code Coverage 100% 이여도 통합을 실패하면 더 위험하다.
통합테스트
장점: 서드파티문제, 다양한 외부문제들을 미리 파악할 수 있다.
단점: 에러를 찾기 어렵다.
일반적으로 먼저 서버가 안정적이라고 확정짓고 클라이언트를 검증해야 한다.
클라이언트 (다른 Android OS, 다른 Browser, 다른 해상도) 조합성 폭발의 다양한 환경에 노출되어 있기 때문에
클라이언트문제인지 서버문제인지 알기 어렵다.
하지만 현실세계는 서버, 클라 둘다 같이 통합테스트를 진행한다.
인수테스트
장점: 시스템의 실패, 컨텍스트 오류 (고객 요구사항)를 캐치할 수 있다.
단점: 통합테스트 보다 더 많은 조합성이 폭발한다.
코드작성요령
모든 문제를 해결하는 코드
모든 사용자에 대해 고려해서 짜는 것인지(지구 평화를 지킨다고 표현)? 주어진 업무를 해결하는 것인지?
우리는 지구 평화를 지킬려고 노력하면 안된다.
주어진 업무에 해결하는 것이 효율적으로 비용을 사용하는 방법이다.
실제 필요한 기능테스트, 필요한 문제를 해결하는 코드
실제 Client 코드나 Test Code를 작성하면서
"모든 문제를 해결하는 코드" 에서 말했던 지구 평화를 위해 짜는 것인지? 주어진 업무를 해결하는 것인지 알 수 있다.
불필요한 코드가 없는 것보다 있는 것이 더 큰 문제이다.
시간이 지남에 따라 혼란스러울 수 있기 때문이다.
오른쪽 이두박근은 Unit Test 에 대한 나열이다.
그러면서 입문자일 수록 단위 테스트를 작성하는데 몰입되기 쉽다.
하지만 정말 중요한 테스트는 행위를 테스트 하는 것이다.
우리는 행위를 테스트를 기준으로 테스트를 해야한다.
과도한 UnitTest 가 있는 이유는 값을 기준으로 함수가 만들어졌기 때문이다.
값이 아닌 Type 형으로 함수를 짜야한다.
❌ func function(value1: int, value2: int)
✅ func function(object: class)
실패하는 테스트는 진단정보를 명시한다.
이 테스트를 통과하지 못하면 코드에 이런 기능이 책임과 역할을 다하지 못할 것이라는 것을 의미
개인적으로 생각으로 "진단정보" 풀어서 이야기하면 요구사항이다.
어떠한 요구사항이 있는지를 구체화하는 과정이라고 설명할 수 있다.
규모가 큰 코드의 유지보수
객체작성의 원칙
단일책임원칙을 기반으로 객체망으로 분리할 수 있어야 한다.
객체망으로 분리할 때 가장 중요한 것은 메시지이다.
메시지는 어떻게 전송할 수 있을까?
관계(의존성, 알림, 조정) 3가지 기준을 가지고 메시지를 전송하여야 한다.
관계를 다른 말로 하면 메시지의 전송 이유이다.
객체 지향 설계의 달성
테스트와 객체설계 및 발견
메인(호스트) 코드부터 작성
무조건 TestCode, Main 함수부터 짜야한다.
객체지향에서 핵심적인 내용 중 하나는 객체지향의 주인공은 Class 가 아니라 Instance 이다.
Instance 가 실제로 어떤 동작을 하기 원하는 가를 파악하고 Class 를 짜는 것이 좋다.
작은 규모의 테스트 유지
테스트 규모가 크다는 의미
- 값 Context로 함수를 작성했다.
- 단일 책임 원칙을 지키고 있지 않다.
- 의존성이 높다
의존성 주입
의존성 주입은 처음부터 코드를 작성하는 것이 좋다.
Instance 가 외부에서 주입될때 얼마나 제약점이 많은지를 파악할 수 있다.
Java 를 기준으로 메소드에 지정되어 있는 Parameter의 Type 형은 기본적으로 반공변이다.
공변성, 반공변성, 무공변성에 대한 블로그는 여기에서 확인할 수 있습니다.
[ +T ] 공변성 : 타입 T를 확장한 타입에 대해서 허용
[ T ] 무공변성 : 타입 T만 허용
[ -T ] 반공변성: 타입 T의 상위(부모) 타입에 대해서 허용
객체지향에서 값을 쓰지 않고 Type을 쓰는 이유는
값이나 형에 의존적이지 않고 역할과 책임을 기술하는 것이다.
함수는 외부에서 Type을 받아들일 때 Type의 범위를 제한할 수 있다.
그러므로 처음부터 의존성 주입을 할 수있도록 만들어야 한다.
목객체 테스트
객체망의 협력을 테스트할 수 있다.
컨텍스트 독립성: 객체가 격리되어 있는 책임을 수행하고 영향을 미치지 않는지?
책 소개
테스트 주도 개발로 배우는 객체 지향 설계와 실천
통합 테스트에 대해 자세히 배울 수 있다.
엔터프라이즈 애플리케이션 아키텍처 패턴
http://www.yes24.com/Product/Goods/22384677
참고:
https://partnerjun.tistory.com/78
'객체지향설계' 카테고리의 다른 글
코드스피츠 2강 오브젝트 - 5회차 (0) | 2019.10.31 |
---|---|
코드스피츠 2강 오브젝트 - 3회차 (0) | 2019.10.30 |
코드스피츠 2강 오브젝트 - 4회차 (0) | 2019.10.30 |
코드스피츠 2강 오브젝트 - 2회차(2) (0) | 2019.10.29 |
코드스피츠 2강 오브젝트 - 1회차 (0) | 2019.10.28 |