1
2
3
4
    static void waitForLoad(WebDriver driver) {
        new WebDriverWait(driver, 30).until((ExpectedCondition<Boolean>) wd ->
                ((JavascriptExecutor) wd).executeScript("return document.readyState").equals("complete"));
    }
cs


Junit을 사용하여 테스트를 진행하고, 결과 중 fail된 case만 따로 다시 수행시켜보는 방법 보다는

fail된 case를 바로 더 돌리도록 해서 그 이후에 결과를 확인하는 것이 더 좋을 때가 있다.


이럴 때는 TestRule을 이용해서 JUnit Retry를 구현하는 방법을 사용한다. 

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
import org.junit.rules.TestRule; 
import org.junit.runner.Description; 
import org.junit.runners.model.Statement; 
 
public class JUnitRetry implements TestRule { 
 
    private int retryCount; 
 
    public JUnitRetry(int retryCount) { 
        this.retryCount = retryCount; 
    } 
 
    @Override
    public Statement apply(Statement base, Description description) { 
        return statement(base, description); 
    } 
 
    private Statement statement(final Statement base, final Description description) { 
        return new Statement() { 
            @Override
            public void evaluate() throws Throwable { 
                Throwable caughtThrowable = null
 
                // implement retry logic here 
                for (int i = 0; i < retryCount; i++) { 
                    try { 
                        base.evaluate(); 
                        return
                    } catch (Throwable t) { 
                        caughtThrowable = t; 
                        System.err.println(description.getDisplayName() + ": run " + (i+1+ " failed"); 
                    } 
                } 
                System.err.println(description.getDisplayName() + ": giving up after " + retryCount + " failures"); 
                throw caughtThrowable; 
            } 
        }; 
    } 
}
cs

위와 같이 JUnitRetry 클래스를 만들어 놓고, 테스트코드에서는 Rule을 사용하여 Retry 를 몇번 할지 횟수만 지정하고 사용하면 된다. 

1
2
@Rule
public JUnitRetry retry = new JUnitRetry(2);
cs


Windows에서 Selenium으로 UI자동화 진행하다보면, chromedriver.exe가 남아있어서 브라우저가 기동이 안될 경우가 있다.

driver.quit()을 꼼꼼히 호출해도 남아있는 경우가 있는데, 

jenkins 에서 pre-build 단계나, test진행 전 단계에 taskkill을 이용하는 것이 좋다.


windows batch command 수행 (jenkins pre-build)

taskkill /f /fi "pid gt 0" /im chromedriver.exe


Java code에서 (BeforeClass)

Runtime.getRuntime().exec("taskkill /f /fi /im chromedriver.exe");


selenide는 driver.close()나 wait() 등을 알아서 진행해주기 때문에 좀 더 스마트한 UI자동화 도구이다.

selenium은 사실 webdriver를 통한 브라우저 제어 도구에 좀 더 가깝다. 


selenide에서 chrome webdriver를 세팅할때, 

