يرتبط بهذا النهج الخيار ، وإطار عمل الروبوت ، والسلوك ، وغيرها ، والتي تفصل بين نصوص التنفيذ وتنفيذ كل بناء. يساعد هذا الفصل في كتابة نصوص قابلة للقراءة ، لكنه يستغرق وقتًا طويلاً وبالتالي قد يكون غير عملي عند كتابة تنفيذ.
دعنا نلقي نظرة على كيفية تبسيط عملك مع BDD باستخدام الأدوات المناسبة - على سبيل المثال ، إطار عمل Spock ، الذي يجمع بين الجمال والراحة لمبادئ BDD وميزات jUnit.
إطار سبوك
Spock هو إطار عمل للاختبار والمواصفات لتطبيقات Java و Groovy. باستخدام منصة JUnit كأساس ، يتوافق هذا الإطار مع جميع IDEs الشائعة (على وجه الخصوص ، IntelliJ IDEA) وأدوات البناء المختلفة (Ant و Gradle و Maven) وخوادم التكامل المستمر (CI).
كيف تكتب مطوري إطار العمل ، فإن Spock «ألهمت JUnit و RSpec و jMock و Mockito و Groovy's و Scala's و Vulcans وغيرها من أشكال الحياة المثيرة."
في هذه المقالة ، سنلقي نظرة على أحدث إصدار متاح ، Spock Framework 2.0. ميزاته: القدرة على استخدام JUnit5 و Java 8+ و groovy 2.5 (يوجد أيضًا تجميع مع الإصدار 3.0). Spock مرخص بموجب Apache 2.0 ولديه مجتمع مستخدم متجاوب. يواصل مطورو إطار العمل تحسين Spock وتطويره ، والذي يتضمن بالفعل العديد من الإضافات التي تسمح لك بضبط التشغيل التجريبي الخاص بك. على سبيل المثال ، أحد أكثر مجالات التحسين المعلنة إثارة للاهتمام هو إضافة تنفيذ الاختبار الموازي.
رائع
Groovy هي لغة برمجة موجهة للكائنات تم تطويرها لمنصة Java كإضافة مع قدرات Python و Ruby و Smalltalk. يستخدم Groovy بناء جملة يشبه Java مع تجميع ديناميكي لـ JVM bytecode ويعمل مباشرة مع أكواد Java الأخرى والمكتبات. يمكن استخدام اللغة في أي مشروع Java أو كلغة برمجة نصية.
تشمل ميزات groovy: الكتابة الثابتة والديناميكية ؛ بناء جملة مدمج للقوائم والمصفوفات والتعبيرات العادية ؛ عمليات التحميل الزائد. ومع ذلك ، ظهرت الإغلاقات في Groovy قبل وقت طويل من Java.
يعد Groovy مناسبًا تمامًا لتطوير الاختبار السريع ، حيث يمكنك استخدام السكر النحوي الشبيه بالثعبان دون القلق بشأن كتابة الكائنات.
ميزات Spock Framework
تتمثل إحدى الميزات الرئيسية لإطار العمل في أن المطور لديه القدرة على كتابة المواصفات بخصائص النظام المتوقعة باستخدام مبادئ نهج BDD. يتيح هذا النهج إمكانية تكوين اختبارات وظيفية موجهة نحو الأعمال لمنتجات البرامج ذات الموضوعات العالية والتعقيد التنظيمي.
المواصفات هي فئة رائعة تمتد spock.lang.Specification
class MyFirstSpecification extends Specification {
// fields
// fixture methods
// feature methods
// helper methods
}
يمكن أن تحتوي قائمة مكونات الصنف على حقول مساعدة مختلفة يتم تشغيلها لكل فئة قائمة مكونات الصنف.
باستخدام التعليق التوضيحيShared ، يمكنك منح حق الوصول إلى الحقل للفئات الموروثة من المواصفات.
abstract class PagesBaseSpec extends Specification {
@Shared
protected WebDriver driver
def setup() {
this.driver = DriverFactory.createDriver()
driver.get("www.anywebservice.ru")
}
void cleanup() {
driver.quit()
}
}
طرق تخصيص فئة BOM:
def setupSpec() {} // feature
def setup() {} // feature
def cleanup() {} // feature
def cleanupSpec() {} // feature
يوضح الجدول التالي الكلمات الأساسية والطرق في إطار عمل Spock التي لها نظراء JUnit.
كتل العجين
في Spock Framework ، يتم فصل كل مرحلة اختبار إلى كتلة منفصلة من التعليمات البرمجية (انظر الوثائق للحصول على مثال ).
تبدأ كتلة الكود بتسمية وتنتهي ببداية الكتلة التالية من الكود أو بنهاية الاختبار.
الكتلة المعطاة مسؤولة عن تحديد شروط الاختبار الأولية.
حظر متى ، ثم يتم استخدامها معًا دائمًا. تحتوي كتلة when على المنشط ومحفز النظام ، وتحتوي الكتلة بعد ذلك على استجابة النظام.
في الحالات التي يكون فيها من الممكن تقصير جملة when-then إلى تعبير واحد ، يمكنك استخدام كتلة توقع واحدة... سيتم استخدام الأمثلة التالية من وثائق إطار عمل Spock الرسمية:
when:
def x = Math.max(1, 2)
then:
x == 2
أو تعبير واحد
expect:
Math.max(1, 2) == 2
يتم استخدام كتلة التنظيف لتحرير الموارد قبل التكرار التالي للاختبار.
given:
def file = new File("/some/path")
file.createNewFile()
// ...
cleanup:
file.delete()
و هو الشرط حيث تستخدم لنقل البيانات لاختبار (اختبار المستندة إلى البيانات).
def "computing the maximum of two numbers"() {
expect:
Math.max(a, b) == c
where:
a << [5, 3]
b << [1, 9]
c << [5, 9]
}
سيتم مناقشة أنواع نقل بيانات الإدخال أدناه.
نموذج تنفيذ الاختبار على Spock Framework
بعد ذلك ، سننظر في مناهج تنفيذ اختبار صفحة الويب للحصول على إذن المستخدم في النظام باستخدام السيلينيوم.
import helpers.DriverFactory
import org.openqa.selenium.WebDriver
import spock.lang.Shared
import spock.lang.Specification
abstract class PagesBaseSpec extends Specification {
@Shared
protected WebDriver driver
def setup() {
this.driver = DriverFactory.createDriver()
driver.get("www.anywebservice.ru")
}
void cleanup() {
driver.quit()
}
}
هنا نرى الفئة الأساسية لمواصفات الصفحة. في بداية الفصل ، نرى استيراد الفئات المطلوبة. ما يلي هو التعليق التوضيحي المشترك ، والذي يسمح للفئات المشتقة بالوصول إلى برنامج تشغيل الويب. في كتلة الإعداد () ، نرى رمز تهيئة محرك الويب وفتح صفحة الويب. تحتوي كتلة cleanup () على رمز إنهاء برنامج تشغيل الويب.
بعد ذلك ، دعنا ننتقل إلى نظرة عامة على مواصفات صفحة تسجيل دخول المستخدم.
import pages.LoginPage
import spock.lang.Issue
class LoginPageTest extends PagesBaseSpec {
@Issue("QAA-1")
def "QAA-1: Authorization with correct login and password"() {
given: "Login page"
def loginPage = new LoginPage(driver)
and: "Correct login and password"
def adminLogin = "adminLogin"
def adminPassword = "adminPassword"
when: "Log in with correct login and password"
loginPage.login(adminLogin, adminPassword)
then: "Authorized and moved to main page"
driver.currentUrl == "www.anywebservice.ru/main"
}
}
ترث مواصفات صفحة التفويض من مواصفات الصفحة الأساسية. يحدد التعليق التوضيحي للمشكلة معرف الاختبار في نظام تتبع خارجي (مثل Jira). في السطر التالي ، نرى اسم الاختبار ، الذي يتم تعيينه ، حسب الاصطلاح ، بواسطة سلسلة حرفية ، مما يسمح باستخدام أي أحرف في اسم الاختبار (بما في ذلك الأحرف الروسية). في الكتلة المحددة ، تتم تهيئة كائن الصفحة لفئة صفحة التفويض ، ويتم الحصول على تسجيل الدخول وكلمة المرور الصحيحين للترخيص في النظام. في كتلة when ، يتم تنفيذ إجراء التفويض. يتم استخدام الكتلة ثم للتحقق من الإجراء المتوقع ، أي التخويل الناجح وإعادة التوجيه إلى الصفحة الرئيسية للنظام.
باستخدام هذه المواصفات كمثال ، نرى الميزة الأكثر أهمية لاستخدام نموذج BDD في spock - مواصفات النظام هي في نفس الوقت توثيقه. يصف كل اختبار سلوكًا معينًا ، وكل خطوة في الاختبار لها وصفها الخاص ، وهو أمر مفهوم ليس فقط للمطورين ، ولكن أيضًا للعملاء. يمكن تقديم أوصاف الكتل ليس فقط في الكود المصدري للاختبار ، ولكن أيضًا في رسائل التشخيص أو التقارير المتعلقة بعملية الاختبار.
يوفر إطار العمل القدرة على نقل العديد من عمليات تسجيل الدخول وكلمات المرور للاختبار (تحديد معلمات الاختبار).
الاختبار المستند إلى البيانات في إطار عمل Spock
الاختبار المستند إلى البيانات = الاختبار المستند إلى الجدول = الاختبار المعتمد
لاختبار سيناريو بمعلمات متعددة ، يمكنك استخدام خيارات متنوعة لتمريرها.
جداول البيانات
لنلقِ نظرة على بعض الأمثلة من وثائق إطار العمل الرسمية.
class MathSpec extends Specification {
def "maximum of two numbers"() {
expect:
Math.max(a, b) == c
where:
a | b | c
1 | 3 | 3
7 | 4 | 7
0 | 0 | 0
}
}
كل صف في الجدول عبارة عن تكرار اختبار منفصل. أيضًا ، يمكن تمثيل الجدول بعمود واحد.
where:
a | _
1 | _
7 | _
0 | _
_ هو كائن أبتر لفئة BOM.
للحصول على تصور بصري أفضل للمعلمات ، يمكنك إعادة كتابة المثال أعلاه بالشكل التالي:
def "maximum of two numbers"() {
expect:
Math.max(a, b) == c
where:
a | b || c
1 | 3 || 3
7 | 4 || 7
0 | 0 || 0
}
يمكننا الآن ملاحظة أن a و b عبارة عن مدخلات و c هي القيمة المتوقعة.
أنابيب البيانات
في بعض الحالات ، سيكون استخدام جدول التصميم مرهقًا للغاية. في مثل هذه الحالات ، يمكنك استخدام النوع التالي من تمرير المعلمة:
...
where:
a << [1, 7, 0]
b << [3, 4, 0]
c << [3, 7, 0]
هنا ، التحول الأيسر << هو عامل تشغيل رائع يعمل الآن كإضافة عناصر إلى القائمة.
لكل تكرار اختبار ، سيتم طلب البيانات التالية من القائمة لكل متغير:
1 التكرار: أ = 1 ، ب = 3 ، ج = 3 ؛
التكرار الثاني: أ = 7 ، ب = 4 ، ج = 7 ؛
3 التكرار: أ = 0 ، ب = 0 ، ج = 0.
علاوة على ذلك ، لا يمكن نقل بيانات الإدخال بشكل صريح فحسب ، بل يمكن طلبها أيضًا ، إذا لزم الأمر ، من مصادر مختلفة. على سبيل المثال ، من قاعدة البيانات:
@Shared sql = Sql.newInstance("jdbc:h2:mem:", "org.h2.Driver")
def "maximum of two numbers"() {
expect:
Math.max(a, b) == c
where:
[a, b, c] << sql.rows("select a, b, c from maxdata")
}
تعيين متغير البيانات
...
where:
a = 3
b = Math.random() * 100
c = a > b ? a : b
هنا نرى المتغير المحسوب ديناميكيًا c في بيانات الاختبار.
مزيج من أنواع مختلفة من نقل المعلمات
...
where:
a | _
3 | _
7 | _
0 | _
b << [5, 0, 0]
c = a > b ? a : b
لا أحد يمنعك من استخدام عدة أنواع من النقل دفعة واحدة ، إذا لزم الأمر.
مثال على تنفيذ اختبار معلمات على Spock Framework
@Issue("QAA-1-parametrized")
def "QAA-1-parametrized: Authorization with correct login and password"() {
given: "Login page"
def loginPage = new LoginPage(driver)
when: "Log in with correct login and password"
loginPage.login(login, password)
then: "Authorized and moved to main page"
driver.currentUrl =="www.anywebservice.ru/main"
where: "Check for different logins and passwords"
login | password
"adminLogin" | "adminPassword"
"moderatorLogin" | "moderatorPassword"
"userLogin" | "userPassword"
}
هنا نرى كتلة المكان المألوفة بالفعل ، حيث يتم تعيين مفاتيح المعلمات (تسجيلات الدخول وكلمات المرور) ، والتي يتم تخزينها في ملف التكوين.
نظرًا لخصائص التنفيذ المستخدم للمواصفات ، سيتم تنفيذ دورة تكوين برنامج تشغيل الويب وإغلاقها (وبالتالي إغلاق المتصفح) لكل معلمة ، مما سيؤثر سلبًا على وقت تنفيذ الاختبار. نقترح إنهاء المواصفات وتحسين وقت التشغيل التجريبي.
مثال على تنفيذ اختبار ذي معلمات بمواصفات معدلة
قبل المراجعة
abstract class PagesBaseSpec extends Specification {
@Shared
protected WebDriver driver
def setup() {
this.driver = DriverFactory.createDriver()
driver.get("www.anywebservice.ru")
}
void cleanup() {
driver.quit()
}
}
بعد المراجعة
import helpers.DriverFactory
import org.openqa.selenium.WebDriver
import spock.lang.Shared
import spock.lang.Specification
abstract class PagesNoRestartBaseSpec extends Specification {
@Shared
protected WebDriver driver
def setupSpec() {
this.driver = DriverFactory.createDriver()
}
def setup() {
this.driver.get("www.anywebservice.ru")
}
def cleanup() {
this.driver.get("www.anywebservice.ru/logout")
this.driver.manage().deleteAllCookies();
}
void cleanupSpec() {
this.driver.quit()
}
}
في المواصفات المحدثة ، نرى أن إجراء إنشاء برنامج تشغيل ويب سيتم تنفيذه فقط عند إعداد فئة المواصفات ، وإغلاق المتصفح فقط بعد انتهاء تشغيل الاختبارات من المواصفات. في طريقة الإعداد () ، نرى نفس الرمز للحصول على عنوان الويب الخاص بالخدمة وفتحه في متصفح ، وفي طريقة التنظيف () ، نذهب إلى www.anywebservice.ru/logout لإنهاء العمل مع الخدمة للمستخدم الحالي وحذف ملفات تعريف الارتباط (لاختبار خدمة الويب الحالية ، هذا الإجراء كافٍ لمحاكاة إطلاق "فريد"). رمز الاختبار نفسه لم يتغير.
نتيجة لذلك ، بمساعدة التحسينات البسيطة ، حصلنا على الأقل على انخفاض مضاعف في وقت تشغيل الاختبار التلقائي ، مقارنة بالتنفيذ الأولي.
مقارنة الاختبارات testNG ، pytest ، pytest-bdd
أولاً ، سننظر في تنفيذ اختبار على إطار عمل اختبار testNG في لغة برمجة Java ، والتي ، مثل Spock Framework ، مستوحاة من إطار عمل jUnit وتدعم الاختبار المستند إلى البيانات.
package javaTests;
import org.testng.Assert;
import org.testng.annotations.*;
import pages.LoginPage;
public class LoginPageTest extends BaseTest {
@BeforeClass
public final void setup() {
createDriver();
driver.get("www.anywebservice.ru");
}
@DataProvider(name = "userParameters")
public final Object[][] getUserData(){
return new Object[][] {
{"adminLogin", "adminPassword"},
{"moderatorLogin", "moderatorPassword"},
{"userLogin", "userPassword"}
};
}
@Test(description = "QAA-1-1: Authorization with correct login and password",
dataProvider = "userParameters")
public final void authorizationWithCorrectLoginAndPassword(String login, String password){
//Login page
LoginPage loginPage = new LoginPage(driver);
//Log in with correct login and password
loginPage.login(login, password);
//Authorized and moved to main page
Assert.assertEquals("www.anywebservice.ru/main", driver.getCurrentUrl());
}
@AfterMethod
public final void cleanup() {
driver.get("www.anywebservice.ru/logout");
driver.manage().deleteAllCookies();
}
@AfterClass
public final void tearDown() {
driver.quit();
}
}
هنا يمكننا أن نرى فئة الاختبار مع جميع عمليات الإعداد الضرورية () ، وطرق التنظيف () ، بالإضافة إلى تحديد معاملات الاختبار في شكل طريقة getUserData () إضافية مع التعليق التوضيحيDataProvider ، والذي يبدو مرهقًا بعض الشيء بعد ما فحصناه في الاختبار باستخدام Spock إطار العمل. أيضًا ، لفهم ما يحدث في الاختبار ، تم ترك تعليقات مشابهة لوصف الخطوات.
تجدر الإشارة إلى أن testNG ، على عكس Spock Framework ، يدعم تنفيذ الاختبار المتوازي.
بعد ذلك ، دعنا ننتقل إلى اختبار باستخدام إطار عمل اختبار pytest في لغة برمجة Python.
import pytest
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from PageObjects.LoginPage import LoginPage
class TestLogin(object):
@pytest.mark.parametrize("login,password", [
pytest.param(("adminLogin", "adminPassword"), id='admin'),
pytest.param(("moderatorLogin", "moderatorPassword"), id='moderator'),
pytest.param(("userLogin", "userPassword"), id='user')
])
def test_authorization_with_correct_login_and_password(self, login, password, driver, test_cleanup):
# Login page
login_page = LoginPage(driver)
# Log in with correct login and password
login_page.login(login, password)
# Authorized and moved to main page
assert expected_conditions.url_to_be("www.anywebservice.ru/main")
@pytest.fixture()
def test_cleanup(self, driver):
yield "test"
driver.get("www.anywebservice.ru/logout")
driver.delete_all_cookies()
هنا نرى أيضًا دعمًا للاختبار المستند إلى البيانات كإنشاء منفصل ، على غرارDataProvider في testNG. طريقة تكوين برنامج تشغيل الويب "مخفية" في تركيبات برنامج التشغيل. بفضل الكتابة الديناميكية وتركيبات pytest ، يبدو هذا الاختبار أنظف من Java.
بعد ذلك ، دعنا ننتقل إلى نظرة عامة على رمز الاختبار باستخدام المكون الإضافي pytest-bdd ، والذي يسمح لك بكتابة الاختبارات في شكل ملفات ميزة Gherkin (نهج BDD النقي).
تسجيل الدخول
Feature: Login page
A authorization
Scenario: Authorizations with different users
Given Login page
When Log in with correct login and password
Then Authorized and moved to main page
test_login.py
import pytest
from pytest_bdd import scenario, given, when, then
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from PageObjects.LoginPage import LoginPage
@pytest.mark.parametrize("login,password", [
pytest.param(("adminLogin", "adminPassword"), id='admin'),
pytest.param(("moderatorLogin", "moderatorPassword"), id='moderator'),
pytest.param(("userLogin", "userPassword"), id='user')
])
@scenario('login.feature', 'Authorizations with different users')
def test_login(login, password):
pass
@given('Login page')
def login_page(driver):
return LoginPage(driver)
@when('Log in with correct login and password')
def login_with_correct_login_and_password(login_page, login, password):
login_page_object = login_page
login_page_object.login(login, password)
@then('Authorized and moved to main page')
def authorized_and_moved_to_main_page(driver, login):
assert expected_conditions.url_to_be("www.anywebservice.ru/main")
تتمثل إحدى المزايا في أنه لا يزال إطار عمل pytest ، والذي يحتوي على العديد من المكونات الإضافية لمواقف مختلفة ، بما في ذلك إجراء الاختبارات بشكل متوازٍ. الجانب السلبي هو نهج BDD النقي نفسه ، والذي سيحد باستمرار من المطور بخصائصه الخاصة. يتيح Spock Framework كتابة كود أكثر إيجازًا وسهل التصميم مقارنة بحزمة PyTest + pytest-bdd.
خاتمة
في هذه المقالة ، نظرنا في كيفية تبسيط العمل مع BDD باستخدام إطار عمل Spock. بإيجاز ، دعنا نسلط الضوء بإيجاز ، في رأينا ، على إيجابيات وسلبيات Spock مقارنة ببعض أطر الاختبار الشائعة الأخرى.
الايجابيات:
- يمنحك استخدام مبادئ BDD بدلاً من نهج BDD النقي مزيدًا من المرونة عند كتابة الاختبارات.
- مواصفات الاختبار المكتوبة هي أيضًا توثيق للنظام.
- .
- groovy ( , , closures ).
:
- groovy. , , IDE , . Intellij IDEA, , , , .
- groovy JVM -. , groovy, , . java, groovy .
- مجموعة الامتدادات ليست شاملة مثل مجموعة testNG ، على سبيل المثال. نتيجة لذلك ، لا يوجد تشغيل اختباري موازٍ. هناك خطط لإضافة هذه الوظيفة ، لكن توقيت تنفيذها غير معروف.
في نهاية المطاف ، يستحق Spock Framework الاهتمام لأنه مناسب لحل المشكلة الشائعة المعقدة المتمثلة في أتمتة سيناريوهات الأعمال لمنتجات البرامج ذات الموضوعات العالية والتعقيد التنظيمي.
ماذا يمكنك أن تقرأ أيضًا: