Child pages
  • Selenium과 Cucumber를 활용해 웹 테스트 자동화하기 3 - 브라우저 재사용
Skip to end of metadata
Go to start of metadata
Selenium을 활용해 웹 테스트를 할 때 가장 많은 비용이 발생하는 부분이 브라우저를 시작하는 부분이다. 실제 테스트는 1,2초 걸리지 않는데 브라우저 시작하는데 2,3초가 소요된다. 이 같은 단점을 보완하기 위해 JUnit의 BeforeClass와 AfterClass 어노테이션을 활용해 브라우저 시작 횟수를 최소화할 필요가 있다. 그렇지 않으면 전체 테스트를 하는데 상당히 많은 시간이 걸린다.

여기서부터 또 다시 삽질 시작. JUnit을 활용해 실행하기 때문에 BeforeClass와 AfterClass가 당연히 동작하리라 생각하고 브라우저 시작 횟수를 최소화하는 전략을 세웠다. 그런데 이게 왠일... 당연히 동작하리라 생각했던 BeforeClass와 AfterClass가 실행되지 않는다. 로그를 찍고 생 난리를 쳐봤지만 확실히 동작하지 않는다. 테스트를 해보지 않았지만 JUnit의 Before와 After 어노테이션도 정상 동작하지 않는다는 문서를 봤다. 이런 제길..

해결 방법을 찾기 위해 검색 시작. 그러다가 우연히 https://github.com/cucumber/cucumber-jvm/pull/295을 찾았고, 새로운 접근 방식으로 이슈를 해결해서 BeforeClass와 AfterClass가 필요없으니 이슈를  종료한단다. 이건 또 무슨 소리. 어디를 찾아봐도 해결책이 없는데 이슈를 해결한다니 황당할 따름... 이 이슈 글을 찬찬히 읽어 보니 https://github.com/cucumber/cucumber-jvm/commit/5046b9b59c4e2ddc416448e4d3045121bd642de7에 소스 코드 추가해서 문제를 해결했단다. cucumber-jvm 예제에 포함했다고 하니 소스 코드 다운 받아서 분석하기 시작했다. cucumber-jvm 소스 코드는 https://github.com/cucumber/cucumber-jvm에서 볼 수 있다.

이 소스 코드를 로컬로 가져온 후 examples/java-webbit-websockets-selenium 프로젝트를 빌드해 소스 코드를 분석해 보니 WebDriver에서 제공하는 EventFiringWebDriver를 상속하는 새로운 WebDriver 클래스를 생성해 해결하고 있다. 

 

public class SharedDriver extends EventFiringWebDriver {
    private static final WebDriver REAL_DRIVER = new ChromeDriver();
    private static final Thread CLOSE_THREAD = new Thread() {
        @Override
        public void run() {
            REAL_DRIVER.close();
        }
    };

    static {
        Runtime.getRuntime().addShutdownHook(CLOSE_THREAD);
    }

    public SharedDriver() {
        super(REAL_DRIVER);
    }

    @Override
    public void close() {
        if(Thread.currentThread() != CLOSE_THREAD) {
            throw new UnsupportedOperationException("You shouldn't close this WebDriver. It's shared and will close when the JVM exits.");
        }
        super.close();
    }

    @Before
    public void deleteAllCookies() {
        manage().deleteAllCookies();
    }

    @After
    public void embedScreenshot(ScenarioResult result) {
        try {
            byte[] screenshot = getScreenshotAs(OutputType.BYTES);
            result.embed(screenshot, "image/png");
        } catch (WebDriverException somePlatformsDontSupportScreenshots) {
            System.err.println(somePlatformsDontSupportScreenshots.getMessage());
        }
    }
}

위와 같이 새로운 WebDriver를 구현한 후 이 WebDriver를 각 StepDefinitions 클래스에 의존성 주입(DI)을 해서 사용하는 방식을 취하고 있었다.

 

public class NavigationStepdefs {
    private final WebDriver webDriver;

    public NavigationStepdefs(SharedDriver webDriver) {
        this.webDriver = webDriver;
    }

    @Given("^I am on the front page$")
    public void i_am_on_the_front_page() {
        webDriver.get("http://localhost:" + ServerHooks.PORT);
    }
}

이와 같이 의존성 주입을 통해 SharedDriver를 공유하기 때문에 인스턴스는 하나로 사용 가능하고, SharedDriver 클래스가 가지는 WebDriver의 인스턴스 또한 하나이다. 이는 테스트를 실행하는 JVM 상에 단지 한 개의 WebDriver만 가짐으로써 재사용할 수 있도록 한다. 위 코드에서 더 좋은 점은 모든 쿠키를 @Before 어노테이션에서 초기화하기 때문에 각 테스트마다 초기화된 상태에서 테스트가 가능하도록 할 수 있다는 것이다.

이 예제를 참고해 테스트하고 있던 소스 코드에 적용했다. 긴장되는 마음으로 실행했더니 StepDefinitions 소스 코드에 기본 생성자가 없어 생성할 수 없단다. 그렇지 않아도 소스 코드를 적용하면서 
SharedDriver 인스턴스를 의존성 주입하는 놈이 있어야 되는데 아무런 설정도 없는 것이 의아했다. 그래서 또 다시 삽질 시작. 이번 삽질은 생각보다 빨리 끝났다. 에러 메시지에 해결책이 있었다.

 

cucumber.runtime.CucumberException: class archeage.account.LoginPageStepDef doesn't have an empty constructor. If you need DI, put cucumber-picocontainer on the classpath

 

어쩐지 의존성 주입하는 놈이 없더라니 picocontainer가 그 역할을 하는군이라 생각하면서 pom.xml 파일에 다음 설정 추가했다.

 

<dependency>
     <groupId>info.cukes</groupId>
     <artifactId>cucumber-picocontainer</artifactId>
     <version>${cucumber.version}</version>
</dependency>

"mvn eclipse:eclipse"를 실행한 후 다시 실행했더니 된다. 브라우저 하나로 전체 Feature가 잘되고, 쿠키 초기화도 잘 된다. 삽질을 좀 하기는 했지만 내가 원하는 형태로 테스트를 진행할 수 있게 되었다. 이제는 이 기반으로 자동화된 테스트만 계속해서 추가하면 되겠다.

  • No labels