Test Practice using Selenium - 1

Dahun Yoo·2022년 3월 5일
0

QA or Test

목록 보기
23/38
post-thumbnail

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

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

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


Test Practice

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

가장 좋은 케이스에 관한 메모

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

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

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

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


Overview of Test Automation

테스트 자동화에 대해

일단 정말로 브라우저를 사용할 필요가 있는지 스스로 질문해보는 것부터 시작해야합니다. 어느 시점에서 복잡한 Web Application으로 작업하고 있는 경우, 아마도 브라우저를 실행시켜 실제로 테스트할 필요가 있을 것입니다.

단, Selenium 테스트 등 기능적인 End to End Test의 실행에는 비용이 소모됩니다. 더욱이 그러한 것들은 통상적으로, 효과적으로 실행하기 위해 적절한 인프라가 적용되어있을 필요가 있습니다. Unit test와 같은 좀 더 가벼운 테스트 방법들을 사용하거나, 하위 레벨의 방법들을 사용하여 테스트할 수 있는지 항상 스스로 질문해야합니다.

웹브라우저의 테스트를 시작하기로 결정하고, Selenium 환경에서의 테스트를 시작할 수 있게된다면, 보통은 아래 3 Step을 조합하여 실행합니다.

  • 데이터를 설정한다.
  • 독립적인 일련의 액션을 실행한다.
  • 결과를 평가한다.

이러한 순서는 가능한 짧게 구성해야하빈다. 대부분의 경우 한 번 혹은 두 번의 조작으로 충분합니다. 브라우저의 자동화는 불안정 하다는 평가가 있습니다만, 실제로는 유저가 빈번하게 많은 것을 조작하는 경우가 많이 있기 때문입니다. 글의 후반부에서는 특히 브라우저와 WebDriver간의 경쟁 조건을 극복 (overcome race condition) 하는 방법에 관한, 테스트에서 명백히 간헐적으로 발생하는 문제에 대해 완화하는 기술에 대해 기재해봅니다.

테스트를 짧게하여 대체수단이 전혀 없는 경우에만 웹브라우저를 사용하는 것으로 불안정함을 최소화하여 많은 테스트를 실행할 수 있을 것입니다.

Selenium 테스트의 명확한 이점은, 유저의 관점에서, 백엔드에서 프론트엔드까지, 어플리케이션의 모든 Component를 테스트하는 고유한 기능에 있습니다. 즉, 기능 테스트는 실행에 많은 비용이 소모될 가능성이 있습니다만, 동시에 비즈니스에 불가결한 대규모 테스트도 포함됩니다.

Testing requirements

테스트 요구사항

앞서 말씀드렸듯, Selenium 테스트의 실행에는 큰 비용이 소모되는 경우가 있습니다. 어느정도까지는 테스트를 실행시킬 브라우저에 의존합니다만, 역사적으로 브라우저의 동작은 매우 다양하기 때문에, 여러 개의 브라우저에 대한 Cross browser Test를 목표로하는 경우가 많습니다.

Selenium을 사용한다면 여러 OS상에서의 여러 브라우저에 대해 같은 커맨드를 실행할 수 있습니다만, 모든 가능한 브라우저, 해당 브라우저들의 다른 버전들 및 그러한 브라우저들을 실행시키는 많은 OS의 열거는 쉬운일은 아닙니다.

Let's start with an example

Larry는 유저가 커스텀 유니콘을 주문할 수 있는 웹사이트를 만들었습니다.
일반적인 워크플로우 (Happy path라고 부르는) 는 다음과 같습니다.

  • 계정을 생성한다.
  • 유니콘을 설정한다.
  • 쇼핑 카트에 유니콘을 추가한다.
  • 체크아웃하여 결제한다.
  • 유니콘에 대한 피드백을 보낸다.

