이직과 개인적으로 휴식이 필요해서 한동안 포스팅을 못했네요. 제가 최근에 실무에서 레거시 청산 혹은 UI 수정을 진행하면서 Postman, Katalon Studio, Selenium IDE를 거치면서 최종적으로 업무와 개인적인 일(게임 혹은 크롤링과 같은 개인적으로 관심이 가는 부분)에 사용하기 위해서 테스트 자동화와 크롤링을 조금 더 편하게 할 수 있는 프로젝트를 설계, 구축하고 있습니다.
아직 정리되지 않은 버전이지만 지금 업무를 진행하면서 많은 도움이 되었고 이정도면 블로그에 포스팅할 수 있겠다 싶어서 왜 만들게 되었는지, 그리고 어떻게 만들고 어떻게 활용할 수 있는지를 한 번 포스팅해보려고 합니다.
우선 프로젝트 설명에 앞서서 Postman(API), Katalon Studio(API + UI), Selenium IDE(UI) 를 거치면서 왜 프로젝트를 만들게 되었는지 그리고 어떤 불편함을 개선하고 싶었는지 한 번 설명해보겠습니다.
01. 방대한 레거시와 익숙하지 않은 프로젝트 환경
제가 이번에 진행하는 프로젝트는 C# 으로 구성되어 있습니다. 물론 이전 회사에서도 Python 2.7 (Webapp2) 기반의 프로젝트를 진행했었습니다. 아무래도 업무를 진행하다 보면 레거시를 개선하거나 코드를 수정하는 일은 피할 수 없는 것 같습니다. 물론, 레거시 프로젝트가 깔끔하거나 이미 테스트를 위한 환경 구축이 되어있다면 편하고 수정에 용이하겠지만 그렇다면 그것은 레거시가 아니겠죠?
우선 프로젝트에서 테스트 코드가 아닌 별도의 툴을 이용하려고 했던 이유는 기존 레거시 코드에서 테스트 코드 작성이 쉽지 않고 제가 많이 다루는 타입스크립트 혹은 자바가 아니었고,
제한된 시간안에서 진행해야 하는 업무가 많기 때문에 저의 불안한 C#, Python 실력을 제가 익숙한 언어나 툴을 통해서 보장받고 싶은 마음에 툴을 이용한 테스팅을 진행하게 되었습니다.
02. 커스텀하기 어려운 외부 툴 (Postman)
포스트맨으로는 주로 API 테스트를 진행했는데 이때 저는 리팩터링 업무 혹은 레거시 이관을 위한 업무를 진행할 때 많이 사용했습니다.
Test Script 를 통해서 로컬 혹은 레거시의 원본 API와 새로 정리하면서 작성하는 API의 결괏값을 체크하고 동일한지 체크할 수 있도록 구성해서 현재까지의 결과물이 잘 작성되었는지 중간중간 손쉽게 확인할 수 있습니다.
포스트맨에서 리팩토링을 진행 중인 API와 기존 사용 중인 API와 데이터 비교 샘플
// 테스트 코드를 통해서 API 호출이 완료되면 레거시 API 역시 호출해서 둘의 결과값을 비교한다.
// 물론 데이터까지 비교하는 기능도 있지만 타입만 비교하는 기능도 존재,
// 만약 새로운 API 에서 특정 프로퍼티가 삭제되었다면 로직을 추가해서 비교가 가능하기에 비교적 커스텀이 잘 된다.
const userId = pm.request.url.path[3];
const legacyUrl = `http://localhost:3000/users/v1/users/${userId}`;
pm.sendRequest(`${legacyUrl}`, function (err, response) {
const renewData = pm.response.json()
const legacyData = response.json()
pm.expect(renewData).to.eql(legacyData);
});
위의 테스트는 블로그상 실패처리를 하였고 실제 업무를 진행하면서는 에러 로그를 보면서 어떤 부분이 추가 혹은 삭제되었는지 아니면 어떤 값이 다른지를 API 호출로 체크해 API 응답 상태는 정상인지, 데이터에 대한 레거시와의 호환성 등을 체크할 수 있습니다.
하지만 치명적인 단점이 있었으나 디버깅을 하면서 상세데이터를 확인은 가능하나 서로의 어떤 데이터가 다른지 쉽게 확인할 수 없다는 점, 그리고 무엇보다도 에러 메시지가 친절하지 않다는 느낌이 많이 들었습니다.
그래도 해당 기능을 통해서 많은 API 의 기존 소스와 변경 중인 소스의 데이터를 확인하면서 업무를 진행했으나 사용하다 보니 response 데이터 비교, 에러 메시지 등의 변경이 어렵고 조금 더 디테일한 커스텀이 어려운 이유로 다른 툴을 찾기 시작합니다. (물론 UI 테스트까지 잘 지원되면 너무나 땡큐...)
03. 라이센스의 문제 (Katalon Studio)
테스트를 위한 툴을 찾던 중 발견한 테스트 솔루션, IDE를 제공하고 나름 테스트를 위한 각 객체들을 Test Cases, Test Suites, Data Files, Checkpoints, Keywords, Plugins, Object Repository 등의 개념을 통해서 구조적으로 테스트를 위한 코드를 작성하거나 IDE를 통해서 테스트 환경을 구축할 수 있는 아주 훌륭한 툴을 찾았습니다.
// groovy 를 통해서 내가 원하는 기능을 스크립트로 작성할 수 있다.
@Keyword
def doLogin(String id, String password){
// Login Logic....
}
사용하기에 앞서 우선 라이센스 체크를 진행했다면 좋았을 텐데, 너무 마음에 드는 구성에 무작정 연습을 위해서 테스트 케이스와 스크립트를 통해서 여러 가지 기능을 작성해보기 시작합니다.
충분히 사용할 수 있겠다 라는 생각이 들무렵 갑자기 생각난 라이센스... (분명 첫 시작 화면에서는 Start Free 가 있었지만 당연히 상용 목적으로는 라이센스 구매 필요, 여러분들은 미리 라이센스부터 확인을ㅠㅠ)
그래도 수확이 있었다면 잘 만들어진 구조 덕분에 이정도의 기능을 제공하거나 이런 구조적인 부분까지 제공하면 테스트 코드의 재사용성도 확보하면서 저 같은 테린이도 비교적 빠르게 API 테스트나 UI 테스트를 구성할 수 있다는 자신감이 생기기 시작함
아쉬운데 내가 직접 만들어 볼까?
Katalon Studio 를 공부하면서 든 생각은 매우 구조적으로 잘 만들었고 확장성과 사용성, 무엇보다도 친절한 Docs를 통해서 저 역시 손쉽게 만드는 것을 보면서 실제 업무에서 사용하지 못해 매우 아쉬운 상황이었습니다.
마침 올해 오픈소스로 진행하고 싶던 개인 프로젝트가 무산되면서 시간이 남았고 뭔가 이 프로젝트는 잘 만들어두면 개인적인 성취감 외에 실제 업무에 잘 활용할 수 있겠다 라는 생각이 들어서 무작정 Typescript로 프로젝트를 만들고 툴로 작성하면서 괜찮았던 개념, 구글링을 통해서 많은 사람들이 타입스크립트를 통해서 크롤링 혹은 테스트를 구현하는 방법을 찾아보면서 나만의 테스트를 위한 프로젝트를 작성하기 시작합니다.
아직은 샘플이 부족하고 채우고 싶은 부분이 있어서 깃허브에 올리지는 못하고 있지만 충분히 실무에서 잘 활용하고 있습니다. 나름 만족스럽고 저와 같은 고민을 하는 분이 계실 것 같아서 조금 더 정리해서 누구나 사용할 수 있도록 올릴 생각입니다.
물론 저보다 더 잘하시는 분들의 피드백도 받고 조금 더 좋은 방향으로 진행하고 싶은 마음이기도 합니다.
오늘은 작성한 프로젝트의 일부분과 함께 글을 마무리합니다. 테스트 자동화 다음 글의 주제는 실제로 이 프로젝트를 통해서 어떻게 사용하고 있는지 샘플을 작성해서 소개하는 시간을 가져볼까 합니다.
// 커스텀 데코레이터를 통해서 페이지 별로 테스트에 필요한 프로퍼티를 정의하고 이 프로퍼티를 지속적으로 재사용한다.
// Katalon Studio 의 Object Type 과 크롤링을 위한 프로젝트를 구현하신 분의 Page Pattern 을 보고서 따라해봤다.
export class NaverMain extends Page{
@FindBy(...Query)
public searchInput!: WebElementPromise
@FindBy(...BTN)
public searchBtn!: WebElementPromise
@FindAllBy(...Selector)
public news!: Promise<WebElement[]>
async open() {
await this.browser.get(...URL)
}
async waitLoaded(): Promise<void> {
return Promise.resolve()
}
}
// 테스트를 위한 코드는 Jest 를 위해서 동작할 수 있게 진행했다.
// 네이버 페이지에서 특정 검색어로 검색 후 다음 페이지에서 특정 데이터를 가져와서 검증하는 로직
describe('test.spec.ts', () => {
it('test', async () => {
const browser = await ChromeWebDriver.getDriver()
const naver = new NaverMain(browser)
const naverResult = new NaverSearchResult(browser)
await naver.open()
await naver.searchInput.sendKeys('안녕하세요')
await naver.searchBtn.click()
await naverResult.waitLoaded()
const result = await naverResult.searchInput.getAttribute('value')
expect('안녕하세요').toBe(result)
browser.quit()
})
})
// 혹시나 Jest 말고 스케쥴링을 이용해서 동작하거나 Slack, Telegram 연동 등 개인적으로 토이 프로젝트로 진행하고 싶은 부분이 있어서
// 테스트 외 task 성격의 tasks 중 일부. 해당 코드는 코로나 현황 사이트에서 일일, 주간 사망-확진자의 정보를 log 로 작성한다.
(async () => {
try{
const driver = await ChromeWebDriver.getDriver()
const corona19Page = new Corona19(driver)
await corona19Page.open()
const handles = await driver.getAllWindowHandles()
await driver.switchTo().window(handles[1])
await driver.close()
await driver.switchTo().window(handles[0])
const dailyDeaths = await corona19Page.dailyDeaths.getText()
const dailyConfirmed = await corona19Page.dailyConfirmed.getText()
const weeklyDeaths = await corona19Page.weeklyDeaths.getText()
const weeklyConfirmed = await corona19Page.weeklyConfirmed.getText()
console.log(`[일일] 사망 : ${dailyDeaths}, 확진 : ${dailyConfirmed}`)
console.log(`[주간] 사망 : ${weeklyDeaths}, 확진 : ${weeklyConfirmed}`)
await driver.quit()
}catch(e) {
console.error(e)
}
})()
'스마트하게 일하기 > 테스트 자동화 이야기' 카테고리의 다른 글
테스트 코드(Jest)를 이용한 API 리팩토링 전략 (0) | 2022.03.28 |
---|