Test Practice using Selenium - 2

Dahun Yoo·2022년 3월 5일
0

QA or Test

목록 보기
24/38
post-thumbnail

Selenium 공식문서에서 소개하고 있는 테스트 전략 및 추천/비추천하는 테스트 케이스들에 대해 번역하여 소개해드립니다.

영어와 일어번역판을 참조하여 작성하였음을 말씀드립니다.

https://www.selenium.dev/documentation/test_practices/


Test Pracetice

Selenium 프로젝트로부터 테스트에 관한 몇가지 가이드라인과 추천사항

Some guildeline and recommendatios on testing from the Selenium project

Best practice 에 관한 메모 : 이 문서에서는 Best practice 라고 하는 문구를 의도적으로 피하고 있습니다. 모든 상황에 유효한 접근방법은 없습니다. 가이드라인과 추천 이라고 하는 아이디어를 좋아합니다. 이러한 내용들을 쭉 읽어보시고 특정한 환경에서 어떠한 접근 방법이 유효한지를 신중이 결정하시길 바랍니다.

기능 테스트는 많은 이유에서 적절히 실행하는 것이 어렵습니다. 어플리케이션의 상태, 복잡도 및 의존관계를 포함한 브라우저 (특히 크로스 브라우저의 비호환성)를 다루는 문제는 좋은 테스트의 작성을 어렵게 합니다.

Selenium은 기능적인 User interaction을 간단하게 하는 도구를 제공합니다만, 적절히 설계된 Test suite의 작성에는 그다지 도움되지는 못합니다. 여기서는 기능적인 Web page의 자동화를 구현하는 방법에 관한 조언, 가이드라인, 및 추천드리는 것들을 말씀드립니다.

긴 시간에 걸쳐 성공해온 Selenium의 많은 유저들 사이에서 인기있는 소프트웨어 설계 패턴을 소개합니다.


Encouraged behaviors

추천드릴만한 패턴 및 사례들

Page Object Model

PageObject는 테스트 코드의 유지보수를 용이하게 해주며, 코드의 중복을 줄이기위해 테스트 자동화 시에 널리 사용되는 디자인 패턴입니다. PageObject는 AUT(Application Under Testing, 테스트 대상 어플리케이션) 의 페이지 인터페이스로써 기능하는 객체지향 클래스입니다.

테스트는 해당 페이지의 UI와 상호작용이 필요할 때에는 항상, 이 PageObject class의 메소드를 사용합니다. 좋은 점으로는 페이지의 UI가 변경되었을 떄, 테스트케이스 코드 자체의 변경은 필요없고, PageObject 내의 코드만 변경하기만 하면 됩니다. 결과적으로, 새로운 UI에 대응하기 위한 모든 변경은 한 부분에서만 변경하면 된다는 것입니다.

Advantages

  • 테스트 케이스 코드와 페이지를 모델링한 코드들은 분리되어 있습니다.
  • 테스트 전체에 페이지의 기능이 분산되어 있지 않고, 페이지 모델들 또한 한 곳으로 모여 있습니다.

어떤 경우도, PageObject Model에 의하여 UI변경으로 인하여 발생하는 코드 변경을 여러 코드에서 나누어서 작업하지 않아도 됩니다. 이 테스트 디자인패턴이 널리 사용되면서, 이 방법에 대한 정보는 많은 블로그에서도 찾아보실 수 있습니다. 좀 더 자세히 알고싶으신 분들은 이 테마에 관한 블로그를 검색해보시길 바랍니다. 많은 사람들이 이 설계패턴에 대해 기술하고 있으며, 이 문서의 설명을 뛰어넘는 유용한 힌트들을 찾아보실 수 있을 것입니다. 단, 간단히 설명해드리기 위해 PageObject의 간단한 예씨들을 소개해드립니다.

Examples

일단, PageObject를 사용하지 않는 테스트 자동화의 전형적인 예시를 생각해봅시다.

/***
 * Tests login feature
 */
public class Login {

  public void testLogin() {
    // fill login data on sign-in page
    driver.findElement(By.name("user_name")).sendKeys("userName");
    driver.findElement(By.name("password")).sendKeys("my supersecret password");
    driver.findElement(By.name("sign_in")).click();

    // verify h1 tag is "Hello userName" after login
    driver.findElement(By.tagName("h1")).isDisplayed();
    assertThat(driver.findElement(By.tagName("h1")).getText(), is("Hello userName"));
  }
}

