Program/One Day Project

#2 API서버에 테스트 자동화 적용하기

Hue Kim 2017. 2. 11. 21:12


#1 보러가기


어제 했던 OneDay 프로젝트 결과

- 사실 Test-driven development TDD)는 아니다. 따로 설명할 단어가 생각나지 안나서 TDD적용이라고 붙였을뿐.  
TDD는 아니고 어울리는 적당한 단어를 못찾았는데
테스트 자동화라고 페북에서 알려주셔서 수정합니다.

 

 OneDay Project 하게된 이유

1. API Test 자동화를 하고 싶다. 

2. Local, Dev, Stage, Product 를 전부다 테스트 하고 싶다.

3. 테스트 결과를 Slack 으로 받아보고 싶다.


Mock을 통한 의존성이 제거된 테스트는 Product에서 발생하는 것과 동일하다고 판단할 수 없다.

실제 Work-Flow는 unit테스트로는 테스트 하기 불가능한 점들이 있다.

내가 하는 서비스가 RealTime Sync를 지원한다(심지어 동기화하는 데이터도 다 같지 않다, user별로 다른 데이터가 나뉠수도 있다)

그리고 결제가 들어간다. 심지어 결제도 Sync와 이중결제의 위험성도 가지고 있다. 


점심먹고 난후 13:00~18:00시까지 5시간이라는 제한 시간

 - 사실 점심을 늦게 먹는 바람에 13:30분부터 했습니다 


짧은 시간이라 효율적으로 테스트 할 수 있는 방법을 생각했습니다.

1. Test 해야하는 API 갯수는? - 대략 160개

2. 시간내에 다 할 수 있는가? - No!

3. 그럼 Toy Test Project의 목표는?  - JUnit Test Case 간의 Session 유지, JUnit Test Suite 를 통한 테스트 시나리오 순서대로 테스트


심플하게 Test Project 만 만들고 싶었기 때문에 새로 Maven 프로젝트 만들고.

- TDD는 아니고 뭔가 거창한건 부담스러워서 Test+Toy 프로젝트라는 뜻으로 testoy-user-api 


pom.xml에 필요한 depency들을 추가 해주고    

AWS-SDK의 일부는 AWS제공하는 JSONObject 때문에 넣었습니다. 

GSon이나 다른 JsonLib 보다 Java Collection 객체와 호환이 좋아서 com.amazonaws.util.json.JSONObject 를 주로 씁니다.

Link : Aws JSONObject SampleCode

통신해야되니깐 httpClient도 넣어주고 나중에 Slack Webhook 통해서 자동 Reporting도 해주고 싶으니깐 slack-webhook 도 추가.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!-- Test -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.7</version>
</dependency>
 
<!-- AWS-SDK-SNS -->
<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-java-sdk-sns</artifactId>
    <version>1.9.32</version>
</dependency>
 
<!-- http2Client -->
<dependency>
    <groupId>org.eclipse.jetty.http2</groupId>
    <artifactId>http2-client</artifactId>
    <version>9.4.0.M0</version>
</dependency>
 
<!-- slack webhook -->
<dependency>
    <groupId>net.gpedro.integrations.slack</groupId>
    <artifactId>slack-webhook</artifactId>
    <version>${slack.webhook.version}</version>
</dependency>
 
cs

기본 프로젝트는 구성이 됐으니 이젠 뭘 테스트 할지 정리를

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
1. 회원가입
    - A. 아이디 체크
    - B. 문자인증
    - C. 신용카드 등록
 
2. 로그인
    - A.회원가입후 로그인
    - B.아이디/패스워드 로그인
    - C.페이스북 계정 로그인
    - D.자동 로그인
 
3. 개인 주문
    - A. 체크인
    - B. 개인 주문
 
4. 그룹 주문
    - A. 체크인
    - B. 친구초대(ACCEPT/REJECT)
    - B. 그룹 주문
 
5. 주문 컨펌
    - A. CONFIRM/REJECT A/B TEST
 
6. 주문 완료
 