이러한 모든 조작을 실행하기 위해 하나의 커다란 Selenium 스크립트를 작성하는 것은 매력적이라고 할 수 있습니다. 그러한 유혹을 뿌리쳐야합니다. 스크립트를 작성하게 된다면,

  1. 시간이 걸린다.
  2. 페이지 렌더링의 타이밍에 관한 일반적인 문제가 발생한다.
  3. 실패하는 경우, 간결하게 단박에 파악할 수 있는, 무엇이 문제였는지 진단하는 방법

과 같은 문제에 마주치게 될 것입니다.

위 시나리오를 테스트하기 위한 바람직한 전략으로는, 일련의 독립되어있는 빠른 테스트로 만들기 위해 테스트를 쪼개는 것입니다. 각 테스트에는 하나의 이유 에 의존해야합니다.

2번째 스텝에 있는, 유니콘의 구성을 테스트하고자 합니다. 다음의 액션을 실행합니다.

  • 계정을 생성한다.
  • 유니콘을 설정한다.

위 순서의 나머지를 생략하고 있는 것에 대해 주의해주세요. 이 순서를 완료한 다음, 다른 작은 개별적인 테스트케이스로 나머지 워크플로우를 테스트합니다.

시작할 때에는, 계정을 생성할 필요가 있습니다. 여기에는 몇가지 선택지가 있습니다.

  • 기존의 계정을 사용할 것인가.
  • 새로운 계정을 생성할 것인가.
  • 설정을 시작하기 전에 고려가 필요한, 유저의 특별한 설정값은 있는지?

이러한 질문들에 대한 답변과는 관계없이, 테스트의 "데이터 준비(date setup)" 부분의 일부로 만든다면 해결할 수 있습니다. Larry가, 유저 (혹은 누구던지)가 계정의 생성 및 갱신할수 있는 API를 공개하고 있는 경우, 이것들을 사용하여 답변할 수 있을 것입니다. 가능하다면 자격정보를 사용하여 로그인할 수 있는 유저 정보가 "준비되어있는 경우" 에만 브라우저를 실행합니다.

각 워크플로우에 대한 테스트가 유저 계정 생성으로 시작되면, 각 테스트의 실행에는 몇초가 더 추가됩니다. API호출과 DB핸들링은 브라우저를 실행한다던지, 적절한 페이지로 이동한다던지, form을 클릭하여 데이터 송신을 기다린다던지 하는 코스트가 높은 프로세스를 이용하지 않아도 되는 방법 입니다.

이상적으로는 1줄의 코드에 이러한 설정단계를 처리할 수 있습니다. 이것은 브라우저를 실행시키기 전에 실행되어야 합니다.

// Create a user who has read-only permissions--they can configure a unicorn,
// but they do not have payment information set up, nor do they have
// administrative privileges. At the time the user is created, its email
// address and password are randomly generated--you don't even need to
// know them.
User user = UserFactory.createCommonUser(); //This method is defined elsewhere.

// Log in as this user.
// Logging in on this site takes you to your personal "My Account" page, so the
// AccountPage object is returned by the loginAs method, allowing you to then
// perform actions from the AccountPage.
AccountPage accountPage = loginAs(user.getEmail(), user.getPassword());
  

예상하신대로, UserFactory 를 확장하여 createAdminUser()createUserWithPayment() 등의 메소드를 제공합니다. 중요한 것은, 위 2줄의 코드는 이 테스트의 최종 목표인 유니콘의 구성을 도와준다는 점입니다.

PageObject Model 은 추후에도 소개해드리겠습니다만, 여기에서는 개념을 먼저 소개해드립니다.

테스트는, 사이트의 페이지를 context 내의 유저의 관점으로부터 실행하는 액션으로 구성할 필요가 있습니다. 이러한 페이지는 객체로써 보존되어 웹페이지가 어떻게 구성되어있는지, 액션은 어떻게 실행되는지에 관한 특정한 정보들이 포함되어 있습니다.

