تخطَّ إلى المحتوى

دليل TestNG الشامل

دليل TestNG الشامل

التثبيت

منصة/أداةطريقة التثبيت
MavenAdd to pom.xml:
<dependency>
  <groupId>org.testng</groupId>
  <artifactId>testng</artifactId>
  <version>7.8.0</version>
  <scope>test</scope>
</dependency>
GradleAdd to build.gradle:
testImplementation 'org.testng:testng:7.8.0'
Eclipse IDEالمساعدة → Eclipse Marketplace → البحث عن “TestNG” → التثبيت
IntelliJ IDEAمجمّعة بشكل افتراضي (أو ملف → الإعدادات → الإضافات → المتجر → “TestNG”)
VS Codecode --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)يُشغِّل استدعاءات متعددة في مؤشرات متوازية
@BeforeMethodExecutes before each @Test method
@AfterMethodExecutes after each @Test method
@BeforeClassيُنفَّذ مرة واحدة قبل أي طريقة اختبار في الفئة
@AfterClassيُنفَّذ مرة واحدة بعد جميع طرق الاختبار في الفئة
@BeforeTestExecutes before any test method in <test> tag
@AfterTestExecutes after all test methods in <test> tag
@BeforeSuiteيُنفَّذ مرة واحدة قبل جميع الاختبارات في المجموعة
@AfterSuiteيُنفَّذ مرة واحدة بعد جميع الاختبارات في المجموعة
@BeforeGroupsيُنفَّذ قبل أول طريقة اختبار في المجموعة (المجموعات) المحددة
@AfterGroupsيُنفَّذ بعد آخر طريقة اختبار في المجموعة (المجموعات) المحددة
@DataProviderيوفر بيانات لاختبار الطرق للتمثيل المعلمي
@ParametersInjects 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جعل الاختبارات مستقلة: يجب أن يكون كل اختبار مستقلاً بذاته ولا يعتمد على ترتيب التنفيذ أو الحالة المشتركة. استخدم