7. 결제
    - A. 성공/실패
    - B. 성공/실패 RESPONSE DATA LOG
    - C. TEST RESULT POST SLACK
cs

오늘 다 할 건 아닙니다. 전체 프로세스를 크게 정리해서 두면 다음 API할때 헤매는 시간을 줄일 수 있기때메 작성.

이것도 프로젝트에 포함. 모든건 처음엔 0.0.1이죠.


테스트 프로젝트기 때문에 테스트할 ServerConstant class를 하나 만들어주고.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.test.user.constant;
 
 
/**
 * 서버 정보
 * @author Hugh Kim
 *
 */
public class ServerConstant {
 
    public static enum serverType {
        DEV,
        STAGE,
        REAL;
    }
 
    public final static serverType SERVER_TYPE  = serverType.DEV;
 
    private final static String USER_DEV_HOST   = "https://dev.apiServer.com:8080/userApi";
    private final static String USER_STAGE_HOST = "https://stage.apiServer.com:8080/userApi";
    private final static String USER_REAL_HOST  = "https://real.apiServer.com:8080/userApi";
 
    private static String Serverhost = USER_DEV_HOST;
 
    /**
     * 서버 타입
     * @return dev, stage, real
     * @author Hugh Kim
     * @date
     * now
     */
    public static String getServer(){
 
        switch (SERVER_TYPE) {
 
            default:
            case DEV:
                Serverhost = USER_DEV_HOST;
                break;
            case STAGE:
                Serverhost = USER_STAGE_HOST;
                break;
            case REAL:
                Serverhost = USER_REAL_HOST;
                break;
 
        }
 
        return Serverhost;
    }
}
 
cs


Login Test Case 를 작성합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.test.user.api;
 
import static org.junit.Assert.assertEquals;
 
import java.io.IOException;
import java.util.HashMap;
 
import org.junit.Test;
 
import com.amazonaws.util.json.JSONException;
import com.amazonaws.util.json.JSONObject;
import com.test.user.constant.APIResult;
import com.test.user.constant.ApiPrameters;
import com.test.user.constant.ServerConstant;
import com.test.util.HttpClientUtil;
 
public class Login extends ApiPrameters{
 
    public static String loginUrl = ServerConstant.getServer() + "/member/login";
 
    public static String userId = "testUser01";
    public static String userPw = "abcd0001!@#$";
 
    @Test
    public void login() throws IOException, JSONException {
 
        HashMap<String, Object> sendMap = new HashMap<String, Object>();
        sendMap.put("MEMBER_ID"  , userId);
        sendMap.put("PWD"        , userPw);
 
        String repsonseJson = HttpClientUtil.post(loginUrl, sendMap);
 
        JSONObject json = new JSONObject(repsonseJson);
        assertEquals(APIResult.OK, json.get("RESULT_CODE"));
        
        ApiPrameters.JESSION_ID     = String.valueOf(json.get("JSESSIONID"));
 
    }
 
}
cs


Test 결과 : 녹색막대. 혹시나 빨간 막대를 보시면 다시 한번 에러를 확인하시고 녹색막대를 꼭 보시기 바랍니다.은근 중독되는 녹색막대

 - 스벅에서 하니 엄청 오래 걸리네요.

Http통신 유틸에 넣어놓은 response result 도 잘 나옵니다. 

Http 통신하는건 코드가 좀 길어서 링크 로 대신합니다.


중간에 만들지 않은 ApiPrameters 클래스가 나오는데 JUnit Test Suite에서 세션key 공유를 위해서 class 간데 transfer Object 용으로 만들어준 클래스 입니다.

1
2
3
4
5
6
7
package com.test.user.constant;
 
public class ApiPrameters {
 
    public static String JESSION_ID;
}
cs

static 변수로 class에서 호출하기 편하게 선언만 해주고 