어떠한 유니콘이 필요합니까? 핑크색 유니콘이 필요할지도 모릅니다만, 반드시 그렇지는 않습니다. 보라색은 매우 인기가 있습니다. 유니콘에게 선글라스가 필요합니까? 타투는 어떻습니까?

위와 같이 선택하는 것은 어렵습니다만, 테스터에게는 최대의 관심사입니다. 발송센터에서 적절한 유니콘을 적절한 사람에게 발송하는 것을 확인할 필요가 있습니다.

이 단계에서는 버튼, 필드, 드롭다운, 라디오 버튼 혹은 Web form에 대해서는 설명하지 않습니다. 또한 테스트하면 안됩니다! 유저가 문제를 해결하고자 하는 것 처럼 코드를 작성하고자 합니다. 이것을 실행하는 한 가지 방법은 아래와 같습니다. (앞의 코드에서 계속)

// The Unicorn is a top-level Object--it has attributes, which are set here. 
// This only stores the values; it does not fill out any web forms or interact
// with the browser in any way.
Unicorn sparkles = new Unicorn("Sparkles", UnicornColors.PURPLE, UnicornAccessories.SUNGLASSES, UnicornAdornments.STAR_TATTOOS);

// Since we are already "on" the account page, we have to use it to get to the
// actual place where you configure unicorns. Calling the "Add Unicorn" method
// takes us there.
AddUnicornPage addUnicornPage = accountPage.addUnicorn();

// Now that we're on the AddUnicornPage, we will pass the "sparkles" object to
// its createUnicorn() method. This method will take Sparkles' attributes,
// fill out the form, and click submit.
UnicornConfirmationPage unicornConfirmationPage = addUnicornPage.createUnicorn(sparkles);
  

유니콘의 설정이 완료된다면, 스텝 3을 진행하여 유니콘이 실제로 기능하는 것을 확인할 필요가 있습니다.

// The exists() method from UnicornConfirmationPage will take the Sparkles 
// object--a specification of the attributes you want to see, and compare
// them with the fields on the page.
Assert.assertTrue("Sparkles should have been created, with all attributes intact", unicornConfirmationPage.exists(sparkles));
  

테스터는 아직 이 코드로 유니콘에 대해 말하고 있을 뿐입니다. 버튼이나 로케이터, 브라우저 컨트롤도 없습니다. Larry가 다음주, Ruby on Rails를 하고싶어져서, Fortran 프론트엔드를 사용하여 최신의 Haskell 바인딩으로 사이트 전체를 다시 만든다고 하여도, 어플리케이션을 모델화 하는 이 방법으로 인해, 테스트 코드를 바꾸지 않고 테스트 코드를 유지할 수 있을 것입니다.

PageObject 는, 사이트의 재구성하는 레벨이기 때문에 UI 변경등으로 인해 약간의 유지보수가 필요합니다만, 테스트는 그대로 유지할 수 있습니다. 이 기본적인 설계방법을 채용하는 것으로, 브라우저를 조작하는 최소한의 순서로 워크플로우를 진행할 수 있다고 생각할 수 있습니다.
다음 워크플로우에서는 유니콘을 쇼핑카트에 추가합니다. 카트의 상태가 적절히 유지되는 것을 확인하기 위해 아마도 이 테스트를 몇번이고 반복재생할 필요가 있을 것이비낟. 테스트를 시작하기 전에 카트에 여러개의 유니콘이 들어있나요? 쇼핑카트에는 몇개까지 넣을 수 있나요? 같은 이름이나 기능으로 여러 유니콘을 설정하는 경우에는 레이아웃 등이 꺠지진 않나요? 기존의 있던 것을 유지하는 경우인가요 아니면 새로운 것을 추가해야하는 것인가요?

