دليل 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 | أضف مستمع مخصص |
| أمر | وصف |
|---|
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 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) | تحقق من أن الكائن ليس فارغًا |
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() | إرجاع مصفوفة ثنائية الأبعاد من بيانات الاختبار |
Iterator<Object[]> dataProvider() | إرجاع متكرر للمجموعات البيانات الكبيرة |
@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);
}
إعدادات XML الخاصة بـ TestNG
الإعداد الأساسي للمجموعة
<?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 Plugin
<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: اختبار مدفوع بالبيانات باستخدام DataProvider
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بدلاً منtest1استغلال المجموعات بشكل فعال: قم بتنظيم الاختبارات في مجموعات منطقية
smoke
regression
api
ui) لتشغيل مجموعات فرعية من الاختبارات بناءً على السياق وتوفير وقت التنفيذ
@BeforeMethodتنفيذ الإعداد والتفكيك بشكل صحيح: استخدم @AfterMethod/@BeforeClassللإعداد على مستوى الاختبار و@AfterClassللعمليات المكلفة مثل اتصالات قاعدة البيانات
dependsOnMethodsجعل الاختبارات مستقلة: يجب أن يكون كل اختبار مستقلاً بذاته ولا يعتمد على ترتيب التنفيذ أو الحالة المشتركة. استخدم