위 구현방법에는 2가지 문제점이 있습니다.

  1. 테스트 방법과 AUT의 로케이터간의 구별이 없습니다. 양쪽다 하나의 메소드로 대응하고 있습니다. AUT의 UI가 식별자, 레이아웃 또는 로그인 입력 및 처리 방법을 변경되는 경우, 테스트 코드 자체도 변경해야합니다.
  2. ID 로케이터는 이 로그인 페이지를 사용해야하는 모든 테스트에서, 여러 라인에 분산되어 있습니다.

PageObject로 구현한다면, 위 예시는 아래와 같이 다시 구현해볼 수 있습니다.

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;

/**
 * Page Object encapsulates the Sign-in page.
 */
public class SignInPage {
  protected WebDriver driver;

  // <input name="user_name" type="text" value="">
  private By usernameBy = By.name("user_name");
  // <input name="password" type="password" value="">
  private By passwordBy = By.name("password");
  // <input name="sign_in" type="submit" value="SignIn">
  private By signinBy = By.name("sign_in");

  public SignInPage(WebDriver driver){
    this.driver = driver;
  }

  /**
    * Login as valid user
    *
    * @param userName
    * @param password
    * @return HomePage object
    */
  public HomePage loginValidUser(String userName, String password) {
    driver.findElement(usernameBy).sendKeys(userName);
    driver.findElement(passwordBy).sendKeys(password);
    driver.findElement(signinBy).click();
    return new HomePage(driver);
  }
}

또한 HomePage 의 PageObject는 아래와 같습니다.

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;

/**
 * Page Object encapsulates the Home Page
 */
public class HomePage {
  protected WebDriver driver;

  // <h1>Hello userName</h1>
  private By messageBy = By.tagName("h1");

  public HomePage(WebDriver driver){
    this.driver = driver;
    if (!driver.getTitle().equals("Home Page of logged in user")) {
      throw new IllegalStateException("This is not Home Page of logged in user," +
            " current page is: " + driver.getCurrentUrl());
    }
  }

  /**
    * Get message (h1 tag)
    *
    * @return String message text
    */
  public String getMessageText() {
    return driver.findElement(messageBy).getText();
  }

  public HomePage manageProfile() {
    // Page encapsulation to manage profile functionality
    return new HomePage(driver);
  }
  /* More methods offering the services represented by Home Page
  of Logged User. These methods in turn might return more Page Objects
  for example click on Compose mail button could return ComposeMail class object */
}

따라서, 로그인 테스트는 위 2개의 PageObject를 이용하여 아래와 같이 구현할 수 있습니다.

/***
 * Tests login feature
 */
public class TestLogin {

  @Test
  public void testLogin() {
    SignInPage signInPage = new SignInPage(driver);
    HomePage homePage = signInPage.loginValidUser("userName", "password");
    assertThat(homePage.getMessageText(), is("Hello userName"));
  }

}

PageObject의 설계방법은 융통성이 있습니다만, 테스트 코드의 바람직한 보수성을 위해서는 기본적인 룰이 몇가지가 있습니다.

PageObject 자체는 검증이나 Assertion을 하지 않습니다. 이것은 테스트의 일부이며 항상 PageObject가 아니라 테스트 코드 내부에 있을 필요가 있습니다. PageObject에는 페이지의 표현과, 페이지가 메소드를 통해 제공하는 특정 이벤트(입력, 클릭 등)가 포함됩니다만, 테스트 대상에 관련된 코드는 PageObject 내부에 존재하지 않도록 해야합니다.

PageObject내에 존재해도 되는 하나의 테스트가 있습니다. 이것은 페이지 및 페이지 상의 중요한 Element를 제대로 읽어들였는지 확인하는 로직입니다. 이 테스트는 PageObject를 인스턴스로 만들때 실행할 필요가 있습니다. 위의 예시라면 SignInPage 생성자와 HomePage 생성자가, 각각 생성해야할 대상 페이지의 element를 읽어들이고, 테스트 코드에 대응할 수 있다는 것을 확인해야합니다.