워크프로우를 이동할 때 마다 유저 계정을 생성하고, 유저로 로그인하고, 유니콘을 설정하고 싶진 않다고 생각하빈다. 이상적으로는 API 또는 데이터베이스를 이용하여 계정을 생성하고, 유니콘을 사전등록할 수 있게하는 것이 좋습니다. 그 후에 유저로 로그인하고 넣고자 하는 유니콘을 찾아 장바구니에 추가하기만 하면 됩니다.

To automate or not to automate?

자동화할 것인가, 자동화하지 않을 것인가?

자동화는 항상 유리할까요? 테스트케이스의 자동화를 언제 결정할 필요가 있을까요?

테스트케이스를 자동화하는 것은 반드시 유리하다고는 할 수 없습니다. 매뉴얼 테스트가 좀 더 적절한 경우도 있습니다. 예를 들어, 근시일내에 어플리케이션의 유저 User Interface가 대폭으로 변경될 예정인 경우, 자동화 코드를 다시 써야할 필요가 있을 수 있습니다. 또한, 테스트 자동화를 구축하는데에 시간이 모자라는 경우도 있습니다. 단기적으로는 매뉴얼 테스트가 효과적이기도 합니다. 어플리케이션의 라이프사이클이 잛을 경우, 현재 이용할 수 있는 테스트의 자동화가 아닌, 그 기간 내에 어떻게든 테스트를 실시해야만합니다. 이런 경우라면 매뉴얼 테스트가 적절한 솔루션입니다.


Design patterns and Development strategies

디자인 패턴과 개발 전략

Overview

시간이 흘러감에 따라, 프로젝트는 많은 테스트가 실행되는 경향이 있습니다. 테스트의 총 개수가 증가하면, 코드를 변경시키는 것이 어렵게됩니다. 어플리케이션이 정상적으로 동작하여도, 한 번의 단순한 변경으로 여러 테스트가 실패할 가능성이 있습니다. 이러한 문제를 피할 수 없는 경우도 있습니다만, 문제가 발생한 경우에는 가능한 빠르게 문제를 해결하고 어플리케이션을 다시 동작시켜야할 필요가 있습니다.
다음의 디자인 패턴과 개발방법론은, 테스트의 작성과 유지보수를 쉽게하기 위해 WebDriver에서 사용되고 있습니다. 이러한 내용은 여러분께 도움이될지도 모르빈다.

  • DomainDrivenDesign :어플리케이션의 EndUser(실사용자) 의 언어로 테스트를 표현합니다.
  • PageObject : Web 어플리케이션의 UI를 단순추상화하여 표현합니다.
  • LoadableComponent : PageObject를 컴포넌트로써 모델링합니다.
  • BotStyleTests : PageObject가 추천하는 객체기반의 접근방법이 아닌, 명령어 기반의 접근방법을 이용하여 테스트를 자동화합니다.

Loadable Component

What Is It?

LoadableComponent는, PageObject의 작성의 부담을 경감할 목적을 갖는 Base Class입니다. 이것은 페이지가 로드될 것을 보장 하는 표준적인 방법을 제공하며, 페이지가 로드에 실패되었을 때, 디버그하기 좋은 Hook을 제공합니다. 이것을 사용하여 테스트의 정형적인 코드의 양을 줄일 수 있습니다. 이것으로 인해 테스트의 보수작업이 조금은 쉬워집니다.

현재 Selenium 2의 일부로 구현되어있는 Java의 일부를 기재하였으나, 사용되는 접근방법 자체는 어떠한 언어로도 구현할 수 있을정도로 간단합니다.

Simple Usage

모델링할 UI를 예로, 새로운 issue 페이지를 확인해주세요. 테스트 작성자의 관점에서, 이것은 새로운 issue를 만들 수 있는 서비스를 제공합니다. 기본적인 PageObject는 아래와 같습니다.

package com.example.webdriver;

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

public class EditIssue {

  private final WebDriver driver;

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

  public void setSummary(String summary) {
    WebElement field = driver.findElement(By.name("summary"));
    clearAndType(field, summary);
  }

