reflection API 를 이용하여 매개변수로 넘어온 클래스의 종류 및 메소드 목록을 출력하는 메소드를 작성하시오.

public void printClass(Object object) {
	System.out.println(object.class.getName());
	System.out.println(object.class.getMethods());
}

XML 파서인 SAX 와 DOM 파서의 특징 및 장단점을 서술하시오.

SAXDOM
순차적 처리한번에 다 읽어온다
메모리 부담이 더적음-
-노드를 추가,수정,삭제 하기 쉬운 구조
손이 많이감-
크기가 커질수록 상대적으로 속도가 빠름-

JMX 에 대하여 서술하시오.

  • 모니터링 API
  • Java Management Extensions 의 약자로 자바 기반의 모든 애플리케이션을 모니터링 하며 JDK 5.0 부터 지원

JMX는 4단계 레벨로 이루어진다

  • instrumentation level
  • agent level
  • distributed servide level
  • addtional management protocol APIs

JMX를 모니터링할 수 있는 도구를 3개 이상 나열하고 링크도 포함시키시오.

Web access log 의 패턴을 확인해 보고, 각 패턴에 대하여 서술하시오.

https://httpd.apache.org/docs/2.4/logs.html 를 참고하면 자세히 나온다

예시

127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326

포맷

LogFormat "%h %l %u %t \"%r\" %>s %b" common
CustomLog logs/access_log common
  • 127.0.0.1 (%h)
    • 서버에 요청을 한 클라이언트(원격 호스트)의 IP 주소
  • -(%l)
    • 출력에서 “빼기기호”는 요청한 정보가 없음을 나타냄
  • frank (%u)
    • 이는 HTTP 인증으로 알아낸 문서를 요청한 사용자의 userid
  • [10/Oct/2000:13:55:36 -0700] (%t)
    • 서버가 요청처리를 마친 시간
  • “GET /apache_pb.gif HTTP/1.0” ("%r")
    • 클라이언트의 요청줄
  • 200 (%>s)
    • 이는 서버가 클라이언트에게 보내는 상태코드
  • 2326 (%b)
    • 마지막 항목은 응답 헤더를 제외하고 클라이언트에게 보내는 내용의 크기를 나타냄

자바 GC 종류를 모두 나열 하시오.

GC - Garbage Collection 의 약자이다.

  • 기본적으로 메모리할당
  • 사용중인 메모리 인식
  • 사용하지 않는 메모리 인식

역할을 담당한다.

GC의 종류는 크게 두가지 타입으로 나뉘며 마이너GC, 메이저GC라고한다

  • 마이너GC = Young 영역에서 발생하는 GC
  • 메이저GC = Old 영역이나 Perm 영역에서 발생하는 GC

GC 상황을 모니터링할 수 있는 도구를 3개 이상 나열하고, 링크도 포함시키시오.

다음과 같이 resultCode, resultMessage, siteId JSON 스트링이 있고, 해당 JSON 스트링 값을 ConstraintData DTO 클래스의 프로퍼티에 bind 시키기 위해 다음과 같은 코드를 사용했다.

String body = {\"resultCode\":1000,\"resultMessage\":\"test\",\"siteId\":1111}


@Data

public class ConstraintData {

private int siteId;

}


ObjectMapper mapper = new ObjectMapper();

ConstraintData data = mapper.readValue(body, ConstraintData.class);


허나, 이와 같이 코드를 작성하게 된다면 다음과 같은 예외를 접하게 될 것이다.

org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field "resultCode" (Class com.test.dto.base.ConstraintData), not marked as ignorable


이유는 JSON 스트링에는 resultCode, resultMessage name이 존재하지만 ConstraintData 클래스에는 해당 멤버 변수가 존재하지 않기 때문이다.

예외 내용을 살펴보면 "알려지지 않은 resultCode" 라고 표시된다.


전달해 주는 JSON 스트링 값을 모두 다 받아서 처리해야 한다는 규칙이 없다면 내가 필요한 값만 ConstraintData 클래스에 정의하여 바인드 시키면 될 것이다.

그러기 위해선 다음과 같이 두 가지 방법을 사용하면 된다. 


첫 번째는 ObjectMapper 클래스를 초기화 한 후 다음의 설정을 추가

mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);


두 번째는 ConstraintData 클래스에 다음의 annotation을 추가

@JsonIgnoreProperties(ignoreUnknown = true)





출처 : 탁구치는 개발자 - http://lng1982.tistory.com/180   


spring-boot 관련글은 아래 url을 참고하세요
https://hyeonguj.github.io/2020/01/15/Spring-주기적으로-코드-실행하기-Schedule/

블로그 자료를 이전하고 있습니다.
https://hyeonguj.github.io/


자바 성능 모니터링 도구

  • profiling
  • APM
    • scouter (LG) : 제니퍼와 유사함 , 리얼타임
    • pinpointer (Naver) : 사후 분석

서버의 성능은 ? - TPS

서버의 성능을 늘리려면 ? -> 서버를 늘린다!  단, 병목을 잡아줘야함 병목현상 -> 주로 CPU에서 발생함.

잘 설계된 서비스라면 DB에서 부하가 일어남

BCI - Byte Code Instrument