PageObject는 반드시 페이지 전체를 나타낼 필요는 없습니다. PageObject 디자인패턴은, 페이지 상의 컴포넌트를 나타내기 위해 사용합니다. AUT의 페이지에 여러개의 컴포넌트가 있는 경우, 컴포넌트별로 고유한 PageObject를 생성해놓는다면 유지보수성이 좋아지는 경우도 있습니다.

또한, 테스트에서 사용할 수 있는 다른 디자인 패턴들도 있습니다. PageFactory 를 사용하여 PageObject를 인스턴스로 생성할 수 있습니다. 이 모든 것들에 대해 설명드리기에는 본 문서의 범위를 넘어가는 것 같습니다. 본 문서에서는 독자분들에게 몇가지 예시의 개념을 소개시켜드릴 뿐입니다. 앞서 말씀드린 것 처럼, 많은 유저들이 이 주제에 관해 포스트를 작성했기 때문에 검색해서 보시면 좀 더 좋을 것 같습니다.

Summary

  • public 메소드는 페이지에서 제공하는 이벤트나 기능 들을 나타냅니다.
  • 페이지의 내부를 노출하려고 하면 안됩니다.
  • 페이지 내에서 assertion을 하지 않습니다.
  • 메소드는 다른 PageObject를 리턴합니다.
  • 페이지 전체를 구현하지 않아도 됩니다.
  • 같은 행위에 대한 다른 결과에 대해서는, 다른 메소드로 구현해야합니다.

Domain Specific language

DSL은 문제를 해결하기 위한 표현수단을 유저에게 제공하는 시스템입니다. 이것으로 인해 고객은 프로그래밍 언어가 아닌 자신들의 언어로 시스템과 인터랙션할 수 있습니다.

보통은 고객은 사이트의 전체적인 디자인에 큰 신경을 쓰진 않습니다. 디자인 요소, 애니메이션, 그래픽스에 대해서는 큰 관심은 없습니다. 고객들은 시스템을 사용하여 새로운 종업원들을 최소한의 코스트로 사내 프로세스에 녹아들게하고 싶어합니다. 고객들은 알래스카행 비행기를 예약한다거나, 유니콘을 할인된 가격에 구매하고 싶어합니다. 테스터로써의 여러분의 업무는 이러한 사고방식을 인식하는 것이 중요합니다. 이것을 인식하고, 테스트 스크립트가 고객을 대변하여 표현할 수 있도록, 개발중의 어플리케이션을 모델링해야합니다.

Selenium에서는 DSL을 보통 API로 쉽게 읽을 수 있도록한 메소드로 표현하고 있스빈다. 개발자와 이해관계자들과의 커뮤니케이션이 가능하도록 합니다.

Benifits

  • Readable : 비즈니스 관계자들이 동작내용을 이해할 수 있습니다.
  • Writable : 작성하기 쉽고, 불필요한 중복을 피할 수 있습니다.
  • Extensible : 기능을 쉽게 확장할 수 있고, 기존 기능에 결함을 발생시키지 않습니다.
  • Maintainable : 구현 상세 내용을 테스트케이스에서 고려하지 않아도 됩니다. 유지보수하기 용이합니다.

Java

Java의 DSL메소드를 소개합니다. 간결하게 소개하기 위해, driver 오브젝트가 사전에 정의되어 있으며, 메소드에서 사용가능한 것을 전제로 합니다.

/**
 * Takes a username and password, fills out the fields, and clicks "login".
 * @return An instance of the AccountPage
 */
public AccountPage loginAsUser(String username, String password) {
  WebElement loginField = driver.findElement(By.id("loginField"));
  loginField.clear();
  loginField.sendKeys(username);

  // Fill out the password field. The locator we're using is "By.id", and we should
  // have it defined elsewhere in the class.
  WebElement passwordField = driver.findElement(By.id("password"));
  passwordField.clear();
  passwordField.sendKeys(password);

  // Click the login button, which happens to have the id "submit".
  driver.findElement(By.id("submit")).click();

  // Create and return a new instance of the AccountPage (via the built-in Selenium
  // PageFactory).
  return PageFactory.newInstance(AccountPage.class);
}