  • selenide.browser 
  • webdriver.chrome.driver
위 두 프로퍼티를 설정하면, 알아서 UI브라우저 관리를 해주는데, 모바일 에뮬레이션을 하려고 할때는 아래와 같이 진행하면 된다.

단점은 모바일 에뮬레이션을 하면, 직접 browser(webdriver)를 close()를 해줘야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class SelenideMWChromeTest {
 
    @Before
    public void setUp() {        
        Map<StringString> mobileEmulation = new HashMap<>();
        mobileEmulation.put("deviceName""Nexus 5");
 
        ChromeOptions chromeOptions = new ChromeOptions();
        chromeOptions.setExperimentalOption("mobileEmulation", mobileEmulation);
 
        WebDriver driver = new ChromeDriver(chromeOptions);
 
        WebDriverRunner.setWebDriver(driver);
        Configuration.browser = WebDriverRunner.class.getName();    
    }
 
    @After
    public void tearDown() throws Exception{
         close();
    }
 
    @Test
    public void testStep() throws Exception{
        ... 중략 ...
cs



junit 4.x에서 csv 파일을 읽어서 data driven testing을 구현하는 방법.

junit 5에는 @CsvFileSource 라는게 이미 있지만, junit 4.x 에는 없어서 구현을 해야 한다.

근데, 구글링 해보니, JUnitParams라는 아주 편리한 라이브러리가 있네.


구현 방법도 쉽다.

우선 maven pom.xml에 dependancy 추가하고

<dependency>
    <groupId>pl.pragmatists</groupId>
    <artifactId>JUnitParams</artifactId>
    <version>1.1.1</version>
    <scope>test</scope>
</dependency>
cs


테스트 코드에는 아래와 같이 구현하면 끝.

간단하고 편하네.


1
2
3
4
5
6
7
8
9
10
import junitparams.*
 
@RunWith(JUnitParamsRunner.class)    
public class SamplesOfUsageTest {
 
    @Test
    @FileParameters("classpath:test.csv")
    public void loadParamsFromClasspath(int age, String name) { 
        .. 테스트 코드 작성 ..
    }
cs


csv 파일 말고, 다른 방법으로 하는 링크된 샘플코드에 많이 나와 있으니, 생략한다. 


junit을 활용한 테스트 자동화에 익숙하다보니, api테스트 자동화 한다고 하면 request, response 동작을 위한 util 및 json / xml parser 등을 모두 구현해서 사용했었는데, 

그동안 왜 그랬나 싶을 정도로,, Rest-Assured는 잘 만들어진 api 테스트 자동화 도구이다. 

링크 : http://rest-assured.io/


환경 구성이나, 손 쉬운 사용법은 위 링크의 User Guide 문서를 참고하면 사용할 수 있다.

그래도 자동화 하면, 쉬운 반복 수행 및 결과 리포팅이 중요하니, 아래와 같이 구성하자.


  • 빌드 도구 : maven (or gradle)
  • 테스트 도구 : junit + rest-assured
  • 리포트 : extent-report
  • 통합 : Jenkins

환경 구성 (maven)

  <dependencies>
     <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <version>4.12</version>
     </dependency>
     <dependency>
         <groupId>org.hamcrest</groupId>
         <artifactId>hamcrest-all</artifactId>
         <version>1.3</version>
     </dependency>
     <dependency>
         <groupId>com.google.code.gson</groupId>
         <artifactId>gson</artifactId>
         <version>2.6.2</version>
     </dependency>
     <dependency>
          <groupId>io.rest-assured</groupId>
          <artifactId>rest-assured</artifactId>
          <version>3.0.2</version>
     </dependency>
     <dependency>
         <groupId>io.rest-assured</groupId>
         <artifactId>json-path</artifactId>
         <version>3.0.2</version>
     </dependency>
     <dependency>
         <groupId>io.rest-assured</groupId>
         <artifactId>xml-path</artifactId>
         <version>3.0.2</version>
     </dependency>        
     <dependency>
         <groupId>com.aventstack</groupId>
         <artifactId>extentreports</artifactId>
         <version>3.0.1</version>
     </dependency>
  </dependencies>
cs

샘플 테스트 코드 예제 

1
2
3
4
5
6
7
8
given()
    .header("Cookie""Cookie-Value")
    .contentType("application/x-www-form-urlencoded")
    .body("Body Message")
.when()
    .post("http://www.naver.com/openapi/apitest?method=abcde")
.then()
    .statusCode(200);
cs

Extent-Report 세팅

Jenkins 연동

  • Maven Project 생성 후  > Build Step에 mvn clean test 정도만 추가하면 완료
  • html publisher plugin설정 후, 생성된 extent report 파일을 설정해주면 끝
  • html report에 CSS가 jenkins 권한 등의 문제로 수행이 안된다면, 
    • 젠킨스 관리 > Script console 에서 아래 명령어를 넣어준다.
  • System.setProperty("hudson.model.DirectoryBrowserSupport.CSP", "")

api 테스트 자동화를 Java 환경에서 진행한다면, rest-assured는 진짜 적극 검토해볼만하다~  좋다~



지금은 API 테스트를 진행하라고 하면 rest-assured를 사용하기 때문에 다른 도구 사용할 일이 없지만

그래도 api testing 도구라고 하면 제일 많이 사용되는 도구가 뭐냐고 하면 SoapUI라고 말할 것 같다. 

이전에 SoapUI를 업무에 잠시 활용했을 때 나름 정리해두었던 내용을 여기에 다시 게시한다.

기본적인 사용법은 여기저기에 더 잘 정리되어 있으니, 생략하고.. tip 위주로..


Property 제어

  • 프로퍼티는 동적으로 변경되는 값, 자주 변경되어야 하는 값을 처리할 때 사용하고, 가급적 프로젝트 레벨(혹은 Suite레벨)로 관리하자.
    • 예) Endpoint URL, Username, MDN 등
  • 프로퍼티 제어는 Groovy Script를 활용하거나 Property Transfer 등을 활용한다

Assertion


(1) XPath Assertion
  • xml형식의 response message 중 단순 string추출이 아닌 특정한 형식의 값을 추출해낼 수 있는 방법으로 xpath 를 사용한다.
  • xml syntax 참고 : http://www.w3schools.com/xpath/xpath_syntax.asp
  • SoapUI에서는 xpath문법을 그대로 사용하면 동작하지 않는다. 아래 예제를 참고하여 작성하기 바란다.
    • xml response의 namespace값을 꼭 사용해야 한다.

    • expression부분에 하기와 같이 작성한다. 하기 예제는 response message중 첫번째로 보이는 spotCode의 value값을 가져온다.

      declare namespace ns1="http://www.naver.com"; 
      //ns1:spotCode[1]
(2) Regular Expression Assertion
  • Contains/NotContains 의 Assertion에 RegularExpression을 사용하여 적용할 수 있다. 이를 사용하면, 특정 string값이 아닌 원하는 형태의 문장을 비교할 수 있다.
  • Regular Expression 연습하기 좋은 사이트 url : http://www.regerx.com
  • SoapUI에서는 Regular Expression 문법을 그대로 사용하면 동작하지 않는다. 아래 예제를 참고하여 작성하기 바란다.
    •  response message가 json형식일때를 예로 들어, code값이 4만에서 5만번 사이의 숫자가 포함되는 조건을 추가하기 원한다면,

    • Contains/Not Contains Assertion에 use token as Regular Expression 체크박스를 체크하고, Content박스에 하기의 예와 같이 입력한다.

      (?s).*"code": "[4,5]\d{4}".*

(3) Script Assertion

Groovy Script

  • Javascript를 수행해야 하거나, Property를 제어하고자 할 때 많이 사용한다. 
  • Property 수행예제, javascript 실행 예제, cookie 예제를 하나씩 정리한다.

(1) Property 수행 예제

    • 참고 : https://www.soapui.org/scripting-properties/tips-tricks.html#0-1-how-to-get-and-set-properties-from-groovy
    • // get properties from testCase, testSuite and project
      def testCaseProperty = testRunner.testCase.getPropertyValue( "MyProp" )
      def testSuiteProperty = testRunner.testCase.testSuite.getPropertyValue( "MyProp" )
      def projectProperty = testRunner.testCase.testSuite.project.getPropertyValue( "MyProp" )
      def globalProperty = com.eviware.soapui.SoapUI.globalProperties.getPropertyValue( "MyProp" )
       
      // setting values is equally straigh forward
      testRunner.testCase.setPropertyValue( "MyProp", someValue )
      testRunner.testCase.testSuite.setPropertyValue( "MyProp", someValue )
      testRunner.testCase.testSuite.project.setPropertyValue( "MyProp", someValue )
      com.eviware.soapui.SoapUI.globalProperties.setPropertyValue( "MyProp", someValue )

(2) JavaScript 실행 예제

  • import javax.script.*
       
    // 기본으로 제공되는 JS엔진 사용
    ScriptEngineManager factory = new ScriptEngineManager();
    ScriptEngine engine = factory.getEngineByName("JavaScript")
    Invocable invocable = (Invocable) engine;     // JS 안의 메소드를 호출하는 인터페이스
      
    // JS loginName 을 파라미터로 넣어 호출하기 위한 함수를 만들어준다.
    def rsaFile = new URL("https://www.domain.com/encrypt.js").getText();
    rsaFile += """
    function fireEncrypt(str){
        return rsa.encrypt(str);
    };
    """;
    // 넣지 않으면 navigator가 없어 에러가 남.
    engine.put("navigator", "");
    engine.eval(rsaFile);
    def loginName = testRunner.testCase.testSuite.project.getPropertyValue("loginName");    // testSuite 에 설정한 property 를 읽어옴
    def encryptedLoginName = invocable.invokeFunction("fireEncrypt", loginName);
     
    testRunner.testCase.testSuite.project.setPropertyValue("encryptedLoginName",encryptedLoginName);      // testSuite 에 property 를 설정

(3) Cookie 설정 예제

  • import com.eviware.soapui.model.iface.SubmitContext
    import org.apache.http.client.protocol.ClientContext
    import org.apache.http.impl.cookie.BasicClientCookie
       
    // 현재 Case 가 실행되는 Context 를 받아와 여기에 저장한다. 그래서 이 Step 만 따로 실행하기는 안됨
    def httpContext = context.getProperties().getProperties().get(SubmitContext.HTTP_STATE_PROPERTY)
      
    def cookieStore = httpContext.getAttribute(ClientContext.COOKIE_STORE)
    def cookies = cookieStore.getCookies()
       
    // 쿠키에 세팅할 값.
    def sampleCookie = new BasicClientCookie("sample","sampleData");
    pcidCookie.setDomain(".naver.com")
      
    cookieStore.addCookie(sampleCookie)

Reporting


Jenkins 연동하기

1. 준비하기

  • jenkins에 아래와 같은 plugin이 설치되어 있어야 한다.

    • junit attachments plugin

    • junit plugin

    • git plugin

    • svn plugin
  • jenkins가 구동되는 장비에 soapUI가 설치되어 있어야 한다.

2. 수행하기

(0) Jenkins에서 프로젝트(new jobs) 생성 후..

(1) 소스(script) 코드 관리 부분 설정

    소스(soapui script)를 버전관리시스템(리파지토리, 현재 bitbucket)에서 가져오도록 설정

(2) Build 부분 설정

  • execute shell을 선택

    • command입력박스에 아래와 같이 입력 (SoapUI 의 TestRunner 수행을 위한 CommandLine 명령어를 넣어준다)

export JAVA_HOME=/app/jdk1.7.0_80; SOAPUI_HOME=/home/SoapUI-5.2.1; export SOAPUI_HOME; export PATH=$JAVA_HOME/bin:$PATH; export WORKSPACE=$WORKSPACE/MyWorkspace; rm -rf $WORKSPACE/report/JReport/*.xml; $SOAPUI_HOME/bin/testrunner.sh -a -j -I -f$WORKSPACE/report/JReport -PEndpoint=www.naver.com $WORKSPACE/test_api_project.xml

  • workspace는 soapui스크립트가 존재하는 곳을 지정하는 것이 좋음. 
  • -f 옵션은 결과파일 위치 지정, -P옵션은 스크립트 수행에 필요한 파라미터입력
  • 수행 전 기존 결과파일은 삭제하는 것이 좋음. 

(3)  빌드후 조치 부분 설정

  •  publish junit test result report 선택

    • Test report XMLs :  MyWorkspace/report/JReport/*.xml

    • Additional test report features 체크박스 체크.


Open API란?

  • 다양한 서비스와 컨텐츠 그리고 데이터를 좀 더 쉽게 이용할 수 있도록 공개한 인터페이스 (application pragramming interface)
  • 내부를 몰라도 Open API를 통해 해당 서비스의 기능 및 데이터를 쉽게 이용할 수 있다. 

Rest ?

  • 기존의 soap, wsdl 기반의 웹서비스를 대체하는 방식
  • CURD를 위해 Http method 를 사용
  • stateless 임 -> 세션, 쿠키값 등을 유지하지 않음
  • 디렉토리 구조와 같은 URI를 통해 리소스 접근하고, XML/Json 데이터 구조를 통해 데이터를 전송함
  • 구성
    • HTTP URI (Resource) + HTTP Method + Message (json)
  • Method
    • GET (Read) / POST (Create) / PUT (Update) / DELETE (Delete)
  • HTTP 응답코드
    • 2xx - 성공
      • 200 OK
    • 3xx - Redirection 
      • 304 Not Modified
    • 4xx - Client Error
      • 400 Bad Request
      • 401 Unauthorized
      • 403 Forbidden
      • 404 Not Found
    • 5xx - Server Error
      • 500 Internal Server Error
      • 503 Service Unavailable
      • 504 Gateway Timeout

테스트 접근 방법

  • open api의 사용자 관점에서 접근하되, sql query라고 생각하고 접근한다.
  • api 스펙 등 문서 기반으로 접근한다.
  • 전략
    • method type별 테스트 
      • GET : 조회 결과가 있을때, 없을때 
      • POST : 중복 생성이 불가한 경우를 고려한 중복 등록 시도
      • PUT : 존재하지 않는 데이터에 대한 수정 시도, 중복이 불가한 PK값으로 수정 시도
      • DELETE : 존재하지 않는 데이터 삭제 시도 등.
    • 응답코드 확인 테스트
      • 문서에 존재하는 응답코드 별로, 응답코드를 발생시키는 테스트 수행
      • 예) 필수 파라미터 누락, 존재하지 않는 데이터 조회 등
    • 파라미터에 대한 입력 예외 케이스 
      • String type의 경우 : 빈문자, 공백문자, 앞뒤 공백문자, 특수문자, 긴 문자열 등
      • 숫자의 경우 : 문자, int 범위를 넘어서는 숫자, 음수, 0 등
      • 날짜/시간 등 : 시간 연도 구분을 넘어가는 숫자 등 (25시간, 13월 등)
      • 정의된 코드 값 : 정의되지 않은 값, 대소문자 구분 등
      • 범위형 변수 : 시작과 끝, 시작과 끝을 벗어난 값 등
    • 비즈니스 흐름기반, 혹은 사용자 시나리오 기반 테스트 수행
      • api 하나로 기능을 확인할 수 없어, 여러 api를 호출해야 기능 하나를 확인할 수 있는 경우
      • 사용자 권한에 따라 다르게 동작해야 하는 경우
      • 여러 조건에 따라 다르게 동작해야 하는 경우 등.
    • 보안 테스트 수행
      • Parameter fuzzing
      • SQL Injections
      • Username harvesting
      • Cross-site scripting
      • External entities
      • Schema invalid XML
      • Large XML document
      • Malformed XML


appium으로 사내 서비스 자동화를 진행하다가, junit report나 surefire report 포맷이 별로라서, 좀 더 새로운 html report를 찾다가 발견한 extent report.


좀 더 이쁘장하고, 결과에 따라 필터링도 된다. 

다만, junit 결과를 자동으로 report로 변환하는게 아니라서, 테스트 이후 결과에 따른 로그를 직접 넣어야 된다. 


selenium이나 appium 자동화에 사용하는 사례도 구글링하면 좀 나오는거 보니, 쓸만한 것 같아 적용하기로 했고, 

실제 ui 자동화랑 api 자동화 업무에 해당 리포트를 사용하고 있다. 


우선, maven 설정을 추가하고,

<dependency>
    <groupId>com.aventstack</groupId>
    <artifactId>extentreports</artifactId>
    <version>3.1.2</version>
</dependency>


  • 테스트 시작 시점에는 createReport
  • 테스트케이스 시작시점에는 createTest 
  • 테스트 종료시점에 pass/fail/skip 의 결과를 extent report에 작성-> test.log(STATUS.PASS...)


우선 report 생성할때, 설정 내용을 반영하도록 해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private static ExtentReports createReport() throws Exception {
    if(extent != nullreturn extent;
 
    String reportFile= new File("target/result.html").getAbsolutePath();
 
    htmlReporter = new ExtentHtmlReporter(reportFile);
    htmlReporter.setAppendExisting(true);
    htmlReporter.config().setChartVisibilityOnOpen(true);
    htmlReporter.config().setDocumentTitle("타이틀적용");
    htmlReporter.config().setEncoding("UTF-8");
    htmlReporter.config().setReportName("리포트제목");
 
    extent = new ExtentReports();
    extent.setSystemInfo("OS""Windows 7");
    extent.attachReporter(htmlReporter);
 
    return extent;
}
cs


테스트 시작시

1
2
3
4
5
@BeforeClass
public static void setUp() throws Exception {
    enableLoggingOfRequestAndResponseIfValidationFails();
    extent = createReport();
}
cs


케이스 실행 시작 시점

1
2
3
4
@Before
public void beforeMethod() throws Exception {
    test = extent.createTest(methodName);
}
cs

케이스 종료 시점

1
2
3
4
@After
public void afterMethod() throws Exception {
    extent.flush();
}
cs

테스트 결과에 따라 리포트 로그를 남겨야 하기에 TestWatcher를 활용.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Rule
public TestWatcher testWatcher = new TestWatcher() {
 
    @Override
    protected void failed(Throwable e, Description description) {
        test.log(Status.FAIL, e.toString());
    }
 
    @Override
    protected void succeeded(Description description) {
        test.log(Status.PASS, "SUCCESS");
    }
 
     @Override
    protected void skipped(AssumptionViolatedException e, Description description) {
        test.log(Status.SKIP, SKIP_MSG);
    }
 
}
cs


대충 이정도 구성해놓으면, 각 테스트케이스 @Test 단위에서는 기존과 같이 자동화 코드를 작성하면 된다.


덧붙여 좀 더 추가해서 신경쓸 부분이 있다면, 

  • test.assignCategory(SuiteName); 등을 추가해서, 카테고리별로 구분해서 보게 할 수 있다.
  • 혹시 retry 를 사용하고 있다면, retry시에는 createTest를 추가할 필요가 없다.
  • report에 screenshot을 추가하고 싶다면, test.log(...).addScreenCaptureFromPath() 를 사용하면 된다.
  • jenkins에 연동할때에는 CSS가 적용될 수 있도록 jenkins에 CSS 관련 설정을 해야 한다.


더 좋거나 쉬운 리포트 라이브러리가 있으면 찾아보겠지만, extent report만으로도 현재까지는 만족.





'QA > Test Automation' 카테고리의 다른 글

SoapUI를 이용한 API 테스트 방법 정리  (0) 2018.01.28
API 테스트 방법  (0) 2018.01.27
Jenkins와 Sonar 연동하기  (0) 2012.07.19
Ant 로 Sonar 수행하기.  (0) 2011.04.12
Sonar 소개  (0) 2011.02.14

하도 오래전에 Jenkins와 Sonar를 연동해봤었는데,

요번에 BMS 프로젝트 진행하면서 Sonar한번 설치할래니 기억이 가물가물..;;


그래서 다시 이곳에 정리하기로 했다. 우선 Jenkins와 Sonar가 설치되어 있다는 가정하에 내용을 정리하겠다.


  1. Jenkins에 Sonar Plugin을 설치해야 한다. 
    • Jenkins 관리 > 플러그인 관리
  2. Maven 관련 설정을 한다. Sonar 자체가 Maven기반으로 동작하기 때문에 Maven설정은 필수다.
    • Jenkins 관리 > Configuration (시스템 설정) > Maven
  3. Sonar 관련 설정을 한다.
    • Jenkins 관리 > Configuration (시스템 설정) > Sonar
  4. Build Job을 생성하고 Build 관련 설정을 한다. 
    1. Build Job > 설정 > Build 관련 설정
      • root pom.xml 을 설정하고, goal에서는 -DskipTests=true를 꼭 넣어준다. sonar에서 test를 수행하기 때문에 저 옵션을 넣지 않으면 test가 두번 수행된다.
    2. Build Job > 설정 > Post-build Actions > Sonar 관련 설정
      • Add post-build action에서 Sonar를 선택하고, Sonar관련 설정을 한다.
      • -Dsonar.projectVersion옵션을 사용하면 Sonar에 version 정보를 전달할수 있다. 옵션을 사용하지 않으면 pom.xml의 <version> 정보가 sonar로 넘어간다. 
  5. Jenkins에서 Build를 수행한다. 연동 완료!


'QA > Test Automation' 카테고리의 다른 글

API 테스트 방법  (0) 2018.01.27
자동화에 extent report 적용하기  (0) 2018.01.22
Ant 로 Sonar 수행하기.  (0) 2011.04.12
Sonar 소개  (0) 2011.02.14
CI(Continuous Integration) 서버 구성안  (0) 2011.02.14

+ Recent posts