  public void enterDescription(String description) {
    WebElement field = driver.findElement(By.name("comment"));
    clearAndType(field, description);
  }

  public IssueList submit() {
    driver.findElement(By.id("submit")).click();
    return new IssueList(driver);
  }

  private void clearAndType(WebElement field, String text) {
    field.clear();
    field.sendKeys(text);
  }
}

이것을 LoadableComponent로 변환하기 위해서는 기본형으로 설정만하면 될 것 입니다.

public class EditIssue extends LoadableComponent<EditIssue> {
  // rest of class ignored for now
}

위 코드는 조금 이상할지도 모르겠습니다만, 이것은 클래스가 Editissue 페이지를 로드하는 LoadableComponent를 나타내는 것을 의미합니다.

이 BaseClass를 extends하는 것으로 인해, 2개의 새로운 메소드를 구현할 필요가 있습니다.

  @Override
  protected void load() {
    driver.get("https://github.com/SeleniumHQ/selenium/issues/new");
  }

  @Override
  protected void isLoaded() throws Error {
    String url = driver.getCurrentUrl();
    assertTrue("Not on the issue entry page: " + url, url.endsWith("/new"));
  }

load 메소드는 페이지에 이동할 때마다 사용되며 isLoaded 메소드는 해당 페이지에 잘 이동했는지 판단하기 위해 사용됩니다. 이 메소드는 boolean 값을 리턴할 필요가 있는 것 처럼 보여집니다만, 그 대신에 JUnit의 Assert클래스를 사용하여 일련의 assertion(판정)을 진행합니다. assertion은 많든 적든 상관없습니다. 이 assertion을 사용하는 것으로 클래스의 유저에게 테스트의 디버깅에 사용할 수 있는 명확한 정보를 제공해줄 수 있습니다.

package com.example.webdriver;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;

import static junit.framework.Assert.assertTrue;

public class EditIssue extends LoadableComponent<EditIssue> {

  private final WebDriver driver;
  
  // By default the PageFactory will locate elements with the same name or id
  // as the field. Since the summary element has a name attribute of "summary"
  // we don't need any additional annotations.
  private WebElement summary;
  
  // Same with the submit element, which has the ID "submit"
  private WebElement submit;
  
  // But we'd prefer a different name in our code than "comment", so we use the
  // FindBy annotation to tell the PageFactory how to locate the element.
  @FindBy(name = "comment") private WebElement description;
  
  public EditIssue(WebDriver driver) {
    this.driver = driver;
    
    // This call sets the WebElement fields.
    PageFactory.initElements(driver, this);
  }

  @Override
  protected void load() {
    driver.get("https://github.com/SeleniumHQ/selenium/issues/new");
  }

  @Override
  protected void isLoaded() throws Error {
    String url = driver.getCurrentUrl();
    assertTrue("Not on the issue entry page: " + url, url.endsWith("/new"));
  }
  
  public void setSummary(String issueSummary) {
    clearAndType(summary, issueSummary);
  }

  public void enterDescription(String issueDescription) {
    clearAndType(description, issueDescription);
  }

  public IssueList submit() {
    submit.click();
    return new IssueList(driver);
  }

  private void clearAndType(WebElement field, String text) {
    field.clear();
    field.sendKeys(text);
  }
}

위의 설명들은 조금 믿기 어려웠을지도 모릅니다. 지금까지 말씀드린 것들 중 한 가지는, 페이지에 이동하는 방법에 관한 정보를, 페이지 자체에서 캡슐화하는 것입니다. 즉, 이 정보는 코드베이스 전체에뿌리지 않았다는 것입니다. 이것은 테스트 자체에서 실행시킬 수 있는 것과 마찬가지입니다.

EditIssue page = new EditIssue(driver).get();

위 호출로 인하여, 드라이버는 해당 페이지로 이동할 수 있습니다.

Nested Components