위 메소드는, 테스트 코드로부터 입력필드, 버튼, 클릭 및 페이지의 개념을 완전하게 추상화시킵니다. 이 방식을 사용하면 테스터는 이 메소드를 호출하는 것으로 끝나고, 상세 구현내용은 알지 않아도 됩니다. 이것으로 유지보수의 이점을 얻을 수 있습니다. 로그인 필드가 변경된 경우, 테스트코드가 아닌 이 메소드를 수정하기만 하면 됩니다.

public void loginTest() {
    loginAsUser("cbrown", "cl0wn3");

    // Now that we're logged in, do some other stuff--since we used a DSL to support
    // our testers, it's as easy as choosing from available methods.
    do.something();
    do.somethingElse();
    Assert.assertTrue("Something should have been done!", something.wasDone());

    // Note that we still haven't referred to a button or web control anywhere in this
    // script...
}

다시 한 번 말씀드립니다. 중요한 목적 중 하나로는, 테스트가 UI문제가 아닌, 근본적인 문제에 대처할 수 있는 API를 작성하는 것입니다. UI는 유저에게 있어서 부차적인 관심사입니다. 유저(고객)은 UI를 크게 신경쓰지 않고 그저 본인들의 니즈를 해결하고자 할 뿐입니다. 테스트 스크립트는 유저가 하고싶은 것과 알고 싶은 것에 대해 파악해야할 필요가 있습니다. 테스트에서는 UI가 어떻게 그것을 표현하는지에 대해 작성되어있을 뿐, 자세하게는 신경쓰지 않아도 됩니다.

Generating application state

어플리케이션 상태의 생성

Selenium은 테스트케이스의 준비에 사용하지 말아주십시오. 테스트 케이스의 모든 반복적인 액션과 준비는, 다른 방법으로 실시할 필요가 있습니다. 예를 들어, 대부분의 Web UI에는 인증처리가 있습니다. (로그인 등) 모든 테스트의 실시 전에는 Web브라우저에서 로그인을 없애는 것으로, 테스트의 실행 속도와 안정성을 향상시킵니다. AUT에 접근하기 위한 메소드를 만들 필요가 있습니다. (API를 사용하여 로그인하고, cookie를 설정하는 등).
또한, 테스트용도의 데이터를 미리 로드하는 메소드는, Selenium을 사용하여 실행하지 않는 것이 좋습니다. 앞서 말씀드렸듯, AUT에 테스트용도의 데이터를 준비할 때에는 기존의 API를 활용할 필요가 있습니다.

Mock external service

외부 서비스와의 의존관계를 배제한다면, 테스트의 실행 속도와 안정성이 대폭 향상됩니다.

Improved Reporting

Selenium은 실행된 테스트케이스의 실행결과를 레포트할 수 있도록 설계되어있지는 않습니다. Unit Test framework를 조합하여, 레포트기능을 이용하는 것은 좋은 시도입니다. 대부분의 Unit Test framework에는 xUnit 또는 HTML형식의 레포트를 생성할 수 있습니다. xUnit 레포트는 Jenkins, Travis, Bamboo 등의 계속적 통합(Continuous Integration) 도구와 같은, 서버에 결과를 import할 수 있는 것들이 인기가 있습니다.

몇가지 프로그래밍 언어의 레포트 출력에 상세한 정보에 대해서는 아래 링크들을 참고해주세요.

Avoid sharing state

어플리케이션의 상태를 공유하지 않는 것.

몇가지 다른 문서에서 언급드리고 있습니다만, 계속해서 말씀드릴 가치가 있다고 생각합니다. 테스트케이스는 상호간에 분리되어야 함이 중요합니다.

  • 테스트 케이스에서 사용되는 데이터를 공유하지 마십시오. 액션을 실행하기 전에 각각의 유효한 주문을 데이터베이스에서 조회하는 테스트케이스를 상상해보세요. 2개의 테스트에서 같은 순서를 선택한다면, 예상하지 않은 동작이 발생할 가능성이 있습니다.
  • 다른 테스트에서 생성될 가능성이 있는 어플리케이션 내의 오랜 데이터를 삭제하세요.
    • 예) 유효하지 않은 주문 레코드
  • 테스트를 실행할 때마다 새로운 WebDriver 인스턴스를 생성합니다. 이것으로 테스트 케이스 간 분리가 보증되며 병렬화를 좀 더 간단하게 실행할 수 있습니다.

