لن تجد اليوم تطبيقًا غير مملوء بالاختبارات، لذلك سيكون هذا الموضوع أكثر أهمية من أي وقت مضى للمطورين المبتدئين: لا يمكنك النجاح بدون اختبارات. دعونا نفكر في أنواع الاختبارات المستخدمة من حيث المبدأ، وبعد ذلك سندرس بالتفصيل كل ما يمكن معرفته حول اختبار الوحدة.
أنواع الاختبار
ما هو الاختبار؟ وفقًا لويكيبيديا: "يتضمن اختبار البرامج تنفيذ مكون برنامج أو مكون نظام لتقييم خاصية واحدة أو أكثر محل اهتمام." بمعنى آخر، إنه التحقق من صحة نظامنا في مواقف معينة. حسنًا، دعونا نرى ما هي أنواع الاختبارات الموجودة بشكل عام:- اختبار الوحدة - اختبارات الغرض منها هو فحص كل وحدة من وحدات النظام بشكل منفصل. وينبغي أن تنطبق هذه الاختبارات على أصغر الأجزاء الذرية في النظام، على سبيل المثال الوحدات.
- اختبار النظام — اختبار عالي المستوى للتحقق من تشغيل جزء أكبر من التطبيق أو النظام ككل.
- اختبار الانحدار — اختبار يُستخدم للتحقق مما إذا كانت الميزات الجديدة أو إصلاحات الأخطاء تؤثر على وظائف التطبيق الحالية أو تقدم أخطاء قديمة.
- الاختبار الوظيفي - التحقق مما إذا كان جزء من التطبيق يلبي المتطلبات المنصوص عليها في المواصفات وقصص المستخدم وما إلى ذلك.
أنواع الاختبارات الوظيفية:
- اختبار المربع الأبيض — التحقق مما إذا كان جزء من التطبيق يلبي المتطلبات مع معرفة التنفيذ الداخلي للنظام؛
- اختبار الصندوق الأسود - التحقق مما إذا كان جزء من التطبيق يلبي المتطلبات دون معرفة التنفيذ الداخلي للنظام.
- اختبار الأداء - الاختبارات المكتوبة لتحديد كيفية أداء النظام أو جزء من النظام تحت حمل معين.
- اختبار الحمل — اختبارات مصممة للتحقق من استقرار النظام في ظل الأحمال القياسية والعثور على الحد الأقصى للحمل الذي لا يزال التطبيق يعمل فيه بشكل صحيح.
- اختبار الإجهاد — اختبار مصمم للتحقق من أداء التطبيق في ظل الأحمال غير القياسية ولتحديد الحد الأقصى للحمل قبل فشل النظام.
- اختبار الأمان - الاختبارات المستخدمة للتحقق من أمان النظام (من المتسللين والفيروسات والوصول غير المصرح به إلى البيانات السرية وغيرها من الهجمات المبهجة).
- اختبار التعريب — اختبارات توطين التطبيق.
- اختبار قابلية الاستخدام - اختبار يهدف إلى التحقق من سهولة الاستخدام وقابلية الفهم والجاذبية وقابلية التعلم.
- الوحدة — يشير هذا القسم إلى اختبارات الوحدة، التي يتم تطبيقها في طبقات مختلفة من التطبيق. إنهم يختبرون أصغر وحدة قابلة للقسمة من منطق التطبيق. على سبيل المثال، الطبقات، ولكن في أغلب الأحيان الأساليب. تحاول هذه الاختبارات عادةً قدر الإمكان عزل ما يتم اختباره عن أي منطق خارجي. أي أنهم يحاولون خلق الوهم بأن بقية التطبيق يعمل كما هو متوقع.
يجب أن يكون هناك دائمًا الكثير من هذه الاختبارات (أكثر من أي نوع آخر)، نظرًا لأنها تختبر قطعًا صغيرة وخفيفة الوزن جدًا، ولا تستهلك الكثير من الموارد (أي ذاكرة الوصول العشوائي والوقت).
- التكامل - يشير هذا القسم إلى اختبار التكامل. يقوم هذا الاختبار بفحص أجزاء أكبر من النظام. أي أنه إما يجمع بين عدة أجزاء من المنطق (عدة طرق أو فئات)، أو أنه يتحقق من صحة التفاعل مع مكون خارجي. عادة ما تكون هذه الاختبارات أصغر من اختبارات الوحدة لأنها أثقل.
مثال على اختبار التكامل يمكن أن يكون الاتصال بقاعدة بيانات والتحقق من صحة طرق العمل معها.
- واجهة المستخدم - يشير هذا القسم إلى الاختبارات التي تتحقق من تشغيل واجهة المستخدم. إنها تتضمن المنطق على جميع مستويات التطبيق، ولهذا السبب تُسمى أيضًا الاختبارات الشاملة. كقاعدة عامة، هناك عدد أقل بكثير منها، لأنها الأكثر تعقيدًا ويجب التحقق من المسارات الأكثر ضرورة (المستخدمة).
في الصورة أعلاه، نرى أن الأجزاء المختلفة للمثلث تختلف في الحجم: توجد نفس النسب تقريبًا في عدد أنواع الاختبارات المختلفة في العمل الحقيقي.
سنلقي اليوم نظرة فاحصة على الاختبارات الأكثر شيوعًا، واختبارات الوحدة، نظرًا لأن جميع مطوري Java الذين يحترمون أنفسهم يجب أن يكونوا قادرين على استخدامها على المستوى الأساسي.
المفاهيم الأساسية في اختبار الوحدة
تعد تغطية الاختبار (تغطية الكود) أحد المقاييس الرئيسية لمدى جودة اختبار التطبيق. هذه هي النسبة المئوية للكود الذي تغطيه الاختبارات (0-100%). ومن الناحية العملية، يسعى الكثيرون إلى تحقيق هذه النسبة كهدف لهم. وهذا شيء لا أوافق عليه، لأنه يعني بدء تطبيق الاختبارات حيث لا تكون هناك حاجة إليها. على سبيل المثال، لنفترض أن لدينا عمليات CRUD (إنشاء/حصول/تحديث/حذف) قياسية في خدمتنا دون منطق إضافي. هذه الأساليب عبارة عن وسطاء بحتين يقومون بتفويض العمل إلى الطبقة التي تعمل مع المستودع. في هذه الحالة، ليس لدينا ما نختبره، ربما باستثناء ما إذا كانت الطريقة المحددة تستدعي طريقة DAO، ولكن هذه مزحة. تُستخدم عادةً أدوات إضافية لتقييم تغطية الاختبار: JaCoCo، وCobertura، وClover، وEmma، وما إلى ذلك. للحصول على دراسة أكثر تفصيلاً لهذا الموضوع، إليك بعض المقالات ذات الصلة: TDD لتقف علي التطوير القائم على الاختبار. في هذا الأسلوب، قبل القيام بأي شيء آخر، تكتب اختبارًا يتحقق من كود معين. لقد تبين أن هذا بمثابة اختبار للصندوق الأسود: فنحن نعرف المدخلات ونعرف ما يجب أن تكون عليه المخرجات. وهذا يجعل من الممكن تجنب تكرار التعليمات البرمجية. يبدأ التطوير القائم على الاختبار بتصميم وتطوير الاختبارات لكل جزء من الوظائف في تطبيقك. في نهج TDD، نقوم أولاً بإنشاء اختبار يحدد ويختبر سلوك الكود. الهدف الرئيسي من TDD هو جعل التعليمات البرمجية الخاصة بك أكثر قابلية للفهم وأبسط وخالية من الأخطاء. ويتكون النهج مما يلي:- نكتب اختبارنا.
- نقوم بإجراء الاختبار. ومن غير المستغرب أن يفشل، لأننا لم ننفذ بعد المنطق المطلوب.
- أضف الكود الذي يؤدي إلى اجتياز الاختبار (نجري الاختبار مرة أخرى).
- نحن نعيد صياغة الكود.
مراحل الاختبار
يتكون الاختبار من ثلاث مراحل:- تحديد بيانات الاختبار (التركيبات).
- قم بتمرين الكود قيد الاختبار (استدعاء الطريقة التي تم اختبارها).
- التحقق من النتائج ومقارنتها بالنتائج المتوقعة.
بيئات الاختبار
لذا، الآن إلى هذه النقطة. هناك العديد من بيئات الاختبار (الأطر) المتاحة لـ Java. الأكثر شعبية من هذه هي JUnit وTestNG. لمراجعتنا هنا، نستخدم: اختبار JUnit هو طريقة في الفصل تُستخدم للاختبار فقط. عادةً ما يتم تسمية الفصل بنفس اسم الفصل الذي يختبره، مع إلحاق كلمة "اختبار" في النهاية. على سبيل المثال، CarService -> CarServiceTest. يتضمن نظام البناء Maven تلقائيًا مثل هذه الفئات في نطاق الاختبار. في الواقع، تسمى هذه الفئة فئة الاختبار. دعنا نتناول بإيجاز التعليقات التوضيحية الأساسية:- يشير @Test إلى أن الطريقة عبارة عن اختبار (في الأساس، الطريقة المميزة بهذا التعليق التوضيحي هي اختبار وحدة).
- @Before يشير إلى الطريقة التي سيتم تنفيذها قبل كل اختبار. على سبيل المثال، لملء الفصل ببيانات الاختبار، وقراءة بيانات الإدخال، وما إلى ذلك.
- يتم استخدام @After لتحديد الطريقة التي سيتم استدعاؤها بعد كل اختبار (على سبيل المثال، لمسح البيانات أو استعادة القيم الافتراضية).
- يتم وضع @BeforeClass فوق طريقة مشابهة لـBefore. ولكن يتم استدعاء مثل هذه الطريقة مرة واحدة فقط قبل جميع الاختبارات للفئة المحددة، وبالتالي يجب أن تكون ثابتة. يتم استخدامه لإجراء المزيد من العمليات كثيفة الاستخدام للموارد، مثل تشغيل قاعدة بيانات اختبارية.
- @AfterClass هو عكس @BeforeClass: يتم تنفيذه مرة واحدة للفئة المحددة، ولكن فقط بعد كل الاختبارات. يتم استخدامه، على سبيل المثال، لمسح الموارد المستمرة أو قطع الاتصال بقاعدة بيانات.
- يشير @Ignore إلى أن الطريقة معطلة وسيتم تجاهلها أثناء تشغيل الاختبار الشامل. يُستخدم هذا في مواقف مختلفة، على سبيل المثال، إذا تم تغيير الطريقة الأساسية ولم تتم إعادة صياغة الاختبار بعد لاستيعاب التغييرات. في مثل هذه الحالات، من المستحسن أيضًا إضافة وصف، أي @Ignore("Some description").
- يتم استخدام @Test(expected = Exception.class) للاختبارات السلبية. هذه هي الاختبارات التي تتحقق من كيفية تصرف الطريقة في حالة حدوث خطأ، أي أن الاختبار يتوقع أن تقوم الطريقة بطرح نوع من الاستثناء. تتم الإشارة إلى هذه الطريقة من خلال التعليق التوضيحيTest، ولكن مع الإشارة إلى الخطأ الذي يجب اكتشافه.
- @Test(timeout = 100) يتحقق من تنفيذ الطريقة في مدة لا تزيد عن 100 مللي ثانية.
- يتم استخدام @Mock أعلى الحقل لتعيين كائن وهمي (هذا ليس تعليقًا توضيحيًا لـ JUnit، ولكنه يأتي بدلاً من ذلك من Mockito). حسب الحاجة، قمنا بضبط سلوك النموذج لموقف معين مباشرةً في طريقة الاختبار.
- يتم وضع @RunWith(MockitoJUnitRunner.class) فوق الفصل الدراسي. يخبر هذا التعليق التوضيحي JUnit باستدعاء الاختبارات في الفصل. هناك العديد من البرامج المتسابقة، بما في ذلك: MockitoJUnitRunner وJUnitPlatform وSpringRunner. في JUnit 5، تم استبدال التعليق التوضيحي @RunWith بالتعليق التوضيحي @ExtendWith الأكثر قوة.
- AssuredEquals(الكائن المتوقع، الكائن الفعلي) - يتحقق مما إذا كانت الكائنات التي تم تمريرها متساوية.
- تأكيد ترو (علامة منطقية) - يتحقق مما إذا كانت القيمة التي تم تمريرها صحيحة.
- تأكيدFalse (علامة منطقية) - يتحقق مما إذا كانت القيمة التي تم تمريرها خاطئة.
- تأكيد Null (كائن كائن) - يتحقق مما إذا كان الكائن الذي تم تمريره فارغًا.
- تأكيدSame(Object firstObject, Object SecondObject) - يتحقق مما إذا كانت القيم التي تم تمريرها تشير إلى نفس الكائن.
- AssurThat(T, Matcher
المطابق) — التحقق مما إذا كان يفي بالشرط المحدد في المطابق.
الاختبار في الممارسة العملية
الآن دعونا نلقي نظرة على المواد المذكورة أعلاه في مثال محدد. سنقوم باختبار طريقة تحديث الخدمة. لن نأخذ في الاعتبار طبقة DAO، لأننا نستخدم الطبقة الافتراضية. دعونا نضيف بداية للاختبارات:<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.2.2.RELEASE</version>
<scope>test</scope>
</dependency>
وهنا لدينا فئة الخدمة:
@Service
@RequiredArgsConstructor
public class RobotServiceImpl implements RobotService {
private final RobotDAO robotDAO;
@Override
public Robot update(Long id, Robot robot) {
Robot found = robotDAO.findById(id);
return robotDAO.update(Robot.builder()
.id(id)
.name(robot.getName() != null ? robot.getName() : found.getName())
.cpu(robot.getCpu() != null ? robot.getCpu() : found.getCpu())
.producer(robot.getProducer() != null ? robot.getProducer() : found.getProducer())
.build());
}
}
السطر 8 - اسحب الكائن المحدث من قاعدة البيانات. الأسطر 9-14 - قم بإنشاء كائن من خلال المنشئ. إذا كان الكائن الوارد يحتوي على حقل، فقم بتعيينه. إذا لم يكن الأمر كذلك، سنترك ما هو موجود في قاعدة البيانات. انظر الآن إلى اختبارنا:
@RunWith(MockitoJUnitRunner.class)
public class RobotServiceImplTest {
@Mock
private RobotDAO robotDAO;
private RobotServiceImpl robotService;
private static Robot testRobot;
@BeforeClass
public static void prepareTestData() {
testRobot = Robot
.builder()
.id(123L)
.name("testRobotMolly")
.cpu("Intel Core i7-9700K")
.producer("China")
.build();
}
@Before
public void init() {
robotService = new RobotServiceImpl(robotDAO);
}
السطر 1 - عداءنا. السطر 4 - نقوم بعزل الخدمة عن طبقة DAO عن طريق استبدال طبقة وهمية. السطر 11 - قمنا بتعيين كيان اختباري (الذي سنستخدمه كخنزير غينيا) للفصل الدراسي. السطر 22 - قمنا بتعيين كائن الخدمة، وهو ما سنختبره.
@Test
public void updateTest() {
when(robotDAO.findById(any(Long.class))).thenReturn(testRobot);
when(robotDAO.update(any(Robot.class))).then(returnsFirstArg());
Robot robotForUpdate = Robot
.builder()
.name("Vally")
.cpu("AMD Ryzen 7 2700X")
.build();
Robot resultRobot = robotService.update(123L, robotForUpdate);
assertNotNull(resultRobot);
assertSame(resultRobot.getId(),testRobot.getId());
assertThat(resultRobot.getName()).isEqualTo(robotForUpdate.getName());
assertTrue(resultRobot.getCpu().equals(robotForUpdate.getCpu()));
assertEquals(resultRobot.getProducer(),testRobot.getProducer());
}
وهنا نرى أن الاختبار يحتوي على ثلاثة أقسام واضحة: الأسطر 3-9 - تحديد التركيبات. السطر 11 - تنفيذ الكود قيد الاختبار. الأسطر 13-17 - التحقق من النتائج. بمزيد من التفصيل: الأسطر 3-4 - قم بتعيين سلوك محاكاة DAO. السطر 5 - قم بتعيين المثيل الذي سنقوم بتحديثه وفقًا لمعاييرنا. السطر 11 - استخدم الطريقة وخذ المثيل الناتج. السطر 13 - تأكد من أنه ليس فارغًا. السطر 14 - قارن معرف النتيجة ووسائط الطريقة المحددة. السطر 15 - تحقق مما إذا كان الاسم قد تم تحديثه. السطر 16 - شاهد نتيجة وحدة المعالجة المركزية. السطر 17 - لم نحدد هذا الحقل في المثيل، لذا يجب أن يظل كما هو. نحن نتحقق من هذا الشرط هنا. لنجري الأمر: الاختبار أخضر! يمكننا أن نتنفس الصعداء :) باختصار، يعمل الاختبار على تحسين جودة الكود ويجعل عملية التطوير أكثر مرونة وموثوقية. تخيل مقدار الجهد المبذول لإعادة تصميم البرامج التي تتضمن مئات الملفات الصفية. عندما يكون لدينا اختبارات وحدة مكتوبة لجميع هذه الفئات، يمكننا إعادة البناء بثقة. والأهم من ذلك أنه يساعدنا في العثور بسهولة على الأخطاء أثناء التطوير. شباب وبنات، هذا كل ما لدي اليوم. أعطني لايك واترك تعليق :)
GO TO FULL VERSION