LoadableComponents는 다른 LoadableComponents와 조합하여 사용하면, 좀 더 편리해집니다. 아래의 예시를 사용하면, "edit issue" 페이지를 프로젝트의 웹사이트 내의 컴포넌트로써 표시할 수 있습니다. (즉, 해당 사이트의 탭에서 접근합니다.) 또한, issue를 보고하기 위해서는 로그인할 필요가 있습니다. 이것을 nested된 컴포넌트의 트리로 모델화할 수 있습니다.

 + ProjectPage
 +---+ SecuredPage
     +---+ EditIssue

코드에서는 어떻게 알 수 있을까요? 우선 각각의 논리 컴포넌트에는 독자적인 클래스가 있습니다. 각각의 load 메소드는 부모 클래스의 get 을 호출합니다. 위 EditIssue 클래스에 더해서, 아래와 같이 표현할 수 있을 것입니다.

ProjectPage.Java

package com.example.webdriver;

import org.openqa.selenium.WebDriver;

import static org.junit.Assert.assertTrue;

public class ProjectPage extends LoadableComponent<ProjectPage> {

  private final WebDriver driver;
  private final String projectName;

  public ProjectPage(WebDriver driver, String projectName) {
    this.driver = driver;
    this.projectName = projectName;
  }

  @Override
  protected void load() {
    driver.get("http://" + projectName + ".googlecode.com/");
  }

  @Override
  protected void isLoaded() throws Error {
    String url = driver.getCurrentUrl();

    assertTrue(url.contains(projectName));
  }
}

and SecuredPage.java

package com.example.webdriver;

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

import static org.junit.Assert.fail;

public class SecuredPage extends LoadableComponent<SecuredPage> {

  private final WebDriver driver;
  private final LoadableComponent<?> parent;
  private final String username;
  private final String password;

  public SecuredPage(WebDriver driver, LoadableComponent<?> parent, String username, String password) {
    this.driver = driver;
    this.parent = parent;
    this.username = username;
    this.password = password;
  }

  @Override
  protected void load() {
    parent.get();

    String originalUrl = driver.getCurrentUrl();

    // Sign in
    driver.get("https://www.google.com/accounts/ServiceLogin?service=code");
    driver.findElement(By.name("Email")).sendKeys(username);
    WebElement passwordField = driver.findElement(By.name("Passwd"));
    passwordField.sendKeys(password);
    passwordField.submit();

    // Now return to the original URL
    driver.get(originalUrl);
  }

  @Override
  protected void isLoaded() throws Error {
    // If you're signed in, you have the option of picking a different login.
    // Let's check for the presence of that.

    try {
      WebElement div = driver.findElement(By.id("multilogin-dropdown"));
    } catch (NoSuchElementException e) {
      fail("Cannot locate user name link");
    }
  }
}

EditIssue의 load 메소드는 아래와 같이 됩니다.

  @Override
  protected void load() {
    securedPage.get();

    driver.get("https://github.com/SeleniumHQ/selenium/issues/new");
  }

이것은 컴포넌트가 모두 상호 간 nested 되어있는 것을 나타냅니다. Editissue에서 get() 을 호출한다면, 그 모든 의존관계들도 호출되는 것입니다.
사용 예시:

public class FooTest {
  private EditIssue editIssue;

  @Before
  public void prepareComponents() {
    WebDriver driver = new FirefoxDriver();

    ProjectPage project = new ProjectPage(driver, "selenium");
    SecuredPage securedPage = new SecuredPage(driver, project, "example", "top secret");
    editIssue = new EditIssue(driver, securedPage);
  }

  @Test
  public void demonstrateNestedLoadableComponents() {
    editIssue.get();

    editIssue.setSummary("Summary");
    editIssue.enterDescription("This is an example");
  }
}

테스트에서 Guideberry 등의 라이브러리를 사용하고 있다면, PageObjects의 설정을 생략하여 좀 더 알기쉬운 테스트를 작성할 수 있습니다.

Bot Pattern