Tips on working with locators

locator를 사용할 떄의 tip.

일반적으로, HTML의 id속성이 이용가능한 유니크한 속성인 경우, 페이지에서 element를 찾아내는 방법으로서는 제일입니다. id는 findElement 속도가 매우 빠른 경향이 있으며, 복잡한 DOM Traverse에서 발생하는 처리들을 생략할 수 있습니다.

유니크한 id를 사용하지 않는 경우, 잘 작성된 CSS Selector가 element를 찾아낼 때 효과적입니다. XPath는 CSS Selector와 똑같이 동작하나, 문법이 복잡하고 대부분의 경우 디버깅이 복잡합니다.
XPath는 매우 유연하게 사용할 수 있습니다만, 브라우저 제작사에서는 XPath에 관하여 성능테스트는 통상적으로 진행하지 않으며, 동작이 느린 경향이 있습니다.

link text selector와 partial link text selector는 a태그밖에 동작하지 않는다는 결점이 있습니다. 게다가 WebDriver내부에서 querySelectorAll로 변환되기도 하빈다.

태그명에 따른 locator는 위험한 방법이기도 합니다. 대부분의 경우 페이지 상에는 같은 태그들이 다수 존재합니다. 태그명으로 찾을 때는, 해당 태그의 리스트를 리턴하는 findElements(By) 메소드를 사용할때 도움될 수도 있습니다.

locator는 가능한한 간결하고 일긱 쉬운 상태를 유지할 것을 추천합니다. WebDriver에서 DOM 구조의 traverse를 실행하는 것은 꽤나 코스트가 높은 작업입니다. 검색의 범위를 좁히는 것이 좀 더 좋은 결과를 도출해낼 수 있습니다.

DOM : Document Object Model, XML이나 HTML 문서에 접근하기 위한 일종의 인터페이스.

Test independency

테스트의 독립성

각 테스트 케이스를 하나의 독립적인 단위로 기술해야합니다. 다른 테스트케이스에 의존하지 않는 방법으로 테스트를 구현하십시오.

하나의 모듈로써 업데이트 후에 웹사이트에 표시되는 커스텀 컨텐츠를 작성할 수 있는 컨텐츠 관리 시스템(Contents Management System, CMS) 가 있다고 가정해봅시다. CMS와 어플리케이션 간의 동기에 시간이 걸린다고도 해봅시다.

모듈을 테스트할 때의 잘못된 방법으로는, 하나의 테스트에서 컨텐츠가 생성 및 오픈되어, 다른 테스트에서 해당 모듈을 체크하는 방법입니다. 컨텐츠는 업데이트 후에 다른 테스트에서 바로 사용할 수 없을 가능성이 있기 때문에, 이 방법은 적절하지는 않습니다.

그 대신에 영향을 받는 테스트 케이스 내에서, On / Off할 수 있는 Stub 컨텐츠를 작성하고, 이것을 모듈 검증에 사용하는 것입니다. 단, 컨텐츠의 작성에 대해서는 다른 테스트 케이스에서 실행해볼 수 있습니다.

Considering using a fluent API

Martin Fowler는 Fluent API 라는 용어를 만들었습니다. Selenium은 이미 FluentWait 클래스에서 그것과 같은 것을 구현하고 있스빈다. 이것은 준비의 Wait 클래스의 대체제이기도 합니다. PageObject에서 Fluent API 디자인 패턴을 유효하게 한 다음, 다음과 같은 코드 스니펫을 사용해볼 수 있습니다.

driver.get( "http://www.google.com/webhp?hl=en&amp;tab=ww" );
GoogleSearchPage gsp = new GoogleSearchPage();
gsp.withFluent().setSearchString().clickSearchButton();

이 변화가 크게 발생하는 동작을 가진 Google PageObject class는 다음과 같습니다.

public class GoogleSearchPage extends LoadableComponent<GoogleSearchPage> {
  private final WebDriver driver;
  private GSPFluentInterface gspfi;

  public class GSPFluentInterface {
    private GoogleSearchPage gsp;

    public GSPFluentInterface(GoogleSearchPage googleSearchPage) {
        gsp = googleSearchPage;
    }

