كتابة الاختبارات الذاتية بشكل فعال - اختبارات تحت الجلد

دعونا نتخيل حالة افتراضية (نغرق فيها بانتظام). لقد تم تعيينك في مشروع لأتمتة "gash". يتم منحك خطة اختبار ضخمة مع عدد كبير (الآلاف منهم!) من الاختبارات "اليدوية" ، ويقولون إنك بحاجة إلى القيام بشيء ما ، وهناك على الفور. وأيضًا ، بسرعة وثبات.

لقد فات الأوان لكتابة اختبارات الوحدة ، أو حتى التفكير في TDD ، فقد تمت كتابة رمز المنتج منذ فترة طويلة. كلمتك أيها الرفيق autotester!

صورة

لحسن الحظ ، هناك خدعة صغيرة من شأنها زيادة التغطية وجعل الاختبارات مستقرة وسريعة - الاختبارات تحت الجلد ("الاختبارات تحت الجلد") ، ولكن أولاً وقبل كل شيء.


جوهر المشكلة


أول منعكس شرطي للأتمتة هو أخذ السيلينيوم (حسنًا ، إما هناك ، سيلينايد ، أو معجزة أخرى لاختبارات واجهة المستخدم). هذا هو معيار الصناعة ، ولكن هناك العديد من الأسباب التي لا تقلع:

  • اختبارات واجهة المستخدم بطيئة. لا مفر من هذا. يمكن تشغيلها بالتوازي ، وحفظها وتنفيذها بشكل أسرع قليلاً ، لكنها ستظل بطيئة.
  • اختبارات واجهة المستخدم غير مستقرة. جزئيا لأنها بطيئة. وأيضًا لأن متصفح الويب وواجهة المستخدم لم يتم إنشاؤهما للتحكم بواسطة الكمبيوتر (هذا الاتجاه يتغير حاليًا ، ولكن ليس حقيقة أن هذا جيد).
  • UI- — . . ( , , , «» - , ).
  • , , , UI- . . ID XPath . , «» - - . , , — - , .
  • سيقول شخص ما أن بعض الوظائف ببساطة لا يمكن اختبارها بطريقة أخرى. سأقول أنه إذا كانت هناك وظيفة لا يمكن اختبارها إلا من خلال اختبارات واجهة المستخدم (باستثناء منطق واجهة المستخدم نفسه) ، فقد يكون هذا علامة جيدة على المشكلات المعمارية في المنتج.

الإضافة الحقيقية الوحيدة لاختبارات واجهة المستخدم هي أنها تسمح لك "برمي" فحوصات مفيدة أكثر أو أقل دون الحاجة إلى الغوص ودراسة رمز المنتج نفسه. وهو بالكاد زائد على المدى الطويل. شرح أكثر تفصيلاً لماذا يمكن سماع هذا في هذا العرض التقديمي .

حل بديل


كحالة بسيطة للغاية ، دعنا نفكر في تطبيق يتكون من نموذج حيث يمكنك إدخال اسم مستخدم صالح. إذا قمت بإدخال اسم مستخدم يطابق القواعد - سيتم إنشاء المستخدم في النظام وتسجيله في قاعدة البيانات.



يمكن العثور على رمز المصدر للتطبيق هنا: github.com/senpay/login-form . لقد تم تحذيرك - في التطبيق هناك الكثير من الأخطاء ولا توجد أدوات وإطارات عمل عصرية.

إذا حاولت "رمي" ورقة تحقق لهذا التطبيق ، يمكنك الحصول على شيء مثل:
رقمخطواتنتائج متوقعة
11. أدخل اسم مستخدم صالح
2. انقر فوق الزر "تسجيل الدخول"
1.
2. يتم إنشاء مستخدم جديد.
21. أدخل اسم مستخدم فارغًا
2. انقر على الزر "تسجيل الدخول"
1.
2. يتم إعطاء رسالة الخطأ.

هل تبدو بسيطة؟ ببساطة! هل يمكنني كتابة اختبارات واجهة المستخدم؟ يستطيع. يمكن العثور على مثال للاختبارات المكتوبة (جنبًا إلى جنب مع إطار عمل كامل من ثلاثة مستويات ) في LoginFormTest.java إذا انتقلت إلى تسمية uitests في git ( git checkout uitests ):

public class LoginFormTest {

    SelenideMainPage sut = SelenideMainPage.INSTANCE;
    private static final String APPLICATION_URL = "http://localhost:4567/index";

    @BeforeClass
    public static void setUpClass() {
        final String[] args = {};
        Main.main(args);
        Configuration.browser = "firefox";
    }

    @Before
    public void setUp() {
        open(APPLICATION_URL);
    }

    @After
    public void tearDown() {
        close();
    }

    @Test
    public void shouldBeAbleToAddNewUser() {
        sut.setUserName("MyCoolNewUser");
        sut.clickSubmit();
        Assert.assertEquals("Status: user MyCoolNewUser was created", sut.getStatus());
        Assert.assertTrue(sut.getUsers().contains("Name: MyCoolNewUser"));
    }

    @Test
    public void shouldNotBeAbleToAddEmptyUseName() {
        final int numberOfUsersBeforeTheTest = sut.getUsers().size();
        sut.clickSubmit();
        Assert.assertEquals("Status: Login cannot be empty", sut.getStatus());
        Assert.assertEquals(numberOfUsersBeforeTheTest, sut.getUsers().size());
    }
}


بعض المقاييس لهذا الرمز:
وقت التشغيل: 12 ثانية تقريبًا (12 ثانية 956 مللي ثانية في آخر مرة أجريت فيها هذه الاختبارات) فئة
الرمز
: 100٪
الطريقة: 93.8٪ (30/32)
الخط: 97.4٪ (75/77 )