PageObjects는, 테스트에서 중복을 줄이기 위한 편리한 방법입니다만, 팀이 쾌적하게 팔로잉할 수 있는 방법은 아닐 수 있습니다. 다른 방법으로는 좀 더 "커맨드와 비슷한" 스타일의 테스트가 있습니다.

Bot 은 Selenium API에 대한 액션을 추상화한 개념입니다. 즉, 커맨드가 어플리케이션에 대해 제대로 동작하고 있지 않을 때, 간단히 변경할 수 있습니다.

public class ActionBot {
  private final WebDriver driver;

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

  public void click(By locator) {
    driver.findElement(locator).click();
  }

  public void submit(By locator) {
    driver.findElement(locator).submit();
  }

  /** 
   * Type something into an input field. WebDriver doesn't normally clear these
   * before typing, so this method does that first. It also sends a return key
   * to move the focus out of the element.
   */
  public void type(By locator, String text) { 
    WebElement element = driver.findElement(locator);
    element.clear();
    element.sendKeys(text + "\n");
  }
}

이러한 것들의 추상화가 구현되고, 테스트에서 중복되는 부분이 특정된다면, Bot에서 PageObject를 계층화하여 구축해볼 수도 있을 것입니다.


Type of Testing

Selenium으로 실행할 수 있는 테스트의 유형

Acceptance Testing

인수 테스트는 기능 혹은 시스템이 고객(유저)의 기대와 요건을 만족하고 있는지 판단하기 위해 실시하는 테스트이비낟. 이 타입의 테스트는 통상적으로 고객의 협력 혹은 피드백이 관여합니다. 아래 질문에 대답하는 것으로 확인할 수 있을 것입니다.

올바른 제품을 만들고 있습니까?

Web 어플리케이션의 경우, 유저의 예상 동작에 대해 테스트의 자동화를 Selenium으로 직접 구현/실행하여 시뮬레이션해볼 수 있습니다. 이 시뮬레이션은, 본 문서에서 설명하고 있듯이, 기록/재생 혹은 Selenium에서 서포트하고 있는 여러 개발언어로 제작하여 실행해볼 수 있습니다.

주의 : 인수 테스트는 기능 테스트의 하위 유형이며 일부 유저는 이 두 유형을 비슷하게 설명하기도 합니다.

Functional Testing

이 유형의 테스트는, 기능 혹은 시스템이 문제없이 정상적으로 기능하고 있는지를 판단하기 위해 실시하는 테스트입니다. 시스템을 다방면의 각도에서 체크하여 모든 시나리오를 커버하고 있는지, 시스템이 실행해야할 것이 제대로 실행되고 있는지를 확인합니다. 아래 질문에 대답하는 것으로 그것을 확인해볼 수 있을 것입니다.

제품을 올바르게 만들고 있습니까?

이것은 통상적으로, 아래의 경우들을 포함합니다.

  • 테스트에서 에러 발생이 없음.
  • 사용가능한 방법으로 동작하고 있을 것.
  • 사용하기 쉬우면서 요구사항과 일치할 것. (인수 테스트와 비슷)

Web 어플리케이션의 경우, 기대하는 동작을 확인해보기 위해 Selenium으로 구현해볼 수 있습니다.

Performance Testing

테스트 명칭에서 알 수 있듯, 성능 테스트는 어플리케이션의 성능을 측정하기 위해 진행합니다.
성능 테스트는 크게 2가지 테스트로 나눌 수 있습니다.

Load Test

로드 테스트는, 정의된 여러가지 부하조건 (특정 수 이상의 유저가 동시에 접속하는 경우가 보통) 에서 어플리케이션이 어느정도 동작하는지 확인하는 테스트입니다.

Stress Test

스트레스 테스트는, 스트레스 조건 (혹은 어플리케이션에서 감당가능한 최대 부하 이상) 에서 어플리케이션이 어떻게 동작하는지 확인하는 테스트입니다.