    public GSPFluentInterface clickSearchButton() {
        gsp.searchButton.click();
        return this;
    }

    public GSPFluentInterface setSearchString( String sstr ) {
        clearAndType( gsp.searchField, sstr );
        return this;
    }
  }

  @FindBy(id = "gbqfq") private WebElement searchField;
  @FindBy(id = "gbqfb") private WebElement searchButton;
  public GoogleSearchPage(WebDriver driver) {
    gspfi = new GSPFluentInterface( this );
    this.get(); // If load() fails, calls isLoaded() until page is finished loading
    PageFactory.initElements(driver, this); // Initialize WebElements on page
  }

  public GSPFluentInterface withFluent() {
    return gspfi;
  }

  public void clickSearchButton() {
    searchButton.click();
  }

  public void setSearchString( String sstr ) {
    clearAndType( searchField, sstr );
  }

  @Override
  protected void isLoaded() throws Error {
    Assert.assertTrue("Google search page is not yet loaded.", isSearchFieldVisible() );
  }

  @Override
  protected void load() {
    if ( isSFieldPresent ) {
      Wait<WebDriver> wait = new WebDriverWait( driver, Duration.ofSeconds(3) );
      wait.until( visibilityOfElementLocated( By.id("gbqfq") ) ).click();
    }
  }
}

Fresh browser per test

테스트를 진행할 때마다 새로운 브라우저를 실행시키는 것.

각각의 테스트는 깨끗한 상태에서부터 시작해야합니다. 이상적인 것은, 각각의 테스트마다 새로운 가상머신을 실행하는 것입니다. 새로운 가상머신을 실행하는 것이 많은 코스트가 소모된다면, 적어도 각각의 테스트마다 새로운 WebDriver를 실행시켜주세요. FireFox의 경우 하나의 프로파일로부터 WebDriver를 실행시킬 수 있습니다.

FirefoxProfile profile = new FirefoxProfile(new File("pathToFirefoxProfile"));
WebDriver driver = new FirefoxDriver(profile);

Discouraged behaviors

Selenium을 이용하여 브라우저를 자동화할때 피해야할, 추천하지 않는 것들을 소개합니다.

Captchas

CAPTCHA(캡챠)는, Completely Automated Public Turing test to tell Computers and Humans Apart 의 약자로, 자동화를 방지하기 위해 명시적으로 설계되어있기 때문에, 자동화를 비추천합니다. CAPTCHA를 회피하기 위해서는 아래 2가지 전략을 추천드립니다.

  • 테스트 환경에서 CAPTCHA를 무효화합니다.
  • 테스트가 CAPTCHA를 회피할 수 있도록 hook을 추가합니다.

File downloads

Selenium의 관리 하에 있는 브라우저에서 링크를 클릭하여 파일을 다운로드받는 것은 가능합니다만, API는 다운로드의 진행상황을 알 수 없기 때문에, 다운로드한 파일의 테스트를 하는 것은 바람직하지 않습니다. 파일의 다운로드는 Web 플랫폼과 유저 인터렉션을 시뮬레이션하는 하나의 주요 항목으로는 취급하지 않기 때문입니다.
대신에, selenium (및 필요한 cookie)를 사용하여 링크를 찾아내고, libcurl 등으 HTTP request 라이브러리를 통해 다운로드합니다.

HtmlUnit 드라이버AttachmentHandler 인터페이스의 구현을 통해 입력 스트림으로써 첨부파일에 접근하는 것이 가능합니다. 이것으로 첨부파일을 다운로드해볼 수 있습니다.
AttachmentHandler는 HtmlUnit에 추가할 수 있습니다.

HTTP response codes

Selenium RC의 일부 브라우저 구성에서는 Selenium은 브라우저와 자동화된 사이트 간의 프록시로써 동작해왔습니다. 이것은 Selenium을 통한 모든 브라우저 트래픽을 확인 및 조작할 수 있다는 뜻이기도 했습니다. captureNetworkTraffic() 메소드는 HTTP response 코드를 포함한 브라우저와 자동화된 사이트 간의 모든 네트워크 트래픽을 확인하는 목적을 가지고 있습니다.