함수 전/후에 원하는 코드를 삽입하여 실행 시킬 수 있다 앞뒤로 성능 측정하는 코드를 넣고, 상태를 확인하는 식으로 구현한다. 프로파일 같은 경우 JVM을 변경하여 사용하기도 한다.


로그 - log

로그파일 (log file)

피크때는 몇기가씩 쌓이기도함. 서비스에 따라서 10분단위 / 시간단위 / 날짜단위로 파일을 저장한다 로그가 쌓이면 -> 디스크 풀 -> 망함!! (터미널도 접속못함) 디스크를 수시로 확인해야 한다 -> du / df 명령어를 통해서 디스크를 확인 로그는 요청이 나갈때 찍힌다

메모리/cpu등 어떤 문제가 생긴다!! -> L4 끊고 로그를 확인해봐야됨. ( 요청이 나갈때 처리되기 때문 ) 도저히 못찾으면? -> 밤마다 껏다키는 스크립트라도??

성능측정 - (각종 지표)

일반적인 데이터는 평균이나 중간값이나 비슷함. WAS응답속도의 경우 중간값이 더 크다.

분산, 표준편차가 크다 -> 응답 속도가 들쭉날쭉하다 95th, 99th  -> 95%, 99%의 사용자는 어떤 성능을 보장한다는 의미로 사용함



Abstract ? Interfae?

Abstract (extends) 확장!

중간역할. 비슷한 코드가 여러번 나올때 그것을 모아서 구현해 놓고 사용

Interface (implements) 구현!

다중상속 불가능 함수를 반드시 구현해야한다. 내부를 모르더라도 그 기능을 하는 함수를 밖에서 사용 할 수 있게 . (User Interface처럼)

  
Abstract classInterface
1) Abstract class can have abstract and non-abstract methods.Interface can have only abstract methods. Since Java 8, it can have default and static methods also.
2) Abstract class doesn’t support multiple inheritance.Interface supports multiple inheritance.
3) Abstract class can have final, non-final, static and non-static variables.Interface has only static and final variables.
4) Abstract class can provide the implementation of interface.Interface can’t provide the implementation of abstract class.
5) The abstract keyword is used to declare abstract class.The interface keyword is used to declare interface.
6) Example:
public abstract class Shape{
    public abstract void draw();
}
Example:
public interface Drawable{
   void draw();
}

Static

static 으로 선언된 변수는 Class에 하나다, WAS에 올릴 경우 이 데이터는 계속 쌓이게 되고 메모리 릭의 원인이 된다 - 조심해서쓰자

static block - 값을 초기화 할때 ( Map 같이 초기화할때 new 하고나서 값넣기 등 더해야 하는 경우)

public class StaticImportSample
{
    static String rookie;
    static Map\\<String,String\\> map
    static {
        rookie = "test"
        map = new HashMap\\<String,String\\>();
        map.put("key", "value");
    }
}

Lambda