لنفترض الآن أنه يمكن كتابة الاختبارات التلقائية الوظيفية على مستوى "أسفل" واجهة المستخدم مباشرةً. تسمى هذه التقنية بالاختبارات تحت الجلد ("الاختبارات تحت الجلد" - الاختبارات التي تختبر مباشرة أسفل مستوى منطق العرض) وقد تم اقتراحها من قبل مارتن فاولر منذ فترة طويلة [ 1 ].

عندما يفكر الناس في الاختبارات التلقائية "غير المتعلقة بواجهة المستخدم" ، غالبًا ما يفكرون على الفور في REST / SOAP أو API الخاص به. لكن API (واجهة برمجة التطبيقات) هي مفهوم أوسع بكثير ، ولا تؤثر بالضرورة على HTTP وبروتوكولات الوزن الثقيل الأخرى.

إذا اخترنا رمز منتج ، فيمكننا العثور على شيء مثير للاهتمام:
public class UserApplication {

    private static IUserRepository repository = new InMemoryUserRepository();
    private static UserService service = new UserService(); {
        service.setUserRepository(repository);
    }

    public Map<String, Object> getUsersList() {
        return getUsersList("N/A");
    }

    public Map<String, Object> addUser(final String username) {
        final String status = service.addUser(username);
        final Map<String, Object> model = getUsersList(status);
        return model;
    }

    private Map<String, Object> getUsersList(String status) {
        final Map<String, Object> model = new HashMap<>();
        model.put("status", status);
        model.put("users", service.getUserInfoList());
        return model;
    }
}


عندما نضغط على واجهة المستخدم ، يتم استدعاء إحدى هذه الطرق ، أو تتم إضافة كائن مستخدم جديد ، أو يتم إرجاع قائمة بكائنات المستخدم التي تم إنشاؤها بالفعل. ماذا لو استخدمنا هذه الأساليب مباشرة ؟ بعد كل شيء ، هذه واجهة برمجة تطبيقات حقيقية! والأهم من ذلك ، REST وواجهات برمجة التطبيقات الأخرى تعمل أيضًا على نفس المبدأ - يسمونها طريقة معينة "مستوى التحكم".

باستخدام هذه الطرق مباشرة ، يمكننا كتابة اختبار أبسط وأفضل:
public class UserApplicationTest {

    private UserApplication sut;

    @Before
    public void setUp() {
       sut = new UserApplication();
    }

    @Test
    public void shouldBeAbleToAddNewUser() {
        final Map<String, Object> myCoolNewUser = sut.addUser("MyCoolNewUser");
        Assert.assertEquals("user MyCoolNewUser was created", myCoolNewUser.get("status"));
        Assert.assertTrue(((List) myCoolNewUser.get("users")).contains("Name: MyCoolNewUser"));
    }

    @Test
    public void shouldNotBeAbleToAddEmptyUseName() {
        final Map<String, Object> usersBeforeTest = sut.getUsersList();
        final int numberOfUsersBeforeTheTest = ((List) usersBeforeTest.get("users")).size();
        final Map<String, Object> myCoolNewUser = sut.addUser("");
        Assert.assertEquals("Login cannot be empty", myCoolNewUser.get("status"));
        Assert.assertEquals(numberOfUsersBeforeTheTest, ((List) myCoolNewUser.get("users")).size());
    }
}


هذا الرمز متاح تحت العلامات الفرعية للملصق :

git checkout subctests


دعونا نحاول جمع المقاييس؟
وقت التنفيذ: ~ 21 مللي ثانية
تغطية الرمز :
الفئة: 77.8٪
الطريقة: 78.1 (30/32)
الخط: 78.7 (75/77)

لقد فقدنا القليل من التغطية ، لكن سرعة الاختبارات زادت 600 مرة !!!

ما مدى أهمية / أهمية فقدان التغطية في هذه الحالة؟ يعتمد على الموقف. لقد فقدنا القليل من رمز الغراء ، والذي قد يكون (أو قد لا يكون) مهمًا (أوصي بتحديد الرمز الذي تم فقده كتمرين).

هل يبرر فقدان التغطية هذا إدخال اختبار الوزن الثقيل على مستوى واجهة المستخدم؟ كما يعتمد على الوضع. يمكننا ، على سبيل المثال:
  • أضف اختبارًا واحدًا لواجهة المستخدم للتحقق من رمز الغراء ، أو
  • إذا كنا لا نتوقع تغييرات متكررة في رمز الغراء - اتركه بدون اختبارات ذاتية ، أو
  • إذا كان لدينا نوع من الاختبار "اليدوي" ، فهناك احتمال كبير أن يلاحظ المختبر مشاكل في رمز الغراء ، أو
  • توصل إلى شيء آخر (نفس نشر الكناري)


في النهاية


  • لا يلزم كتابة الاختبارات التلقائية الوظيفية على مستوى واجهة المستخدم أو REST / SOAP API. إن استخدام "الاختبارات تحت الجلد" في العديد من المواقف سيختبر نفس الوظيفة بسرعة أكبر وثبات أكبر.
  • أحد عيوب النهج هو فقدان معين للتغطية.
  • إحدى طرق تجنب فقدان التغطية هي " نموذج اختبارات الميزات "
  • ولكن حتى مع فقدان التغطية ، فإن الزيادة في السرعة والاستقرار كبيرة.


يتوفر إصدار باللغة الإنجليزية من المقالة هنا .

All Articles