Selenium WebDriver는 브라우저의 자동화에 대한 전혀 다른 방향의 접근방식이며, 실제 사용하는 유저처럼 동작하는 것을 선호하기 때문에, WebDriver를 사용하여 테스트를 기술하는 것으로 유저의 동작을 표현할 수 있습니다. 자동화된 기능 테스트에서는 status code의 확인은, 테스트의 실패에 매우 중요한 부분은 아닙니다. 그것보다는 테스트를 실행하는 순서가 중요합니다.

브라우저는 항상 HTTP status code를 나타냅니다. 예를들어, 404 혹은 500 에러 페이지를 상상해보십시오. 이러한 에러페이지 중 하나와 마주쳤을 때, 빨리 실패시키는 간단한 방법으로는 페이지가 로딩될때마다, 페이지 타이틀 혹은 신뢰할 수 있는 포인트 (예를 들면 h1 태그) 의 컨텐츠를 체크해보는 것입니다. PageObject Modeling을 하고있는 경우, 이러한 체크를 클래스 생성자 혹은 페이지 로딩이 예상되는 포인트에서 해볼 수 있습니다. 경우에 따라서는 HTTP code가 브라우저의 에러 페이지에 직접적으로 나타나는 경우도 있습니다. WebDriver를 사용하여 이것을 읽어들인 후 디버그 출력으로 확인해볼 수도 있습니다.

Web페이지 자체를 확인하는 것은 WebDriver의 이상적인 관습이라 할 수 있으며, WebDriver는 유저의 웹사이트를 확인하는 방법을 표현하고, 주장할 수 있습니다.

HTTP status code를 확인하기 위한 좀 더 고급기술은, 프록시를 사용하여 Selenium RC의 동작을 복제하는 것입니다. WebDriver API는 브라우저의 프록시를 설정하는 기능을 제공합니다. Web서버간의 통신되는 request의 컨텐츠를 프로그램에서 조작할 수 있는 프록시가 몇가지 있습니다. 프록시를 사용한다면, Redirect response code로의 응답방법도 수정할 수 있스빈다. 게다가 모든 브라우저가 WebDriver에서 response code를 확인할 수 있도록 하고 있는 것은 아니기 때문에, 프록시를 사용하여 확인하는 방법을 선택한다면, 모든 브라우저에서 확인이 가능할 것 입니다.

Gmail, email and Facebook logins

여러가지의 이유로 인하여, WebDriver를 사용하여 Gmail, Facebook등의 사이트에 로그인하는 것은 추천하지 않습니다. 이 사이트의 사용조건 (계정이 블록처리 될 가능성이 있음)에 위반되는 것과는 별도로, 신뢰성이 없습니다.

바람직한 방법으로는, 메일 프로아빙더가 제공하는 API를 사용하는 것과, 혹은 Facebook의 경우, 테스트 계정이나 테스트 친구 등을 생성하기 위한 API를 공개하는 개발자 서비스를 이용하는 것입니다. API의 사용은 조금 어려운 작업으로 보일지도 모르겠습니다만, 속도, 신뢰성 및 안정성이 훌륭합니다. 또한 API가 변경될 가능성은 거의 없습니다만, 웹페이지와 HTML locator는 빈번하게 변경되기 때문에 Test framework를 수정해야야할 필요가 생깁니다.

테스트의 임의의 실행 시점에서, WebDriver를 이용하여 서드파티의 사이트에 로그인하면, 테스트가 길어지기 때문에 테스트가 실패할 가능성이 높아집니다. 일반적으로 테스트가 길어지면 질수록 실패할 확률이 높아지고, 신뢰성이 떨어집니다.

W3C 규악을 준수하는 WebDriver의 구현은, 서비스 거부 공격(Denial of Service attacks) 을 경감하기 위해 navigator 오브젝트에 WebDriver 프로퍼티를 추가합니다.

Test dependency

테스트 의존관계

자동테스트에 관한 일번적인 생각과 오해는, 특정 테스트의 순서에 관한 것에서부터 비롯됩니다. 테스트는 임의 의 순서로 실행할 수 있어야하며, 성공하기 위해, 완료하기 위해 다른 테스트케이스에 의존해서는 안됩니다.

Performance testing

