TestNG 종합 치트시트
설치
| 플랫폼/도구 | 설치 방법 |
|---|
| Maven | Add to pom.xml:
<dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>7.8.0</version> <scope>test</scope>
</dependency> |
| Gradle | Add to build.gradle:
testImplementation 'org.testng:testng:7.8.0' |
| Eclipse IDE | 도움말 → Eclipse Marketplace → “TestNG” 검색 → 설치 |
| IntelliJ IDEA | 기본적으로 번들로 제공됨 (또는 파일 → 설정 → 플러그인 → 마켓플레이스 → “TestNG”) |
| VS Code | code --install-extension vscjava.vscode-java-pack
code --install-extension testng.testng |
| Manual (Linux/macOS) | wget https://repo1.maven.org/maven2/org/testng/testng/7.8.0/testng-7.8.0.jar
export CLASSPATH=$CLASSPATH:/path/to/testng-7.8.0.jar |
| Manual (Windows) | Download JAR from Maven Central
set CLASSPATH=%CLASSPATH%;C:\path\to\testng-7.8.0.jar |
핵심 어노테이션
| 주석 | 설명 |
|---|
@Test | 메서드를 테스트 메서드로 표시합니다 |
@Test(priority = 1) | 실행 순서를 설정합니다 (낮은 숫자가 먼저 실행됨) |
@Test(description = "...") | 보고를 위해 설명 텍스트를 추가합니다 |
@Test(timeOut = 5000) | 실행이 타임아웃(밀리초)을 초과하면 테스트 실패 |
@Test(expectedExceptions = Exception.class) | 특정 예외가 발생할 것으로 예상됨 |
@Test(enabled = false) | 테스트를 비활성화/건너뜁니다 |
@Test(groups = {"smoke", "regression"}) | 테스트를 하나 이상의 그룹에 할당합니다 |
@Test(dependsOnMethods = {"testMethod"}) | 지정된 메서드(들) 완료 후 실행됨 |
@Test(dependsOnGroups = {"smoke"}) | 지정된 그룹의 모든 테스트 후에 실행됨 |
@Test(alwaysRun = true) | 의존성이 실패해도 테스트 실행 |
@Test(invocationCount = 3) | 여러 번 테스트 실행 |
@Test(threadPoolSize = 5) | 병렬 스레드에서 여러 호출을 실행합니다 |
@BeforeMethod | Executes before each @Test method |
@AfterMethod | Executes after each @Test method |
@BeforeClass | 클래스의 모든 테스트 메서드 이전에 한 번 실행됩니다 |
@AfterClass | 클래스의 모든 테스트 메서드 후에 한 번 실행됩니다 |
@BeforeTest | Executes before any test method in <test> tag |
@AfterTest | Executes after all test methods in <test> tag |
@BeforeSuite | 모든 테스트 스위트의 테스트들 이전에 한 번 실행됩니다 |
@AfterSuite | 모든 테스트 스위트의 테스트들이 끝난 후 한 번 실행됩니다 |
@BeforeGroups | 지정된 그룹의 첫 번째 테스트 메서드 이전에 실행됩니다 |
@AfterGroups | 지정된 그룹의 마지막 테스트 메서드 후에 실행됨 |
@DataProvider | 매개변수화를 위한 메서드 테스트에 데이터를 제공합니다 |
@Parameters | Injects parameters from testng.xml into test methods |
@Factory | 동적으로 테스트 인스턴스 생성 |
@Listeners | 테스트 클래스에 사용자 정의 리스너 연결 |
명령줄 실행
| 명령어 | 설명 |
|---|
java -cp "classes:lib/*" org.testng.TestNG testng.xml | XML 스위트 파일을 사용하여 테스트 실행 |
java -cp "classes:lib/*" org.testng.TestNG -testclass com.example.MyTest | 특정 테스트 클래스 실행 |
java -cp "classes:lib/*" org.testng.TestNG -testclass Test1,Test2 | 여러 테스트 클래스 실행 (쉼표로 구분) |
java -cp "classes:lib/*" org.testng.TestNG -groups smoke testng.xml | 특정 그룹의 테스트 실행 |
java -cp "classes:lib/*" org.testng.TestNG -excludegroups slow testng.xml | 실행에서 특정 그룹(들) 제외 |
java -cp "classes:lib/*" org.testng.TestNG -d test-output testng.xml | 보고서의 출력 디렉토리 지정 |
java -cp "classes:lib/*" org.testng.TestNG -parallel methods -threadcount 5 | 스레드 수와 함께 병렬로 테스트 실행 |
java -cp "classes:lib/*" org.testng.TestNG -verbose 10 testng.xml | 상세 수준 설정 (0-10, 숫자가 높을수록 더 자세한 정보) |
java -cp "classes:lib/*" org.testng.TestNG -methods MyTest.test1,MyTest.test2 | 특정 테스트 메서드 실행 |
java -cp "classes:lib/*" org.testng.TestNG -suitename "MySuite" -testname "MyTest" | 테스트 스위트와 테스트 이름 재정의하기 |
java -cp "classes:lib/*" org.testng.TestNG -reporter org.testng.reporters.EmailableReporter | 특정 리포터 사용 |
java -cp "classes:lib/*" org.testng.TestNG -listener com.example.MyListener | 맞춤 리스너 추가 |
Maven 명령어
| 명령어 | 설명 |
|---|
mvn test | 모든 테스트 실행 |
mvn test -Dtest=MyTestClass | 특정 테스트 클래스 실행 |
mvn test -Dtest=MyTestClass#testMethod | 특정 테스트 메서드 실행 |
mvn test -Dtest=MyTestClass#test* | 패턴과 일치하는 테스트 메서드 실행 |
mvn test -DsuiteXmlFile=smoke-tests.xml | 특정 스위트 XML 파일 실행 |
mvn test -Dgroups=smoke,regression | 특정 테스트 그룹 실행 |
mvn test -DexcludedGroups=slow | 특정 테스트 그룹 제외 |
mvn test -Denvironment=staging -Dbrowser=chrome | 테스트에 시스템 속성 전달하기 |
mvn test -DskipTests | 테스트 실행 건너뛰기 |
mvn test -Dmaven.test.failure.ignore=true | 테스트가 실패해도 계속 빌드하기 |
mvn test -Dparallel=methods -DthreadCount=4 | 병렬로 테스트 실행 |
mvn clean test | 이전 빌드 정리 및 테스트 실행 |
mvn test -X | 디버그 출력으로 테스트 실행 |
mvn surefire-report:report | HTML 테스트 보고서 생성 |
Gradle 명령어
| 명령어 | 설명 |
|---|
gradle test | 모든 테스트 실행 |
gradle test --tests MyTestClass | 특정 테스트 클래스 실행 |
gradle test --tests MyTestClass.testMethod | 특정 테스트 메서드 실행 |
gradle test --tests *IntegrationTest | 패턴과 일치하는 테스트 실행 |
gradle test --tests MyTestClass --tests OtherTest | 여러 테스트 클래스 실행하기 |
gradle test -Denvironment=staging | 시스템 속성 전달 |
gradle clean test | 테스트 정리 및 실행 |
gradle test --info | 자세한 로깅으로 실행 |
gradle test --debug | 디버그 수준 로깅으로 실행 |
gradle test --rerun-tasks | 최신 상태여도 강제로 다시 실행 |
gradle test --continue | 테스트 실패 후 실행 계속하기 |
gradle test --fail-fast | 첫 번째 테스트 실패 시 실행 중지 |
어서션 메서드
| 메서드 | 설명 |
|---|
Assert.assertEquals(actual, expected) | 두 값이 같은지 확인 |
Assert.assertEquals(actual, expected, "message") | 사용자 정의 실패 메시지로 어설트하기 |
Assert.assertNotEquals(actual, expected) | 두 값이 같지 않은지 확인 |
Assert.assertTrue(condition) | 조건이 참인지 확인하세요 |
Assert.assertFalse(condition) | 조건 확인이 거짓입니다 |
Assert.assertNull(object) | 객체가 null인지 확인 |
Assert.assertNotNull(object) | 객체가 null이 아닌지 확인 |
Assert.assertSame(actual, expected) | 동일한 객체 참조 확인 |
Assert.assertNotSame(actual, expected) | 다른 객체 참조 확인하기 |
Assert.fail("message") | 테스트를 명시적으로 실패시키다 |
Assert.assertThrows(Exception.class, () -> {...}) | 예외가 발생하는지 확인 |
Assert.expectThrows(Exception.class, () -> {...}) | assertThrows와 동일 (별칭) |
데이터 프로바이더
| 패턴 | 설명 |
|---|
@DataProvider(name = "testData") | 데이터 제공자 메서드 정의하기 |
@Test(dataProvider = "testData") | 테스트에서 데이터 제공자 사용하기 |
@DataProvider(parallel = true) | 데이터 제공자 반복을 병렬로 실행 |
Object[][] dataProvider() | 테스트 데이터의 2D 배열 반환 |
Iterator<Object[]> dataProvider() | 대용량 데이터셋을 위한 iterator 반환 |
@DataProvider(indices = {0, 2, 4}) | 특정 데이터 세트 인덱스만 실행 |
데이터 프로바이더 예시
@DataProvider(name = "loginData")
public Object[][] getLoginData() {
return new Object[][] {
{"user1", "pass1", true},
{"user2", "pass2", false},
{"user3", "pass3", true}
};
}
@Test(dataProvider = "loginData")
public void testLogin(String username, String password, boolean expected) {
boolean result = login(username, password);
Assert.assertEquals(result, expected);
}
TestNG XML 구성
기본 스위트 구성
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Test Suite" parallel="methods" thread-count="5" verbose="1">
<!-- Suite-level parameters -->
<parameter name="browser" value="chrome"/>
<parameter name="environment" value="staging"/>
<!-- Define test groups -->
<test name="Smoke Tests">
<groups>
<run>
<include name="smoke"/>
<exclude name="slow"/>
</run>
</groups>
<!-- Specify test classes -->
<classes>
<class name="com.example.LoginTest"/>
<class name="com.example.SearchTest">
<!-- Include specific methods -->
<methods>
<include name="testBasicSearch"/>
<include name="testAdvancedSearch"/>
</methods>
</class>
</classes>
</test>
<!-- Another test configuration -->
<test name="Regression Tests">
<packages>
<package name="com.example.regression.*"/>
</packages>
</test>
<!-- Listeners -->
<listeners>
<listener class-name="com.example.CustomListener"/>
</listeners>
</suite>
병렬 실행 구성
<!-- Parallel at suite level -->
<suite name="Parallel Suite" parallel="tests" thread-count="3">
<test name="Test1">...</test>
<test name="Test2">...</test>
</suite>
<!-- Parallel options: methods, tests, classes, instances -->
Maven Surefire 플러그인 구성
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<!-- Specify suite files -->
<suiteXmlFiles>
<suiteXmlFile>testng.xml</suiteXmlFile>
<suiteXmlFile>smoke-tests.xml</suiteXmlFile>
</suiteXmlFiles>
<!-- Run specific groups -->
<groups>smoke,regression</groups>
<excludedGroups>slow,manual</excludedGroups>
<!-- Parallel execution -->
<parallel>methods</parallel>
<threadCount>5</threadCount>
<!-- System properties -->
<systemPropertyVariables>
<browser>chrome</browser>
<environment>staging</environment>
</systemPropertyVariables>
<!-- Continue on failures -->
<testFailureIgnore>false</testFailureIgnore>
</configuration>
</plugin>
</plugins>
</build>
Gradle 테스트 구성
test {
useTestNG() {
// Suite files
suites 'src/test/resources/testng.xml'
// Include/exclude groups
includeGroups 'smoke', 'regression'
excludeGroups 'slow'
// Parallel execution
parallel = 'methods'
threadCount = 5
// Preserve order
preserveOrder = true
// Group by instances
groupByInstances = true
}
// System properties
systemProperty 'browser', 'chrome'
systemProperty 'environment', 'staging'
// Test output
testLogging {
events "passed", "skipped", "failed"
exceptionFormat "full"
}
}
일반적인 사용 사례
사용 사례 1: 설정 및 정리가 있는 기본 테스트 클래스
import org.testng.annotations.*;
import org.testng.Assert;
public class UserManagementTest {
private DatabaseConnection db;
private UserService userService;
@BeforeClass
public void setupClass() {
// Initialize database connection once for all tests
db = new DatabaseConnection("jdbc:mysql://localhost:3306/testdb");
db.connect();
}
@BeforeMethod
public void setupMethod() {
// Create fresh service instance before each test
userService = new UserService(db);
}
@Test(priority = 1, groups = {"smoke"})
public void testCreateUser() {
User user = userService.createUser("john@example.com", "John Doe");
Assert.assertNotNull(user.getId());
Assert.assertEquals(user.getEmail(), "john@example.com");
}
@Test(priority = 2, dependsOnMethods = {"testCreateUser"})
public void testFindUser() {
User user = userService.findByEmail("john@example.com");
Assert.assertNotNull(user);
Assert.assertEquals(user.getName(), "John Doe");
}
@AfterMethod
public void cleanupMethod() {
// Clean up test data after each test
userService.deleteAllUsers();
}
@AfterClass
public void cleanupClass() {
// Close database connection after all tests
db.disconnect();
}
}
사용 사례 2: 데이터 프로바이더를 사용한 데이터 기반 테스트
import org.testng.annotations.*;
import org.testng.Assert;
public class LoginTest {
private LoginPage loginPage;
@BeforeMethod
public void setup() {
loginPage = new LoginPage();
loginPage.open();
}
@DataProvider(name = "loginCredentials")
public Object[][] getLoginData() {
return new Object[][] {
{"valid@user.com", "ValidPass123", true, "Dashboard"},
{"invalid@user.com", "WrongPass", false, "Invalid credentials"},
{"", "password", false, "Email is required"},
{"user@test.com", "", false, "Password is required"},
{"admin@test.com", "AdminPass!", true, "Admin Panel"}
};
}
@Test(dataProvider = "loginCredentials")
public void testLogin(String email, String password,
boolean shouldSucceed, String expectedMessage) {
loginPage.enterEmail(email);
loginPage.enterPassword(password);
loginPage.clickLogin();
if (shouldSucceed) {
Assert.assertTrue(loginPage.isLoggedIn());
Assert.assertEquals(loginPage.getPageTitle(), expectedMessage);
} else {
Assert.assertTrue(loginPage.hasError());
Assert.assertTrue(loginPage.getErrorMessage().contains(expectedMessage));
}
}
@AfterMethod
public void teardown() {
loginPage.close();
}
}
사용 사례 3: 그룹을 사용한 병렬 테스트 실행
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Parallel Test Suite" parallel="tests" thread-count="3">
<test name="Chrome Tests" parallel="methods" thread-count="2">
<parameter name="browser" value="chrome"/>
<groups>
<run>
<include name="smoke"/>
</run>
</groups>
<classes>
<class name="com.example.tests.HomePageTest"/>
<class name="com.example.tests.SearchTest"/>
</classes>
</test>
<test name="Firefox Tests" parallel="methods" thread-count="2">
<parameter name="browser" value="firefox"/>
<groups>
<run>
<include name="smoke"/>
</run>
</groups>
<classes>
<class name="com.example.tests.HomePageTest"/>
<class name="com.example.tests.SearchTest"/>
</classes>
</test>
<test name="API Tests" parallel="classes" thread-count="3">
<groups>
<run>
<include name="api"/>
</run>
</groups>
<packages>
<package name="com.example.api.*"/>
</packages>
</test>
</suite>
// Test class using parameters
public class CrossBrowserTest {
private WebDriver driver;
@Parameters({"browser"})
@BeforeMethod
public void setup(String browser) {
if (browser.equalsIgnoreCase("chrome")) {
driver = new ChromeDriver();
} else if (browser.equalsIgnoreCase("firefox")) {
driver = new FirefoxDriver();
}
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
}
@Test(groups = {"smoke"})
public void testHomePage() {
driver.get("https://example.com");
Assert.assertEquals(driver.getTitle(), "Example Domain");
}
@AfterMethod
public void teardown() {
if (driver != null) {
driver.quit();
}
}
}
사용 사례 4: 재시도 로직을 사용한 API 테스트
import org.testng.IRetryAnalyzer;
import org.testng.ITestResult;
// Retry analyzer for flaky tests
public class RetryAnalyzer implements IRetryAnalyzer {
private int retryCount = 0;
private static final int MAX_RETRY = 3;
@Override
public boolean retry(ITestResult result) {
if (retryCount < MAX_RETRY) {
retryCount++;
return true;
}
return false;
}
}
// API Test class
public class APITest {
private RestClient client;
@BeforeClass
public void setup() {
client = new RestClient("https://api.example.com");
}
@Test(groups = {"api"}, retryAnalyzer = RetryAnalyzer.class)
public void testGetUser() {
Response response = client.get("/users/1");
Assert.assertEquals(response.getStatusCode(), 200);
Assert.assertNotNull(response.jsonPath().getString("name"));
}
@Test(groups = {"api"}, dependsOnMethods = {"testGetUser"})
public void testCreateUser() {
String payload = "{\"name\":\"John\",\"email\":\"john@test.com\"}";
Response response = client.post("/users", payload);
Assert.assertEquals(response.getStatusCode(), 201);
}
@Test(groups = {"api"}, timeOut = 5000)
public void testPerformance() {
long startTime = System.currentTimeMillis();
Response response = client.get("/users");
long endTime = System.currentTimeMillis();
Assert.assertEquals(response.getStatusCode(), 200);
Assert.assertTrue((endTime - startTime) < 3000,
"API response time exceeded 3 seconds");
}
}
사용 사례 5: 사용자 정의 리스너 및 보고
import org.testng.ITestListener;
import org.testng.ITestResult;
import org.testng.ITestContext;
public class CustomTestListener implements ITestListener {
@Override
public void onTestStart(ITestResult result) {
System.out.println("Starting test: " + result.getName());
}
@Override
public void onTestSuccess(ITestResult result) {
System.out.println("Test passed: " + result.getName());
}
@Override
public void onTestFailure(ITestResult result) {
System.out.println("Test failed: " + result.getName());
// Take screenshot, log error, etc.
captureScreenshot(result.getName());
}
@Override
public void onTestSkipped(ITestResult result) {
System.out.println("Test skipped: " + result.getName());
}
@Override
public void onFinish(ITestContext context) {
System.out.println("Total tests run: " + context.getAllTestMethods().length);
System.out.println("Passed: " + context.getPassedTests().size());
System.out.println("Failed: " + context.getFailedTests().size());
System.out.println("Skipped: " + context.getSkippedTests().size());
}
private void captureScreenshot(String testName) {
// Screenshot logic here
}
}
// Using the listener
@Listeners(CustomTestListener.class)
public class MyTest {
@Test
public void testExample() {
Assert.assertTrue(true);
}
}
모범 사례
- 의미 있는 테스트 이름 사용: 테스트가 무엇을 검증하는지 명확하게 이름 지정 (예:
testUserCanLoginWithValidCredentials대신에
Note: For texts 3-20, I noticed no specific text was provided, so I’ve left them as-is. If you have specific texts for those numbers, please provide them and I’ll translate accordingly.test1)
-
Leverage groups effectively: Organize tests into logical groups (smoke, regression, api, ui) to run subsets of tests based on context and save execution time
-
Implement proper setup and teardown: Use @BeforeMethod/@AfterMethod for test-level setup and @BeforeClass/@AfterClass for expensive operations like database connections
-
Make tests independent: Each test should be self-contained and not rely on execution order or shared state. Use dependsOnMethods sparingly and only when logical dependency exists
-
Use DataProviders for test data: Separate test data from