강의 소개
나만의 MVC 프레임워크 만들기
목표
- 객체지향 프로그래밍에 대한 이해
- HTTP 프로토콜 및 HTTP 웹 서버 동작원리 이해
- MVC 구조 및 DI 내부 동작 원리 이해
내용
- 개발환경 구축
- 객체지향 패러다임
- 웹 애플리케이션 이해
- 서블릿 프로그래밍
- JDBC 프로그래밍
- MVC 프레임워크 만들기
- DI 프레임워크 만들기
- Spring Boot 코드 분석
개발환경 구축
JDK 설치하기.
adoptium.net/
> Documentation > Install Eclipse Temurin
Apache Tomcat 설치
- Servlet Interface(Spec) 구현체
ServletContainer 중 하나
Which version?
에서는 아파치 톰캣 버전 별로 어떤 스펙을 구현했는지 확인 가능하다. 지원하는 자바 버전도 확인 가능.
- 아파치 버전 9를 다운받는다.
- 윈도우는
.zip
맥은 tar.gz
를 다운받는다.
압축파일을 열고, bin > startup.bat
을 실행하면 아파치가 잘 설치되었음을 알 수 있다.
(맥의 경우 startup.sh
)
localhost:8080
을 웹페이지에 입력해서 톰캣 페이지를 띄우는 것으로 제대로 실행됐음을 알 수 있다.
- 종료는
bin > shutdown.bat
을 실행하면 된다.
도커
- 컨테이너 기반의 가상화 플랫폼
- 컨테이너 기반의 가상화
- 격리된 환경에서 프로세스를 실행하는 기술.
- 애플리케이션은 도커 엔진을 통해 호스트 자원을 사용할 수 있고, 게스트OS가 없기 때문에 용량도 가볍운 편.
- 하이퍼 바이저 기반의 가상화
- Guest OS : 가상서버
Host Operating System : 물리 서버.
Hypervisor : 호스트와 게스트를 연결. 서버 가상화 기술로 호스트 서버에 설치되고 호스트와 게스트를 분리하며, 각각의 게스트는 하이퍼바이저에 의해 관리되고, 리소스를 할당받게 된다.
- 격리된 환경에서 또 하나의 가상서버를 실행하는 기술
- 게스트 OS는 다양한 OS 선택이 가능하다.
게스트 OS에서 사용되는 애플리케이션은 호스트 서버의 자원을 사용하기 위해서는 게스트 OS를 통해야되고, 때문에 속도 측면에서 느릴 수 있다.
- 도커 허브
- 도커 컴포즈
- 다중 컨테이너를 정의하고 실행하기 위한 도구
- YAML 파일을 사용하여 다중 컨테이너를 구성.
도커를 이용한 환경 구성
- 도커 다운로드
cmd에 docker -v
를 입력하면 도커가 잘 깔렸는지 확인할 수 있음.
- Docker Hub
도커 이미지 저장소
- Tags 탭을 눌러 좀 더 간단하게 할 수 있다.
하지만 나의 경우
error during connect: This error may indicate that the docker daemon is not running.
라는 오류메시지가 출력되며 실패했다. 원인을 찾아보자.
- 블로그를 좀 뒤져보니 해결방법이 나왔다.
- 설정에 들어가서 저것을 체크해주는 것으로 문제 해결!
도커 컨테이너 생성 및 실행
docker run --name ( name ) -e MYSQL_ROOT_PASSWORD=<password> -p3306:3306 -d mysql:{version}
docker run : 도커 실행
--name ( name ) : 이름 설정
-e MYSQL_ROOT_PASSWORD=~~ : 패스워드 설정
-p 3306:3306 포트 설정
mysql:{version} : mysql 이미지 설정
- 현재 실행중인 도커 컨테이너 목록 출력
docker ps -a
- MySQL 도커 컨테이너 접속
docker exec -it ( 컨테이너 이름 ) bash
- MySQL 접속
mysql -u root -p
-u
: username
-p
: password
객체지향 패러다임
1. 테스트 코드 실습
테스트코드 작성 이유
- 문서화 역할 : 잘 작성된 테스트코드는 그 자체로 문서화역할을 수행함.
- 코드에 결함을 발견하기 위해서 : 테스트코드를 작성하면서 결함을 발견할 수 있음.
- 리팩토링 시 안정성 확보
- 테스트 하기 쉬운 코드를 작성하다 보면 더 낮은 결합도를 가진 설계를 얻을 수 있음
TDD
- Test Driven Development (테스트 주도 개발)
- TFD (Test First Development) 라고도 함.
- 프로덕션 코드보다 테스트 코드를 먼저 작성하는 개발 방법
- TFD(Test First Development) + 리팩토링
- 기능 동작을 검증(메서드 단위)
BDD
- Behavior Driven Development (행위 주도 개발)
- 시나리오 기반으로 테스트 코드를 작성하는 개발 방법
- 하나의 시나리오는 Given, When, Then 구조를 갖는다.
비밀 번호 유효성 검증기
- 요구사항
- 비밀번호는 8~12자
- 8자 보다 적거나 12자 보다 만으면 Exception 예외를 발생시킴.
- 경계조건에 대해 테스트코드를 작성해야 함.
작업 준비하기
- IntelliJ를 실행해서 새로운 프로젝트 생성.
- AssertJ 디펜던시 추가.
- 프로그램을 위한 소스코드 작성부분은 프로덕션 코드
테스트 패키지 밑을 테스트 코드라고 부름.
두 패키지를 맞춰주는 것이 좋다.
테스트 메서드 작성
- alt + insert를 누르고 선택하는 것으로 테스트 메서드 삽입 가능
- 요구사항을 주석처리해서 클래스에 명시한다.
@DispalyName
을 사용해서 목적이 무엇인지 명시해주자.
메서드이름도 그럴듯하게 바꿔준다.
PasswordValidator
라는 클래스에게 validate를 맡길 것임. 비밀번호 serverwizard
에 대해.
assertThatCode
사용. 람다 표현식도 사용.
assertThatCode()
안의 코드가 호출이 되었을 때 예외가 발생하지 않으면
.doesNotThrowAnyException
반환. → 테스트가 성공한 것.
물론, PasswordValidator
클래스가 없기에 당연히 실패한다.
리팩토링 하기
- 비효율적으로 작성된 코드.
password.length()
라는 변수가 두변 중복되니 지역변수로 빼고 싶다.
컨트롤 + 알트 + v
- 리팩토링 후 테스트가 똑같이 통과된다면, 리팩토링이 잘 됐다고 생각할 수 있다.
테스트 코드를 작성해두면 리팩토링 시 (심리적) 안정감을 확보할 수 있다.
- Exception 발생한 경우에 대해서도 Test 코드 작성
경계 조건에 대한 테스트
- 경계값에서 오류가 발생하는 경우가 많다.
때문에 경계값에 대해 테스트를 생성해서 오류를 방지하는 것이 좋다.
@ParameterizedTest
가 필요하다.
- build.gradle에서
junit-jupiter-params
디펜던시를 추가해준다.
@ValueSource
를 주입해주면 validatePasswordTest2가 받을 수 있다.
테스트코드를 작성하다보면 결합도가 낮아진다.
- Passay 의존성을 추가해준다.
implementation 'org.passay:passay:1.6.1'
Parameterized Test가 뭔데?
- 테스트 코드를 작성할 때 중복되게 작성해야할 때 사용할 수 있음.
중복되는 부분은 여러 개의 테스트 메서드를 만드는 것은 관리도, 가독성에도 좋지 못함.
사용방법
- 의존성 추가.
@ParameterizedTest
사용
- 1)
@ValueSource
@ValueSource
는 리터럴 값의 단일 배열을 지정할 수 있으며 매개 변수화 된 테스트 호출마다 단일 인수를 제공하는데만 사용 가능하다.
- short, byte, int, long, float, double, char, boolean 유형의 리텉럴 값 지원.
@ValueSource
안에 원하는 타입을 적어준 뒤 리터럴 값을 넣어주면 된다.
- 2)
Null and Empty Sources
@NullSource
@EmptySource
를 사용하면 파라미터 값으로 null과 empty를 넣어준다.
- 3)
@EnumSource
- 4)
@MethodSource
- 보다 복잡한 인수를 제공하는 방법으로
@MethodSource
를 사용한다.
@ParameterizedTest(name = "{index}: {3}")
@MethodSource("invalidParameters")
@DisplayName("User 생성 유효성 검사")
void invalidCreate(String name, String email, String password, String message, String exceptionMessage) {
IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> new User(email, name, password));
assertThat(e.getMessage()).isEqualTo(exceptionMessage);
}
static Stream<Arguments> invalidParameters() throws Throwable {
return Stream.of(
Arguments.of("a", VALID_EMAIL, VALID_PASSWORD, "name 2자 미만", NAME_NOT_MATCH_MESSAGE),
Arguments.of("qwertasdfzpl", VALID_EMAIL, VALID_PASSWORD, "name 10자 초과", NAME_NOT_MATCH_MESSAGE),
Arguments.of("1232ad", VALID_EMAIL, VALID_PASSWORD, "name 숫자 포함", NAME_NOT_MATCH_MESSAGE),
Arguments.of(VALID_NAME, VALID_EMAIL, "Passw0rd", "password 특수문자 제외", PASSWORD_NOT_MATCH_MESSAGE),
Arguments.of(VALID_NAME, VALID_EMAIL, "P@ssword", "password 숫자 제외", PASSWORD_NOT_MATCH_MESSAGE),
Arguments.of(VALID_NAME, VALID_EMAIL, "p@ssw0rd", "password 대문자 제외", PASSWORD_NOT_MATCH_MESSAGE),
Arguments.of(VALID_NAME, VALID_EMAIL, "P@SSW0RD", "password 소문자 제외", PASSWORD_NOT_MATCH_MESSAGE),
Arguments.of(VALID_NAME, "asdfasd", VALID_PASSWORD, "email 양식 틀림", EMAIL_NOT_MATCH_MESSAGE)
);
}