보통 Selenium과 WebDriver를 사용한 성능테스트는 권장하지 않습니다. 그것이 불가능해서가 아니라, 성능 테스트에 최적화되어있지 않아 좋은 결과를 얻을 수 없기 때문입니다.

유저의 동작을 표현하면서 성능 테스트를 진행하는 것이 이상적이라고 생각할지도 모릅니다만, WebDriver 테스트 스위트는, 내외부로부터 많은 위험요소가 있습니다. 예를 들어 브라우저의 동작속도, HTTP 서버의 속도, Javascript 또는 CSS를 호스팅하는 서드파티 서버의 응답속도, WebDriver 구현 자체의 계측 성능 패널티 등등, 이러한 포인트가 안정적이지 않을 때, 결과가 바뀝니다. 웹사이트의 성능과 외부 리소스의 성능의 차이를 구별하는 것은 매우 어렵습니다. 브라우저에서 WebDriver를 사용하는 것, 특히 스크립트를 실행하는 경우 성능의 저하를 파악하는 것이 어렵습니다.

Selenium을 이용하여 성능 테스트를 해보고자 하는 또 다른 유혹으로는, 시간의 절약 입니다. 기능 테스트와 성능테스트를 동시에 실행합니다. 단, 기능 테스트와 성능테스트는 서로 목적이 다릅니다. 기능을 테스트하기 위해 테스터는 인내심을 가지로 로딩을 기다려야하맂도 모릅니다만, 이것은 성능 테스트의 결과를 애매하게 만듭니다. 그 반대도 마찬가지 입니다.

웹사이트의 성능을 개선하기 위해서는 개선해야할 포인트를 파악하기 위해 환경의 차이에 관계없이, 전체적인 성능을 분석하여 빈약한 코드퀄리티, 리소스의 퍼포먼스 (CSS나 Javascript)를 특정해야할 필요가 있습니다. 이러한 것들을 할 수 있는 성능테스트 도구는 이미 존재하며, 개선을 제안할 수 있는 레포트와 분석결과도 제공합니다.

사용할 수 있는 오픈소스 도구의 예시로는 JMeter가 있습니다.

흔히 알려진 웹 크롤링과 같은 링크를 수집하고 페이지 전체를 풀스캔하여 정보를 수집하는 것의 통칭.

WebDriver를 사용하여 링크를 스파이더링하는 것은 실행할 수 없어서가 아니라, 제일 이상적인 도구가 아니기 때문에 추천드릴 수 없습니다. WebDriver의 기동에는 시간이 필요하며, 테스트의 표현방법에 다라서는 페이지에 도달하여 DOM을 스캔하기 위해서는 수초에서 1분까지 걸리는 경우도 있습니다.

때문에 WebDriver를 사용하는 대신에 curl 커맨드를 실행하던지, BeautifulSoup 등의 라이브러리를 사용하여 , 브라우저의 생성이나 페이지의 이동에 의존하지 않기 때문에, 시간을 대폭 절약할 수 있습니다. 이것은 WebDriver를 사용하지 않는 것으로 시간을 아낄 수 있습니다.

Two Factor Authentication

2FA 라고 알려진 2단계인증은 Google Authenticator, Microsoft Authenticator, 등의 Authenticator 모바일 어플리케이션을 이용하거나 혹은 SMS, 이메일을 이용한 인증을 통해 One-Time Password (OTP)를 생성하는 인증 메커니즘입니다. 2FA를 Seamless하게 자동화하는 것은 Selenium의 큰 과제이기도 합니다. 이 프로세스를 자동화하는 방법은 몇가지가 있습니다. 그러나 Selenium 테스트 상에 별도의 레이어로 실행되는 것이며 안전하지도 않습니다. 따라서 2FA 자동화를 하지 않는 것이 바람직합니다.

2FA 체크를 우회하는 것에는 몇가지 선택지가 있습니다.

  • 테스트 환경에서 특정 유저의 2FA를 무효화하고, 이것을 유저 자격정보를 자동화하여 사용할 수 있도록 합니다.
  • 테스트 환경에서 2FA를 무효화합니다.
  • 특정 IP로부터 로그인하는 경우에 2FA를 무효화합니다. 이렇게 한다면 테스트 머신의 IP를 설정하여 회피할 수 있습니다.
profile
QA Engineer

0개의 댓글