오라클 - **lambda Quick Start - **http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/Lambda-QuickStart/index.html#section1

 package java.awt.event;
 import java.util.EventListener;

 public interface ActionListener extends EventListener {
    public void actionPerformed(ActionEvent e){
        system.out.println(e.getSources());
    {
}

인터페이스안에 메소드가 하나밖에 없는경우! 이렇게 Lambda 쓸 수 있음.

ActionListioner listener2 = e-\\>  System.out.println(e.getSourcesI();
ActionListioner listener2 = e-\\> {
    //여러줄 가능
    System.out.println(e.getSourcesI());
};

Stream

public class Sample { public static void main(String[] argv) { List multiplesOf3 = new ArrayList<>(); for (long loop = 1; loop < 10; loop++) { multiplesOf3.add(loop); }

	multiplesOf3.stream()
	.filter(i-\> i%3==0 && i!=0)
	.forEach(System.out::println);		
} }

stream 으로 각종 다양한 일을 할 수 있다 .filter() .sort() .map() …

자세한건 여기,,,. http://www.oracle.com/technetwork/articles/java/ma14-java-se-8-streams-2177646.html

GC - garbage

http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html

필요없는것은 지워줘야함.

과정

  1. Marking
  2. Normal Deletion 2a : Deletion with Compacting

Generation Garbage Collection Process

hotspot Heap Structure

1. 처음에는 무조건 Eden 으로감 2. Eden 꽉차면 - 필요없는거 지우고 servivor copy - 한쪽으로만 모음 3. Eden 꽉차면 -\> 옮기고\, servivor 은 count 가 1씩 증가됨 .. 6, 늙은 애들(count > threshold) Tenured로 이동한다

young 쪽이 빠름! old쪽이 느림! young 쪽에서 쓸모없는거 지우고, 오래 살아 남은건 오래 살아남을 가능성이 높으니 더 보관하다

OOP


블로그 자료를 이전하고 있습니다. 많은방문 부탁드립니다 ( https://hyeonguj.github.io/ )

이 블로그의 자료는 이관된 페이지에서도 보실 수 있습니다.

https://hyeonguj.github.io/2017/01/15/JMH로-자바-성능측정-비교하기-java-performance/


java performance

Map, Set, List, Queue 의 차이점

위 4가지는 Java에서 사용 가능한 Collection이고 데이터를 모아서 사용한다는 점에서 비슷하지만 차이가 있다.

Map

Map<K,V> key 와 value 쌍으로 이루어져 있어서 두개의 값을 mapping 하는 용도로 많이 사용한다. 쉽게 생각하면 database에 attribute 가 1개밖에 없는 table이다. key 값을 넣으면 value 를 리턴해 준다. 개인적으로 구현체로는 주로 HashMap을 쓰며 , Map<String,String> = new HashMap<>() 처럼 사용

Set,List,Queue

이 3가지를 분리 한 이유는, 이 모두 Map과 다르게 쌍이 아닌 데이터 자체를 모으기 때문이다. 하지만 세부적인 기능은 다르다.

  • List
    • 순서가 있는 데이터를 저장 할 때 사용
    • 같은 값의 데이터가 들어와도 다른 index에 저장이 된다.
    • ArrayList, LinkedList..
  • Set
    • ‘집합’ 이라는 뜻을 가짐
    • 중복을 허용하지 않는다, 데이터를 넣을때 이미 같은 값을 저장하고 있는 경우 저장하지 않음
    • 출력 할때도 어떤 순서로 출력될지 모른다!
  • Queue
    • FIFO 이다 (Stack - FILO)
    • add, poll 같은 함수를 사용한다
    • https://docs.oracle.com/javase/8/docs/api/java/util/Queue.html

참고 - java 에서 제공하는 라이브러리로 자세한 사항은 여기서 확인 할 수 있다


JMH 를 사용하여 Java SE 에 있는 List 를 구현한 클래스들의 추가/조회/삭제 기능의 성능을 비교하시오.

옵션

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)

추가

	@Benchmark
	public void addIntoArrayList() {

		List<String> arrayList = new ArrayList<>();
		for (int i = 0; i < 100; i++) {
			arrayList.add("Hello");
		}
	}

	@Benchmark
	public void addIntoLinkedList() {

		List<String> linkedList = new LinkedList<>();
		for (int i = 0; i < 100; i++) {
			linkedList.add("Hello");
		}
	}

조회(랜덤값)

	@Benchmark
	public void searchArrayList(State state) {

		List<String> arrayList = state.arrayListForSearch;
		int number = (int) (arrayList.size()%Math.random());
		for (int i = 0; i < 100; i++) {
			arrayList.get(number);
		}
	}

	@Benchmark
	public void searchLinkedList(State state) {

		List<String> linkedList = state.LinkedListForSearch;
		int number = (int) (linkedList.size()%Math.random());
		for (int i = 0; i < 100; i++) {
			linkedList.get(number);
		}
	}

삭제(랜덤값)

	@Benchmark
	public void deleteFromArrayList(State state) {

		List<String> arrayList = new ArrayList(state.arrayListForSearch) ;
		int number = (int) (Math.random()%arrayList.size());
		for (int i = 0; i < 10; i++) {
			arrayList.remove(number);
		}
	}

	@Benchmark
	public void deleteFromLinkedList(State state) {

		List<String> linkedList =  new ArrayList(state.LinkedListForSearch);
		int number = (int) (Math.random()%linkedList.size());
		for (int i = 0; i < 10; i++) {
			linkedList.remove(number);
		}
	}

결과

list_jhm.PNG

JMH 를 사용하여 Java SE 에 있는 Map을 구현한 클래스들의 추가/조회/삭제 기능의 성능을 비교하시오.

옵션

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)

추가

	@Benchmark
	public void addIntoHashMap() {

		Map<String, String> hashMap = new HashMap<>();
		for (int i = 0; i < 100; i++) {
			hashMap.put(i + "", "Hello");
		}
	}

	@Benchmark
	public void addIntoTreeMap() {

		Map<String, String> treeMap = new TreeMap<>();
		for (int i = 0; i < 100; i++) {
			treeMap.put(i + "", "Hello");
		}
	}

조회(랜덤값)

	@Benchmark
	public void searchHashMap(State state) {

		Map<String, String> hashMap = state.hashMapForSearch;
		int number = (int) (Math.random()%hashMap.size());
		for (int i = 0; i < 100; i++) {
			hashMap.get(number + "");
		}
	}

	@Benchmark
	public void searchTreeMap(State state) {

		Map<String, String> treeMap = new TreeMap<>();
		int number = (int) (Math.random()%treeMap.size());
		for (int i = 0; i < 100; i++) {
			treeMap.get(number + "");
		}
	}

삭제(랜덤값)

	@Benchmark
	public void deleteFromHashMap(State state) {

		Map<String, String> hashMap = new TreeMap(state.hashMapForSearch);
		int number = (int) (Math.random()%hashMap.size());
		for (int i = 0; i < 10; i++) {
			hashMap.remove(number + "");
		}
	}

	@Benchmark
	public void deleteFromTreeMap(State state) {

		Map<String, String> treeMap = new TreeMap(state.TreeMapForSearch);
		 
		for (int i = 0; i < 10; i++) {
			int number = (int) (Math.random()%treeMap.size());
			treeMap.remove(number + "");
			
		}
	}

결과

map_jhm.PNG

JMH관련

기본적인 사용법 (시작하기)

  1. 아래와 같은 명령어를 사용해 maven project를 생성한다
    $ mvn archetype:generate \
           -DinteractiveMode=false \
           -DarchetypeGroupId=org.openjdk.jmh \
           -DarchetypeArtifactId=jmh-java-benchmark-archetype \
           -DgroupId=org.sample \
           -DartifactId=test \
           -Dversion=1.0
    
  2. 성능 측정 코드 작성 (위에 있음)
  3. mvn clean install 로 빌드
  4. java -jar target/benchmarks.jar 로 성능측정

참고 자료

샘플코드 - http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/

Java Performance Tuning Guide - http://java-performance.info/jmh/

Code Tools: jmh - http://openjdk.java.net/projects/code-tools/jmh/

자바성능 튜닝 이야기 - 이상민 지음

작성한 코드

https://github.com/HyeonGuJ/jmh-banchmark


블로그 자료를 이전하고 있습니다. 많은방문 부탁드립니다 ( https://hyeonguj.github.io/ )

이 블로그의 자료는 이관된 페이지에서도 보실 수 있습니다.

https://hyeonguj.github.io/2017/01/23/Maven-Profile-를-통해-설정-관리하기/


property의 필요성


아주 작은 프로젝트, 절대로 변하지 않을 프로젝트라면 상관없다.

하지만 단순히 서비스를 어느서버에서 하느냐, 디비를 어느것을 이용하느냐, 인증을 어디것을 사용하느냐에 따라 코드를 바꿔야 한다.

이런 불상사..

자주 바뀌는 코드, 코드말고 다른 의존성에 의한 설정값 등의 셋팅이 코드에 있다면 어떤 일이 발생할까?

OAuth 인증 부분

다음은 payco, facebook 을 통한 Oauth 인증 설정 코드 중 일부이다

	private static final String FACEBOOK_LOGIN_PAGE_URL = "https://www.facebook.com";
	private static final String FACEBOOK_LOGIN_PAGE_PATH = "/v2.8/dialog/oauth";
	private static final String FACEBOOK_CLIENT_ID = "
	private static final String FACEBOOK_CLIENT_SECRET_KEY = 
	
	private static final String FACEBOOK_REDIRECT_URL = "http://dev-service.domain.com/facebookAccessToken.do";
//	private static final String FACEBOOK_REDIRECT_URL = "http://localhost:8080/letsparty/facebookAccessToken.do";
	private static final String FACEBOOK_REQUEST_SCOPE = 
	private static final String FACEBOOK_REQUEST_TOKEN_PAGE_URL = "https://graph.facebook.com";
	private static final String FACEBOOK_REQUEST_TOKEN_PAGE_PATH = "/v2.8/oauth/access_token";
	private static final String FACEBOOK_REQUEST_DATA_PAGE_URL = "https://graph.facebook.com";
	private static final String FACEBOOK_REQUEST_DATA_PAGE_PATH = "/me";
	private static final String FACEBOOK_REQUEST_DATA_FIELD = "id,name,email,picture";

	private static final String PAYCO_LOGIN_PAGE_URL =
	private static final String PAYCO_LOGIN_PAGE_PATH = 
	private static final String PAYCO_CLIENT_ID = 
	private static final String PAYCO_CLIENT_SECRET_KEY = "
	
	private static final String PAYCO_REDIRECT_URL 
	private static final String PAYCO_RESPONSE_TYPE =
	private static final String PAYCO_SERVICE_PROVIDER_CODE 
	private static final String PAYCO_USER_LOCALE = 
	private static final String PAYCO_REQUEST_TOKEN_PAGE_URL = ";
	private static final String PAYCO_REQUEST_TOKEN_PAGE_PATH =
	private static final String PAYCO_GRANT_TYPE = 
	private static final String PAYCO_REQUEST_DATA_PAGE_URL = 
	private static final String PAYCO_REQUEST_DATA_PAGE_PATH =
    //....

약 15개 정도 되는 정보들이 박혀있는것을 볼 수 있다.

특히

    private static final String FACEBOOK_REDIRECT_URL = "http://dev-service.domain.com/facebookAccessToken.do";
//  private static final String FACEBOOK_REDIRECT_URL = "http://localhost:8080/letsparty/facebookAccessToken.do";

를 보면 dev 서버에 올려서 테스트 하는 경우와 local 에서 테스트 할 경우에 redirect 주소를 다르게 넘겨주어야 하고, 빈번히 바뀌는 부분이기 때문에 주석처리를 해서 사용한것을 볼 수 있다.. 수작업…ㄱ-)

DB

데이터 베이스 부분도 마찬가지이다

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql:ServerAddress:13306/letsparty?characterEncoding=utf8"/>
        <property name="username" value="id"/>
        <property name="password" value="pass"/>
    </bean>    

driverClassName은 그렇다 쳐도, url의 경우는 local,dev,real,test 와같은 상황에 따라 바뀐다. 따라서 상황에 따라 수동으로 입력 하는것은 매우 번거러운 일이다.

Profile

우리TF는 local,dev,real 상황에 따라 바뀌는 설정들을 미리 정해놓고, 컴파일 타임에 선택하려 한다. 후보는

  • maven profile
  • spring profile

Spring

spring profile의 특징은 런타임에 profile을 바인딩하여 설정 할 수 있는 것이다.

@Configuration
public class AppConfig {

	@Profile("dev")
	@Bean
	public CacheManager cacheManager() {	//...	}

	@Profile("live")
	@Bean
	public EhCacheManagerFactoryBean ehCacheCacheManager(){  	//...	}

	@Profile("testdb")
	@Bean
	public DataSource dataSource() {	//...	}
}

@Profile 을 통해 적용되는 코드를 나누고,

web.xml에서 다음과 같은 설정을 통해 profile을 설정한다.

<context-param>
    <param-name>spring.profiles.active</param-name>
    <param-value>live</param-value>
</context-param>
<context-param>
    <param-name>spring.profiles.active</param-name>
    <param-value>dev, testdb</param-value>
</context-param>

Maven

maven 을 통한 profile 관리는 compile 시점에 mvn -P[profileName] clean package 명령어를 통해 어떤 자원을 사용할지 결정한다.

  1. profile 설정파일들이 들어갈 폴더구조를 만든다. 

  2. profile에 해당하는 resource 폴더구조를 적어준다

pom.xml

<project>

	<profiles>
		<profile>
			<id>local</id>
			<activation>
				<activeByDefault>true</activeByDefault>
			</activation>
			<properties>
				<environment>local</environment>
			</properties>
		</profile>
		<profile>
			<id>real</id>
			<properties>
				<environment>real</environment>
			</properties>
		</profile>	        
		<profile>
			<id>dev</id>
			<properties>
				<environment>dev</environment>
			</properties>
		</profile>
	</profiles>
    
    <build>
		<resources>
			<resource>
				<directory>src/main/resources/${environment}</directory>
			</resource>
		</resources> 
	</build>

</project>

사용

  1. property로 뺄 부분을 별도의 파일로 만든다
  2. 그리고 위 사항을 .java 나 .xml 에서 사용한다.
  • 변수에서 사용
clientId = ....
clientSecret = ....
paycoHost = ....
authURI = ....
accessURI = ....
profileHost = ...
profileURI = ....
redirectURI = ....
@Value("#{payco['paycoHost']}")
String PAYCO_LOGIN_PAGE_URL;	

@Value("#{payco['clientId']}")	
String PAYCO_CLIENT_ID;
  • xml 에서 사용

mysql.properties

jdbc.driver = com.mysql.jdbc.Driver		
jdbc.url = jdbc:mysql://localhost:3306/guestbook		
jdbc.username = root		
jdbc.password = gusrn 

<bean
    class= "org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" >
    <property name= "locations">
    <value>classpath:/mysql.properties</value >
    </property>
</bean>

<bean id="dataSource"
    class= "org.springframework.jdbc.datasource.SimpleDriverDataSource" >
    <property name= "driverClass" value="${jdbc.driverClass}" />
    <property name= "url" value ="${jdbc.url}" />
    <property name= "username" value="${jdbc.username}" />
    <property name= "password" value="${jdbc.password}" />
</bean>

진행상황

OAuth 인증, Database, Object Storage 등 외부 의존도가 잇거나, 용도에 따라 달라지는 환경을 분리하는 작업을 진행중이다

  • [v] OAuth 인증
  • [v] Database
  • [v] Object Storage


블로그 자료를 이전하고 있습니다. 많은방문 부탁드립니다 ( https://hyeonguj.github.io/ )

이 블로그의 자료는 이관된 페이지에서도 보실 수 있습니다.

https://hyeonguj.github.io/2019/01/31/spring-unit-test/


Unit test 구현


[Spring MVC 구조에서 역할에 따른 Unit test]

‘lets party’ 라는 주제로 기술개발교육 프로잭트를 진행중이다. 이때 Spring MVC 로 개발을 하였고 이에 따른 테스트도 과제로 주어졌다.

‘lets party’는 크게 3개의 레이어로 구성되어 있다.

  • Controller :
    1. 처리해야 할 데이터를 브라우저에게 받는다.
    2. 담당할 service를 선택하여 호출한다
    3. 처리한 데이터를 다음 페이지에서 볼 수 있게 셋팅한다.
    4. 이동할 페이지를 리턴한다
  • Service :
    1. 데이터를 받아 비지니스 로직을 처리한다.
    2. DB의 활용이 필요한 경우 해당 처리를 하는 DAO를 호출한다
  • DAO
    1. db를 활용할 데이터를 받는다
    2. 역할을 하는 mapper를 호출하여 처리하게 한다.

이와 같이 역할이 다르다 보니 테스트 하는 방법도 달라진다. 기본원칙은 ‘분리’ 서로가 서로에게 의존하지 않아서, 다른 객체와는 상관 없이 항상 같은 결과가 나오야 한다. (하면서 배웠지만, 적용이 안된 부분도 많다..)

각각의 테스트 방법이 조금씩 다르지만 기본적으로 하는일은 똑같다. 목적 : input에 대한 output이 잘 나오는지 확인하기! 과정 : 입력값 가정하기 -> 호출하기 -> 검증하기 (호출검증 및 output 검증)

Controller


Controller의 테스트는 크게 2가지를 테스트 하였다

  1. 브라우저에서 “/pageName.do”를 호출 했을때 해당 controller를 호출하는지, 그리고 2xx, 3xx와같은 결과를 받는지
mockMvc.perform(get("/partyDetail.do")
				.param("partyId","1")
				.session(httpSession)
				).andExpect(status().isOk());

위 코드는 “/partyDetail.do” 를 브라우저에서 호출했을때, 해당 controller가 잘 호출 되는지를 확인 한 것이다. get방식으로 전달된 데이터는 param() 으로 받을 수 있고, HttpSession은 .session으로 받는다.

  1. Controller에서 재대로 된 Service를 호출 하는지

다른 함수가 잘 호출 되었는지는 Mockito의 verify를 활용하여 검사 할 수 있다.

verify(partyService).selectParty(1);

위 함수는 partyService.selectParty() 함수가 1이라는 인자값을 통해 호출되었는지 확인하는 함수이다.

그 외에도 다양한 방법으로 함수호출을 test 할 수 있다.

verify(partyService, times(3)).selectParty(1);

times(3) 을 통해 3번 호출 되었는지 검사한다.

verify(partyService, times(3)).selectParty(anyInt());

anyInt()를 통해서는 ‘1’이라는 인자 외에 int형으로 된 값으로 호출되었는지 테스트 할 수 있다.



Controller 는 Unit Test 가 아닌 통합 테스트로 진행 했습니다.

유닛테스트로 구현 할 경우, 인터페이스의 구현체를 생성자를 통해 생성한 후 호출하여 테스트합니다.

이렇게 할 경우 Spring 에 대한 의존성을 모두 삭제합니다.



Service

service는 비지니스 로직을 처리하는 부분으로 test도 상대적으로 좀 복잡하다. 일반적으로 과정은 다음과 같다

@Test
public void joinPartyFirstJoinTest() {
	when(partyDAO.getNumOfUserParty(Mockito.any(UserPartyVO.class))).thenReturn(0);	
	when(partyDAO.checkJoin(Mockito.any(UserPartyVO.class))).thenReturn(0);

	partyService.joinParty(userPartyVO);

	verify(partyDAO).getNumOfUserParty(Mockito.any(UserPartyVO.class));
	verify(partyDAO).insertUserParty(Mockito.any(UserPartyVO.class));
	verify(partyDAO).increasePublicParticipantsCount(anyInt());
}

when().thenReturn() 을 통해 비지니스 로직에서 쓰이는 의존성 있는 함수들의 값들을 미리 세팅한다. 이후 함수를 호출하고 (partyService.joinParty(userPartyVO)) 다른 함수들을 잘 호출 했는지, 기능을 잘수행했는지 검증한다.

DAO

DAO는 데이터를 가지고 오는것을 목표로 하고있다. 이후 교육과정에서 Unit test라기보다는 integration test라고 부르는것을 들었다. DB에 의존성이 있고, 이를 잘 수행하여 반영이 되는지를 테스트 하기 때문이다.

letsparty에서는 DAO는 한번에 하나의 쿼리만 처리하게 되어 있기 때문에 간단하게 테스트 한다.

단, test 전 후에 DB의 상태가 같아야 하기 때문에 rollback 이 필요한데, Spring 에서 제공하는 rollback은 mybatis와 함깨 사용 할 수 없다. (따라서 수동으로…)

@Test
public void insertPartyTest() throws Exception {
	int sizeOfBeforeInsert = partyDAO.selectAll().size();

	partyDAO.insert(partyVO);
	int insertedPId = partyVO.getPartyId();		
	int sizeOfAfterInsert =  partyDAO.selectAll().size();

	assertEquals("insertTest - inserted party id  : "+insertedPId , sizeOfBeforeInsert+1, sizeOfAfterInsert);

	//clean database
	partyDAO.deletPartyeById(insertedPId);
}

insert를 하는 DAO를 테스트하는 코드이다. partyDAO.selectAll(), partyDAO.deletPartyeById() 도 함깨 사용하는데, partyDAO.selectAll() 를 사용하여 insert 전 후에 크기를 비교하여 1개가 잘 들어갔는지 확인한다. 이후 partyDAO.deletPartyeById()를 통해 rollback 한다.

이 함수들도 모두 test를 해야한다. selectAll()함수가 잘 동작하지 않는다면 insert는 재대로 test된것이 아니기 때문이다. 함깨 테스트를 해도 되는것인지, 그래도 각각을 분리해서 테스트를 해야하는것인지는 잘 모르겠다.



DAO 는 Unit Test 가 아닌 통합 테스트로 진행 했습니다.

DAO특성상 DB와 함깨 연동 하는것을 테스트 하였습니다.


느낀점

test코드를 작성하다보니 기존 코드의 단점이나 수정해야할 부분도 함깨 보이기 시작했다. test작성만 하는것이 아니라 리팩토링까지 함깨 하다보니 시간이 꽤 걸렸다

목표로한 coverage가 올라가는것을 보니 기분이 좋았다.

coverage.PNG


토비의 스프링 요약 3장 - 템플릿 (2)

[ 3.4 컨텍스트와 DI ]

  • 예제에서 jdbc를 활용하고 예외를 처리하는 부분은 공통적으로 사용 할 수 있음
  • 위 context에 해당하는 부분을 따로 독립 시켜 다른곳에도 활용해 보자.
  • 분리와 동시에 전략을 DI받는 형태로 바뀜
  • 스프링 Bean설정은 오브젝트 레벨의 의존관계에 따라 정의됨

-> text-applicationContext.xml 수정.

<bean id = "userDao" ...>
	<property name = "dataSource" ref="dataSource"/>
	<property name = "jdbcContext" ref="jdbcContext"/>
</bean>

<bean id= jdbcContext" class="..."></bean>

지금까지와는 다른 DI

  • 지금까지와는 다르게 인터페이스를 사용하지않고 DI를 적용함
  • 기존 : 인터페이스를 통해 직접 클래스에 접근하지 않고, 설정을 변경하여 사용

스프링 빈으로 DI

  • DI 란?
    • 인터페이스를 사이에 둬서 클래스 레벵레서 고정되지않게
    • 런타임에 의존할 오브젝트를 동적으로 결정.
  • 스프링의 DI는?
    • 객체의 생성과 관계, 제어권한을 오브젝터에서 제거하고 외부에서 위임
    • IoC라는 개넘을 충실히 수행
    • 인터페이스를 사용하지 않음 - 강하게 연결되어있음
  • 왜쓰는가? (언제 쓰면 좋은가?)
    • 싱글톤으로 관리되어 여러 오브젝트에서 공유해서 사용
    • 다른 bean 에 의존하고 있어서 IoC의 대상으로서 빈에 등록되어야함
    • 단, 이런 구성은 최후에 고려하자.

수동으로 DI

  • 싱글톤 포기 -> DAO마다 생성
  • 자동생성,초기화 -> 생성과 초기화를 직접 담당한다
  • DataSource에서 DI받는것을 못함 -> 다이나믹하게 주입

[ 3.5 템플릿과 콜백 ]

  • 템플릿/콜백 패턴
    • 전락 패턴에서 익명 내부 클래스를 활용한 방식
    • 전략 패턴의 context : 템플릿, 익명 내부 클래스로 만들어지는 오브젝트를 콜백이라고 부름.
  • 코드를 재사용하는 목적이 강함!

특징

  • 보통 단일 메소드 인터페이스를 사용함
    • 특정기능을 위해 한번 호출되는 경우가 많음
    • 하나의 메소드를 가진 익명 내부 클래스로 구현됨
  • 보통 파라미터가 있음
    • 컨텍스트 정보를 전달받을 때 사용됨
  • 절차 : 그림 3-7 참조
    • 클라이언트에서 CallBack을 생성하여 DI ( 템플릿에게 줌)
    • 템플릿은 CallBack 기능 수행 전에 해야할 고정된 코드 수행.
    • CallBack함수 호출되고, Client 쪽에서 받은 Callback이 수행된다.
    • 작업결과를 템플릿이 받고 수행 후에 하는 고정된 코드를 수행한다.
    • 결과를 Client에게 전달한다.
  • 하지만.. 익명 내부 클래스를 사용하기 때문에 코드 작성불편, 가독성이 떨어짐.

콜백의 분리와 재활용

#


토비의 스프링 요약 3장 - 템플릿 (1)

  • 개방 폐쇠의 원칙 OCP : 확장에는 열려있고, 변경에는 닫혀있다.
  • 템플릿
    • 변경이 거의 일어나지 않으며,
    • 일정 패턴으로 유지되는 특성을 가진 부분 -> 자유롭게 변형되는 성질을 가진 부분으로 부터 독립

[ 3.1 다시 보는 초난감 DAO ]

예외처리 기능을 갖춘 DAO

  • jdbc에서 예외가 발생하면 잡아줘야됨 : 리소스반환 필수!
  • try-catch를 이용해 예외처리.-finally 를 통해 리소스반환을 한다.

[ 3.2 변하는 것과 변하지 않는 것 ]

JDBC try/catch/finally 를 그냥 사용할 경우 문제점

  • 중첩구조로 되어있어 매우 답답함
  • 중복된 코드
  • 실수 가능성

템플릿 메소드를 활용한 분리

  • 변하지 않는 부분은 남겨두고, 변하는 부분을 따로 빼서 Abstract method로 만듬.
  • 이 클래스를 상속받아 자식에서 Abstract method를 구현함.
  • 바뀌는 부분만 자식에서 갈아끼워가며 원하는 형태로 구현가능!
  • 단, 상속을 통해서만 가능하다는점..-> 다양한 제약사항

전략 패턴

  • 순서
    • 변하지 않는 맥락 context
    • preparedStatement를 만들어 줄 외부 기능 호출
    • 받아서 실행
    • 예외는 밖으로던지고,
    • 끝나면 적절히 닫아줌.
  • preparedStatement가 고정되어 있으면 OCP라고 할 수 없음

DI적용을 위한 클라이언트/컨텍스트 분리

  • 클라이언트가 전략을 정하고,
  • context에서는 그 전략을 받아서 실행만!

public void deleteAll() thorws SQLException{
	StatementStrategy st - new DeleteAllStatement(); //전략 설정
    jdbcContextWithStatementtStrategy(st);			// context에 DI넣어서 실행
}

[ 3.3 JDBC 전략 패턴의 최적화 ]

추가정보

  • delete와 달리 add 같은거는 어떤걸 추가할지에 대한 추가정보가 더필요함.
  • 그럴때는 입력 받아서 field로 만들어주면됨

public class AddStatement implements StatementStrategy{
	User user;
    
    public AddStatement(User user){
    	this,user = user;
    }
}
//....

로컬 클래스

  • 위와같이 작성하면 delete, add와 같이 DAO접근하는 전략하나마다 class가 생기게 된다
  • 어차피 UserDAO 밖에 사용하지 않으니 내부 클래스로 만들어준다

  • 자바에서 로컬 변수처럼 클래스를 선언 하는 방법임
  • 외부 파라메터는 final로 선언하는것이 좋음 : 외부변수를 받아 그대로사용, 변조 안함.
public void add(final User user) thorws SQLException{
	public class AddStatement implements StatementStrategy{
	//User user;
    /*
   		public AddStatement(User user){
    		this.user = user;
    	}
    */  
        public PreparedStatement makePreparedStatement(Connection c) {
        	//...
        	return ps;
        }
    }
    
    StatementStrategy st = new AddStatement(user);
    jdbcContextWithStatementStrategy(st);       
}

익명 내부 클래스

  • AddStatement()의 경우 add()를 위해 생성된 클래스로 이미 사용처가 정해짐 -> 이름 필요 없음.
  • delete도 마찬가지..
pulbic void deleteAll() throws SQLException{
	jdbcContextWithStatementStrategy(
    	new StatementStarategy(){
        	public PreparedStatement makePreparedStatement(Connection c) throws SQLException{
            	return c.prepareStatement("delete from users");
            }
        }    	
    )
}

[ 3.4 컨텍스트와 DI ]

...

토비의 스프링 요약 (ch2 -테스트)(2)

Autowired : xml에 등록한 Bean과 같은 타입을 찾아서 DI해준다.

절대바뀌지 않는 상황에도 인터페이스를 활용한 DI를 적용해야할까?

  • YES!!
    • 절대로 바뀌지 않는것은 없다
    • 다른 차원의 서비스를 도입 할 수 있다 - (예: 부가기능으로 count를 한다던지..)
    • 효율적인 테스트를 위해서

테스트 코드에 의한 DI

  • 테스트에서, 실제로 사용하는 DB를 사용? -> 위험
  • 테스트 코드 내에서 직접 DI하기 \
  • @DritiesContext : 해당 테스트에서 에플리케이션 컨텍스트 상태를 변경하겠다 (예 : db변경)

테스트를 위한 별도 DI설정

  • 수동으로하면 번거롭다
  • test-applicationContext.xml 생성!
<!-- url 부분에 test시에 사용할 DB로 바꿈! -->
<bean id="dataSoource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
	<property name="driverClass" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost/testdb" />
    <property name="username" value="user" />
    <property name="password" value="pass" />
</bean>
  • test시에 @ContextConfiguration에 있는 lotation 을 test용으로바꿈,

컨테이너 없이 DI테스트

  • 목적이 분명하다면 해당 Object를 직접 생성하여 만든다
  • API에 의존하지 않고 관심에만 집중하여 깔끔하게 만든다.
  • API에 종속되지 않고 비 침투적 기술로, 스프링에 의존하지않고도 DI가능

어떤것이 좋은가??

  • 일단 컨테이너 없이 테스트 하는것을고려
  • 복잡하다면 Spring 설정을 사용
  • 영향을 주지 않게 하려면 테스트 설정을 따로 만든다.

학습테스트 - learning test

  • 다른사람이 만든 코드와 기능에 대한테스트
  • 프레임워크나 라이브러리에 대해서도 테스트

학습 테스트의 장점

  • 해당 코드의 기능을 다양한조건을 활용해서 쉽게 확인할 수 있음
  • 테스트 코드를 참고하여 개발에 활용 할 수 있음.
  • 업데이트할때 호환성 검증
  • 테스트 코드 작성 훈련
  • 새로운 기술을 배울대 즐거워짐…??? (오래 기억에 남는다)

버그테스트

  • 요류를 알려줄 수 있는 테스트
  • 버그를 알았을 경우, 그 버그로 인해 실패할만한 케이스를 만들고 성공하도록 코드수정
  • 장점
    • 테스트의 완성도가 높아짐
    • 버그의 내용을 명확하게 분석 할 수 있다.
    • 기술적인 문제를 해결하는데 도움이 됨.

정리

  • 테스트는 자동화, 빠르게실행가능해야함 -> JUnit사용
  • 일관성 보장, 순서에따라 달라지며안됨
  • 테스트하기 좋은코드가 좋은코드
  • 테스트를 먼저 만들고 이를 성공으로바꿔가는 TDD방법
  • @Before, @After와같은 작업으로 공통코드를 줄일 수 있음
  • 스프링 테스트 컨텍스트 프레임워크를 사용하여 성능 향상 가능
  • @Autowired를 사용하여 오브젝트에 DI 할 수 있음
  • 오류에 대비해 버그테스트를 만들어 놓으면 좋음
  • 기술의 사용방법을 익히고 이해를 돕기위해 학습 테스트를 작성하자.


+ Recent posts