[JAVA #15] [Web Automation #15] Selenium With TestNG #4 @DataProvider annotation [KR]
hive-101145·@june0620·
0.000 HBD[JAVA #15] [Web Automation #15] Selenium With TestNG #4 @DataProvider annotation [KR]

image source: [pixabay](https://pixabay.com/)
***
지난번에 이어 TestNG의 annotation을 파헤쳐 보자.
자동화 테스트를 하다 보면 매우 중요한 부분이 많은 데이터를 반복해서 테스트를 하는 것이다.
예를 들어 네이버의 검색 기능을 테스트한다고 가정할 때 국가명을 검색했을 경우 국가 정보가 노출되는지, 달러를 검색했을 때 환율 계산기가 노출되는지 등 수많은 키워드를 테스트해야 한다. 이런 수십, 수백 번을 반복하는 작업을 전문 용어로 `DataDriven`테스트라고 부르는데 거창한 이름보다는 `디지털 노가다`라고 부르는 게 맞을 것 같다. ㅎㅎ
다행히 업계의 많은 자동화 툴이 `DataDriven`테스트를 지원한다.
TestNG 역시 지원하는데 TestNG는 `DataProvider`라는 annotation으로 간단하게 구현할 수 있다. 이 기능을 몰랐을 때는 직접 구현했었는데 리포트 생성, 코드 가독성 저하 등 여러 가지 문제로 골머리를 앓았던 기억이 있다.
`@DataProvider` annotation을 장착한 메소드는 return 값으로 2차원 배열`Object[][]` 혹은 `Iterator<Object>`만 지원한다. 속성 `name`은 `@Test` annotation의 `dataProvider` 속성과 결합해 사용한다.
아래 예제는 스티미언 목록을 `@DataProvider`의 데이터로 사용하...는...데... <sub>(혹시라도 사용된 이름이 실례가 되었다면 코멘트 주시면 삭제하겠습니다^^)</sub>
```
@DataProvider(name = "steemians")
public Object[][] members() {
return new Object[][] {
{"annepink"},
{"gghite"},
{"lucky2015"},
{"honoru"},
};
}
```
`@Test` annotation의 `dataProvider` 속성값과 `@DataProvider`의 `name` 값을 일치하게 설정하면 스티미언 목록을 불러올 수 있고 이 데이터는 인자로 넘겨져 액션을 취하게 된다. <sub>아래 예제에서는 스티미언 프로필 페이지로 이동하는 것까지만 구현하였고 상세한 액션은 추가하지 않았다.</sub>
```
@Test(description = "스티미언 검색", dataProvider = "steemians")
public void CASE_04_Search(String name) {
driver.get(baseUrl + "/@" + name);
/*
* Some actions
*/
}
```
자동화 실행해 보면 리포트도 예쁘게 찍히는 것을 볼 수 있다.

**Sources:**
```
package com.steem.webatuo;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.openqa.selenium.By;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import io.github.bonigarcia.wdm.WebDriverManager;
public class Steemit {
WebDriver driver;
String baseUrl = "https://steemit.com";
@BeforeClass
public void SetUp() {
WebDriverManager.chromedriver().setup();
driver = new ChromeDriver();
driver.get(baseUrl + "/@june0620/feed");
driver.manage().window().maximize();
driver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
}
@Test(enabled = false, description = "글 목록을 불러온다.")
public void CASE_01_GetPosts() {
List<WebElement> list = driver.findElements(By.xpath("//div[@id='posts_list']/ul/li"));
assertTrue(!list.isEmpty(), "글 목록이 있는지 확인");
for (WebElement post : list) {
String title = post.findElement(By.xpath("descendant::h2/a")).getText();
String author = post.findElement(By.xpath("descendant::span[@class='user__name']")).getText();
System.out.println(author + "님의 글: " + title);
}
}
@Test(timeOut = 60000, description = "로그인 한다.")
public void CASE_02_Login() throws InterruptedException {
// Click the login button
driver.findElement(By.linkText("로그인")).click();
// type id
driver.findElement(By.name("username")).sendKeys("june0620");
// type pw and submit
WebElement pw = driver.findElement(By.name("password"));
assertNotNull(pw, "비밀번호 태그가 노출되는지 확인");
pw.sendKeys(Password.getPw());
pw.submit();
Thread.sleep(5000);
}
@Test(description = "스팀잇에 글 쓰기 한다. ")
public void CASE_03_Write() throws InterruptedException {
// Click the write Button
List<WebElement> writeBtns = driver.findElements(By.xpath("//a[@href='/submit.html']"));
assertEquals(writeBtns.size(), 1, "글쓰기 버튼이 노출되는지 확인");
for (WebElement writeBtn : writeBtns) {
if (writeBtn.isDisplayed()) {
writeBtn.click();
Thread.sleep(2000);
// type Text and Keys
WebElement editor = driver.findElement(By.xpath("//textarea[@name='body']"));
String keys = Keys.chord(Keys.LEFT_SHIFT, Keys.ENTER);
editor.sendKeys("hello!! world", keys, "hello!!! STEEMIT", Keys.ENTER, "안녕, 스팀잇", Keys.ENTER, "你好!似提姆");
break;
}
}
Thread.sleep(5000);
}
@Test(description = "스티미언 검색", dataProvider = "steemians")
public void CASE_04_Search(String name) {
driver.get(baseUrl + "/@" + name);
/*
* Some actions
*/
}
@DataProvider(name = "steemians")
public Object[][] members() {
return new Object[][] {
{"annepink"},
{"gghite"},
{"lucky2015"},
{"honoru"},
};
}
@AfterClass
public void tearDownClass() {
driver.quit();
}
}
```
**시연 영상:**
https://youtu.be/UQSlN4KlgRs
.
.
.
.
[Cookie 😅]
Seleniun java lib version: 3.141.59
java version: 13.0.1👍 gamezine, dblogvoter, reward.tier2, gerber, exyle, accelerator, roleerob, cadawg, bestboom, ctime, steem.leo, leo.voter, leo.syndication, one.life, steemcityrewards, daan, mys, rycharde, blockbrothers, dlike, triptolemus, fsm-liquid, triplea.bot, freddio.sport, maxuva, maxuvb, maxuve, triple.aaa, techken, whd, abrockman, julian2013, laissez-faire, triplea.cur, maxuvc, maxuvd, bilpcoinpower, archisteem, mustard-seed, jaydih, pagliozzo, roseofmylife, harkar, itchyfeetdonica, kimzwarch, mapxv, mehta, stupid, unpopular, magicmonk, cnbuddy, wherein, steemfriends, tumutanzi, coldhair, minloulou, lindalex, andrewma, wanggang, changxiu, cherryzz, tina3721, andyhsia, cnbuddy-reward, real3earch, slowwalker, oldstone.sct, springflower, mychoco, lotusofmymom, suonghuynh, we-together, pilimili, lucky2015, lupafilotaxia, minigame, mini.sct, photoholic, steem-agora, p-translation, msg768, swiftcash, tookta, bcm, virus707, shenchensucc, bcm.dblog, bcm.zzan, banjjakism, jjm13, ilovemylife, florianopolis, ew-and-patterns, suhunter, melaniewang, cn-sct, sqljoker, zzan.hmy, xiaoshancun, seapy, ravenkim, marsswim, zzan.co3, udabeu, gerdtrudroepke, tokenindustry, sct1004, nineteensixteen, sklara, ramires, trujik, sindong, rudyardcatling, kr-fund, june0620, gghite, yahisma, sct.curator, sct.krwp,