HttpPost 하는곳에서 Jession 아이디를 넣는 방식으로 post header에 JsessionKey를 넣습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    HttpPost post = new HttpPost(url);
 
    List<NameValuePair> paramList = convertParam(params);
    post.setEntity(new UrlEncodedFormEntity(paramList, encoding));
 
    HttpResponse response = null;
 
    if(null != params.get("JESSION_ID")){
        post.setHeader("Cookie""JSESSIONID="+ params.get("JESSION_ID"));
    }
 
    response =  client.execute(post);
 
    String postResponse = EntityUtils.toString(response.getEntity());
 
    JSONObject json = new JSONObject(postResponse);
    json.put("JSESSIONID", parseSessionID(response));
 
    log.info(url + " response \n " + json.toString());
cs

parseSessionId method는 return SessionId 해주는 private method인데 각자 쓰는 sessionKeyName이 다르니 서버 설정에 맞게 parse하시면 되겠습니다.

이제 Login Test Case는 작성을 끝냈으니 다른 Test Case를 하나더 만들어서 Test Suite돌릴 준비를 합니다.

 - 위에서 목표로 잡았던(3. 그럼 Toy Test Project의 목표는?  - JUnit Test Case 간의 Session 유지, JUnit Test Suite 를 통한 테스트 시나리오 순서대로 테스트) 를 만족시키기 위한!

GetPayGroupInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.test.user.api;
 
import static org.junit.Assert.assertEquals;
 
import java.util.HashMap;
 
import org.junit.Before;
import org.junit.Test;
 
import com.amazonaws.util.json.JSONException;
import com.amazonaws.util.json.JSONObject;
import com.test.user.constant.APIResult;
import com.test.user.constant.ApiPrameters;
import com.test.user.constant.ServerConstant;
import com.test.util.HttpClientUtil;
 
public class GetPayGroupSeq {
 
    private final String getPayGroupSeqUrl = ServerConstant.getServer() + "/paygroup/getPayGroupInfo";
 
    public String JESSION_ID;
    public String PAY_GROUP_SEQ;
 
    @Before
    public void login() throws JSONException{
 
        JESSION_ID    = ApiPrameters.JESSION_ID;
        PAY_GROUP_SEQ = "test110";
 
    }
 
    @Test
    public void getGroupInfo() throws JSONException {
 
        HashMap<String, Object> sendMap = new HashMap<String, Object>();
        sendMap.put("PAY_GROUP_SEQ"    , PAY_GROUP_SEQ);
 
        String repsonseJson = HttpClientUtil.post(getPayGroupSeqUrl, JESSION_ID, sendMap);
        JSONObject json = new JSONObject(repsonseJson);
 
        assertEquals(APIResult.OK, json.get("RESULT_CODE"));
 
    }
 
}
 
cs

@before Annotation 으로 SessionKey 를 주입해주고 파라미터를 선언한 다음 이제 드디어 두개의 TestCase 를 연결해서 순서대로 로그인부터 처리되는지 확인을 해야겠죠?

JUnit Test Suite 를 만들고 이름은 FullUserAPI 로 하겠습니다.

그리고 전에 만들어 놓은 Login, GetPayGroupInfo 를 @SuiteClasses 안에 실행할 순서대로 선언해줍니다.

- @RunWith(Suite.class),@SuiteClasses({ })는 기본적으로 선언되어있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.test.user;
 
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
 
import com.test.user.api.Login;
import com.test.user.api.GetPayGroupInfo;
 
@RunWith(Suite.class)
@SuiteClasses({
    Login.class,
    GetPayGroupInfo.class
})
 
public class FullUserAPI {
 
}
 
cs


Runner 는 FullUserAPI

@SuiteClasses 에 선언된 Login, GetPayGroupInfo 도 순차적으로 실행됨을 볼 수 있습니다.

이제 나머지 API들과 testCase document에 적힌 내용대로 test case 들을 만들고 FullUserAPI등록해주면 될거같습니다.

FullUserAPI에 이부분만 추가 하면 Slack 으로 알림까지 올 수 있겠네요!

1
2
3
4
@After
public void sendSlack(){
    // TODO : Send Slack-WebHook Code
}
cs

(Slack에 알릴 정보는 안 만들...ㅠㅠ)


프로젝트 시간이 5시간걸렸는데 블로그 글 포스팅하는데 4시간이 걸리네요......