일반적으로 성능테스트는, Selenium에서 테스트를 실행하여, 많은 유저가 Web 어플리케이션의 특정 기능을 동작시켜 어느정도 의미있는 측정값을 얻어낼 수 있습니다.
의미있는 지표를 얻어내기 위해서는 다른 도구를 사용하기도 하는데, 대표적으로는 JMeter 가 있습니다.

Web 어플리케이션의 경우, 측정하는 상세값에는 아래와 같습니다.

  • ThroughPut
  • Latency
  • Data loss
  • Individual component loading times

주의1 : 모든 브라우저에는 개발자 도구가 탑재되어있으며 개발자 도구에 퍼포먼스 탭이 내장되어있어, 해당 영역에서도 확인 가능합니다.
주의2 : 일반적으로 각각의 기능다위가 아닌 시스템 단위에서 측정되기 때문에 비기능 테스트 의 하위타입으로 분류합니다.

Regression Testing

회귀 테스트는 보통 변경 / 수정 / 기능의 추가 후 에 실시합니다.
변경으로 인하여 기존의 기능에 문제는 없는지 (Side-effect) 확인하기 위해 이미 실행한 이력이 있는 몇가지 테스트케이스를 다시 한 번 실행하는 테스트입니다.

재실행하는 테스트 세트는, 전체 혹은 일부로 진행하며 어플리케이션 및 개발팀에 따라 몇가지 다른 타입으로 진행할 수도 있습니다.

Test Driven Development (TDD)

테스트주도개발은, 테스트 타입이 아닌 테스트가 기능의 설계를 주도하는 반복적인 개발 방법론입니다.
각 사이클은 기능이 통과해야하는 Unit Testcase를 작성하는 것에서부터 시작합니다. (제일 처음 UT를 실행하면, 실패할 것입니다.)
그 후에 테스트에 합격할 수 있도록 개발을 시작합니다. 추후 다른 사이클에서 테스트를 재실행하여, 모든 테스트케이스에서 통과할 때까지 이 사이클을 반복합니다.

이것은 issue를 쉽게 찾아낼 수 있다는 사실에 착안하여, 어플리케이션의 개발 속도를 올리는 것을 목표로 합니다.

Behavior Driven Developement (BDD)

BDD는 어플리케이션의 개발에 모든 관계자를 참여시키기 위한, TDD를 기반으로한 반복적인 개발방법론입니다.

각 사이클은 몇가지 요구사항을 작성하는 것 부터 시작합니다. (TDD에 기반하기 떄문에, 테스트를 실행하면 실패할 것 입니다.) 그 다음으로, 실패하는 Unit Testcase를 작성하고, 이 testcase를 통과하는 제품을 개발하기 시작합니다.

이 사이클은 모든 유형의 테스트에 합격할때까지 반복합니다.

BDD에는 요구사항을 관계자가 알 수 있는 특정한 언어 가 사용됩니다. 모든 이해관계자가 이해할 수 있을 정도로 단순하며 표준적이고 명시적인 언어로 작성되지 않으면 안됩니다. 대부분의 BDD 툴에서는 Gherkin 이라는 언어가 사용됩니다.

인수 타이밍 시, TDD보다 더 많은 잠재적 이슈의 파악을 하기 위해, BDD의 목적은 각 이해관계자 간의 커뮤니케이션을 원활하게 하는 것입니다.

요구사양을 기재하고, 기재한 내용을 코드의 내용과 일치시키기 위해 CucumberSpecFlow 등 일련의 툴을 사용합니다.

Selenium에서는 이러한 툴이 이미 구축되어있으며, BDD에 기반하여 기재된 요구사항을 실행가능한 코드로 직접 변환하는 것을 통해, 이 프로세스를 한층 더 가속화시킬 수 있습니다.
대표적으로는 JBehave, Capybara, Robot Framework 등이 있습니다.

profile
QA Engineer

0개의 댓글