استراتيجية الاختبار للأنظمة الإنتاجية: الوحدات، التكامل مع قواعد بيانات حقيقية، واختبارات E2E
بناء هرم اختبارات عملي: تحديد حدود العزل واستخدام الحاويات المؤقتة للاختبار مقابل قواعد بيانات حقيقية وتفادي الاختبارات المتقلبة.
🧪 Testing مش "آخر خطوة"
الكود الشغال مش بالضرورة كود صح.
والـ Test Suite القوية مش اللي عندها أكبر رقم Coverage، لكن اللي بتديك ثقة حقيقية في سلوك النظام.
📚 المحتويات
- السؤال
- Testing ليه؟
- Testing مش آخر خطوة
- Coverage
- Mutation Testing
- اختبر السلوك مش السطور
- Unit Testing
- Mocking و SOLID
- Flaky Tests
- Integration Testing
- Testcontainers
- Test Pyramid وهل ما زالت كافية؟
- End-to-End Testing
- Architecture Testing
- Contract Testing
- Load Testing
- Stress Testing
- Regression Testing
- TDD
- BDD
- CI/CD
- اختيار نوع الـ Test
- مثال عملي: Create Account
- أخطاء شائعة
- إجابة انترفيو جاهزة
- الخلاصة
- مصادر رسمية
🎯 السؤال
التيم خلص Feature جديدة.
وحد قال:
"تمام.. فاضل نكتب شوية Tests."
إيه رأيك في الجملة دي؟
المشكلة إن ناس كتير بتتعامل مع الـ Testing كأنه آخر خطوة:
Write Code
↓
Run It
↓
It Works
↓
Write Some Tests
↓
Done
لكن الـ Testing مش مجرد مرحلة بنضيفها بعد انتهاء البرمجة.
هو جزء من طريقة بناء النظام نفسه.
🤔 Testing ليه؟
السؤال الأساسي:
إحنا بنعمل Testing ليه؟
مش فقط عشان نكتشف Bug النهارده.
لكن عشان نعرف إن:
Feature الجديدة
↓
لم تكسر القديمة
وعشان بعد 6 شهور:
Developer changes code
↓
Tests run
↓
Something breaks
↓
We know before production
فيه فرق ضخم بين:
Code works
و:
Code behaves correctly
والاختبارات الجيدة تساعدك تثق في السلوك المطلوب.
🚫 Testing مش آخر خطوة
الأفضل إنك تبدأ تفكر في الـ tests أثناء تصميم الـ feature.
قبل ما تكتب implementation اسأل:
What behavior am I trying to guarantee?
وبعدين:
What can go wrong?
وبعدين تحدد:
Which test level catches this failure cheapest?
وده أفضل من إنك تكتب 500 test في آخر اليوم فقط عشان تحقق رقم Coverage.
📊 Coverage
Coverage غالبًا تعني نسبة أجزاء من الكود تم تنفيذها أثناء تشغيل الاختبارات.
مثلاً:
100 executable lines
80 executed by tests
قد ينتج عنه تقريبًا:
80% line coverage
لكن:
⚠️ 80% Coverage ≠ 80% correctness
وممكن جدًا يكون عندك:
95% Coverage
ومع ذلك عندك Bug خطير.
ليه؟
لأن الـ Coverage يجيب لك معلومة عن ما تم تنفيذه، وليس بالضرورة هل تم التحقق من النتيجة الصحيحة.
مثال:
var discount = price * 0.10;
Test يشغل السطر.
لكن لو ماعملتش assertion صح، ممكن السطر يتنفذ والـ test ينجح بدون ما يثبت الـ business rule.
عشان كده:
Coverage metric وليست شهادة جودة.
🧬 Mutation Testing
طيب لو الـ Coverage مش كفاية، إزاي نعرف إن الاختبارات نفسها قوية؟
هنا يظهر:
Mutation Testing
الفكرة:
بدل ما تسأل "كام سطر اتنفذ؟"
اسأل:
"لو بوّظت الكود عمدًا، هل الـ tests هتكتشف؟"
مثلاً عندك:
if (age >= 18)
{
return true;
}
Mutation ممكن تغيرها إلى:
if (age > 18)
{
return true;
}
لو الاختبارات فشلت:
Mutation killed ✅
ممتاز.
الاختبارات اكتشفت التغيير.
لو الاختبارات فضلت Green:
Mutation survived ❌
يبقى فيه جزء مهم من السلوك مش متختبر بشكل كافي.
Stryker.NET هو أحد أدوات mutation testing للـ .NET. فكرته الأساسية هي إنشاء تغييرات صغيرة في الكود وتشغيل الاختبارات لمعرفة أي تغييرات تم اكتشافها. citeturn0search16
🎯 اختبر السلوك مش السطور
غلط:
I need 90% coverage.
أفضل:
I need confidence that important behavior is protected.
مثلاً بدل ما تختبر:
Property getter
Property setter
Simple mapping
Framework-generated code
ركز على:
Business rules
Authorization
Validation
State transitions
Transactions
Error handling
Important integrations
Critical user journeys
قاعدة ممتازة:
اختبر القرارات، مش مجرد تنفيذ السطور.
🧪 Unit Testing
أشهر نوع.
الفكرة:
تختبر جزء صغير من الـ code في عزلة، بدون الاعتماد على external infrastructure.
مثال:
Gold Member
Price = 1000
Discount = 20%
Expected = 800
في .NET تقدر تستخدم frameworks مثل:
xUnit
NUnit
MSTest
xUnit.net من أشهر أطر الاختبار في .NET، وله إصدارات حديثة تشمل xUnit v3. citeturn0search16
Test بسيط:
[Fact]
public void GoldMember_Should_Get_20_Percent_Discount()
{
var result = CalculatePrice(1000, MemberType.Gold);
Assert.Equal(800, result);
}
المميز:
Fast
Cheap
Isolated
Easy to run
Easy to parallelize
وعشان كده ممكن يكون عندك آلاف Unit Tests بدون ما الـ suite كلها تبقى بطيئة جدًا.
🎭 Mocking و SOLID
السؤال:
Unit Test يكلم Database أو Payment Gateway؟
غالبًا:
لا
لو الهدف اختبار Business Logic فقط، مش محتاج تروح:
Internet
Database
Payment Provider
Email Provider
هنا تستخدم Test Double مناسب، مثل:
Mock
Stub
Fake
Spy
مثلاً عندك:
public class OrderService
{
private readonly IPaymentService _payment;
}
بدل:
OrderService
↓
Stripe/Paymob/...
في Unit Test:
OrderService
↓
Fake/Mock IPaymentService
وتقدر تقول:
Payment succeeds
أو:
Payment fails
بدون تنفيذ Payment حقيقي.
🧠 وهنا تظهر أهمية SOLID
لو الكلاس مربوط مباشرة بـ:
PaymobClient
هيبقى أصعب في الاختبار.
لكن لو معتمد على abstraction:
IPaymentService
تقدر تبدل implementation.
Production
↓
RealPaymentService
Tests
↓
FakePaymentService
لكن خلي بالك:
Mocking مش هدف في حد ذاته.
لو عملت Mock لكل حاجة لدرجة إن الـ tests بقت بتختبر الـ mocks بدل النظام الحقيقي، فأنت ممكن تحصل على ثقة وهمية.
⚠️ Flaky Tests
واحد من أسوأ أنواع الاختبارات:
Test ينجح مرة ويفشل مرة بدون تغيير في الكود.
مثلاً:
Run #1 → PASS
Run #2 → PASS
Run #3 → FAIL
Run #4 → PASS
المشكلة؟
الفريق يبدأ يقول:
"شغله تاني."
وبعد فترة:
"Ignore."
وهنا الكارثة.
لو الاختبار فشل بسبب Bug حقيقي، ممكن يتعاملوا معاه كأنه flaky.
أشهر أسباب الـ Flaky Tests:
DateTime.Now
Shared State
Uncontrolled Randomness
Database State
Async Timing
Race Conditions
Threading
External Services
Order-dependent Tests
Time Zones
Network Dependencies
قاعدة مهمة:
Test result لازم يعتمد على behavior بتاع الكود، مش الحظ أو التوقيت.
Playwright نفسه يوفر assertions مصممة للتعامل مع حالات الـ UI التي تحتاج انتظارًا صحيحًا، ويساعد على تقليل مشاكل race conditions والـ arbitrary timeouts. citeturn0search0
🔗 Integration Testing
هنا بنبدأ نختبر أكثر من جزء مع بعض.
مثلاً:
HTTP API
↓
Application Layer
↓
Repository
↓
Database
↓
Transaction
↓
Response
هنا مش بس بتسأل:
"هل Method رجعت 800؟"
بتسأل:
"هل الـ system interaction الحقيقي شغال؟"
مثلاً:
POST /members
وبعدها تتأكد:
HTTP 201
+
Member exists in DB
+
Expected columns saved
+
Transaction behavior correct
🐳 Testcontainers
واحدة من الأدوات المهمة هنا:
Testcontainers
بدل ما تعمل:
Mock Database
ممكن تشغل Database حقيقية مؤقتة داخل Docker.
مثلاً:
Test
↓
Start PostgreSQL Container
↓
Run migrations
↓
Run test
↓
Assert DB state
↓
Destroy container
Testcontainers for .NET مصمم لتشغيل instances مؤقتة من Docker containers أثناء الاختبارات، ويدعم سيناريوهات مثل قواعد البيانات وRedis وRabbitMQ وغيرها. citeturn0search3turn0search12
وده بيديك integration أقرب للواقع بدل ما تعمل Mock لكل حاجة.
🏗️ مثال مهم جدًا
تخيل إن عندك:
account.MarkAsDeleted();
والـ Unit Test بيختبر إن:
MarkAsDeleted()
غير حالة الـ object.
الـ test ينجح.
لكن implementation الحقيقية نسيت:
await db.SaveChangesAsync();
الـ Unit Tests كلها ممكن تفضل Green.
لكن Integration Test:
DELETE /account
↓
Application
↓
Database
↓
Read account
هتكتشف:
Account still exists ❌
وهنا تظهر قيمة Integration Testing.
🔺 Test Pyramid وهل ما زالت كافية؟
المفهوم التقليدي:
E2E
/ \
/ \
Integration
/ \
Unit Tests
أي:
Many Unit Tests
Fewer Integration Tests
Very Few E2E Tests
وده لسه مفهوم مفيد.
لكن مش لازم تتعامل مع الهرم كأنه قانون فيزيائي.
مع تطور:
Docker
Testcontainers
Cloud CI
Fast databases
Better tooling
بقى تشغيل integration tests أسهل من زمان.
فممكن بعض المشاريع تكون:
Unit-heavy
ومشاريع تانية:
Integration-heavy
المهم:
اختار test mix بناءً على المخاطر وتكلفة الاختبار، مش بناءً على شكل diagram محفوظ.
🌐 End-to-End Testing
ده أقرب اختبار للمستخدم الحقيقي.
مثلاً:
Open Website
↓
Login
↓
Open Course
↓
Buy Course
↓
Open Lesson
↓
Video Appears
هنا بتختبر رحلة كاملة:
Frontend
Backend
Database
Authentication
Authorization
Routing
Cookies
Browser
API
أدوات مشهورة:
Playwright
Cypress
Selenium
Playwright يدعم Chromium وFirefox وWebKit، ويقدر يشتغل مع xUnit/NUnit/MSTest في .NET، بالإضافة إلى تشغيل الاختبارات على CI. citeturn0search5turn0search1
🎭 Playwright
مثلاً:
Browser
↓
Login
↓
Dashboard
↓
Create Order
↓
Assert Success
ميزة مهمة:
Playwright لا يقتصر على browser UI؛ يوفر أيضًا API testing عبر APIRequestContext، وده مفيد لاختبار الـ API مباشرة أو تجهيز state قبل UI tests. citeturn0search6
لكن E2E:
Slower
More expensive
More maintenance
More moving parts
عشان كده لا تعمل كل حاجة E2E.
🏛️ Architecture Testing
ده نوع مهم جدًا لو عندك architecture واضحة.
تخيل:
Domain
Application
Infrastructure
API
وعندك Rule:
Domain
must NOT depend on
Infrastructure
بعد سنة Developer يعمل:
Domain
↓
Infrastructure
المشروع:
Build ✅
Unit Tests ✅
لكن الـ Architecture:
Broken ❌
هنا Architecture Tests تحميك.
مثلاً:
Domain must not depend on Infrastructure
Controllers must not access DbContext directly
Repositories must live in Infrastructure
أدوات .NET:
NetArchTest
ArchUnitNET
NetArchTest يوفر API لفرض قواعد architecture/design داخل test suite ويمكن دمجه مع build pipeline، بينما ArchUnitNET يسمح بتعريف architecture rules واستخدامها ضمن اختبارات .NET. citeturn0search10turn0search8
🤝 Contract Testing
مشكلة مشهورة في Microservices:
Service A
↓
Service B
Service A متوقعة:
{
"id": 10,
"name": "Amr"
}
Service B غيرت:
{
"userId": 10,
"fullName": "Amr"
}
كل Service لوحدها:
Build ✅
Tests ✅
لكن integration:
💥
هنا:
Contract Testing
الفكرة:
الطرفين يتفقوا على contract للـ messages بينهم.
Pact يركز على اختبار الـ HTTP/message integration من خلال contract مشترك بين Consumer وProvider، ويهدف لتقليل مشاكل integrations الهشة والمكلفة. citeturn0search2
في .NET يوجد PactNet، والوثائق الحالية تشير إلى دعمه حتى Pact Specification v4.0 مع استثناء Pact plugins. citeturn0search4
مثلاً:
Frontend / Consumer
↓
Expected Contract
↓
Provider verifies
📈 Load Testing
بعض الشركات تسأل:
"السيرفر يشيل كام مستخدم؟"
الإجابة:
"Let's measure."
مش نخمن.
مثلاً:
100 users
500 users
1,000 users
5,000 users
وتراقب:
Response Time
Throughput
Error Rate
CPU
Memory
DB Connections
Queue Depth
أدوات مشهورة:
k6
JMeter
NBomber
Locust
💥 Stress Testing
الفرق:
Load Testing
السؤال:
"إيه أداء النظام تحت load متوقع أو محدد؟"
مثلاً:
5,000 concurrent users
Stress Testing
السؤال:
"إمتى النظام يبدأ ينهار؟"
مثلاً:
1,000 → OK
5,000 → OK
10,000 → OK
20,000 → Degraded
50,000 → Failure
هنا بتعرف:
Breaking Point
والأهم:
ماذا يحدث بعد الوصول للحد؟
هل النظام:
Gracefully degrades
ولا:
Everything crashes
🔄 Regression Testing
ده مش نوع test مستقل بالضرورة.
هو وصف لهدف الاختبار:
نتأكد إن التغيير الجديد لم يكسر behavior قديم.
مثلاً:
Create Member
كان شغال.
أضفت:
Member Categories
وبعدها:
Create Member ❌
ده Regression Bug.
والاختبار الذي اكتشفه ممكن يكون:
Unit
Integration
E2E
يعني:
Regression هو الهدف/الاستخدام، وليس framework منفصل.
🔴 TDD
لو قلت:
"إيه رأيك نكتب الـ Test قبل الكود؟"
ده ممكن يكون:
Test Driven Development
الفكرة:
Red
↓
Green
↓
Refactor
Red
اكتب test يصف behavior مطلوب.
Expected: 800
Actual: not implemented
Test يفشل.
Green
اكتب أقل كمية implementation تخلي الاختبار ينجح.
Refactor
نظف التصميم بدون تغيير behavior.
ثم تعيد:
Red
Green
Refactor
TDD ممكن يساعدك في:
- تحديد behavior
- تصميم APIs
- تقليل overengineering
- الحصول على feedback مبكر
لكن مش لازم يكون مناسب لكل سطر وكل مشروع بنفس الدرجة.
🗣️ BDD
Behavior Driven Development
جاء جزئيًا لحل مشكلة اختلاف اللغة بين:
Developer
QA
Product
Business
بدل:
if (...)
{
...
}
نفكر في behavior:
Given
When
Then
مثلاً:
Given the user has a Gold membership
When the user buys a product worth 1000
Then the final price should be 800
في .NET يوجد Reqnroll كأداة BDD حديثة مبنية حول Gherkin-style scenarios.
لكن مهم:
BDD مش مجرد كتابة
Given / When / Then.
القيمة الحقيقية هي إن السيناريو يعبر عن behavior مفهوم للفريق.
⚙️ CI/CD
الـ Tests قيمتها تزيد جدًا لما تدخل في pipeline.
مثلاً:
Developer
↓
Push
↓
CI
├── Build
├── Unit Tests
├── Integration Tests
├── Architecture Tests
├── Contract Tests
└── E2E / selected checks
↓
Passed?
/ \
Yes No
↓ ↓
Deploy Stop
الفكرة:
منع الكود غير الموثوق من الوصول للبيئات التالية.
لكن مش لازم كل test suite تشتغل على كل Pull Request.
ممكن تعمل layers:
PR:
Fast Unit + selected integration
Main:
Full integration + contracts
Nightly:
Heavy E2E + Load/Stress
حسب المشروع.
🧭 اختيار نوع الـ Test
بدل ما تسأل:
"أستخدم Unit ولا Integration؟"
اسأل:
"إيه نوع الـ failure اللي عايز أكتشفه؟"
| المشكلة | Test مناسب غالبًا |
|---|---|
| Business rule | Unit |
| Pure function | Unit |
| Repository + DB | Integration |
| API + DB | Integration |
| Browser flow | E2E |
| Service contract | Contract |
| Architecture dependency | Architecture Test |
| Regression | أي مستوى مناسب للـ behavior |
| High traffic behavior | Load Test |
| Breaking point | Stress Test |
| Weak assertions | Mutation Testing |
| Race/concurrency | Specialized integration/load tests |
👤 مثال عملي: Create Account
المستخدم:
Name
Email
Password
↓
Create Account
والسيستم يعمل:
Validation
↓
Duplicate Email Check
↓
Hash Password
↓
Save User
↓
Commit Transaction
↓
Send Verification Email
↓
Audit Log
↓
Response
هل نعمل Test واحد لكل ده؟
❌ لا
نقسم المخاطر.
Unit Tests
اختبر:
Email validation
Password rules
Business rules
Duplicate decision logic
Token generation logic
مثلاً:
Valid email → accepted
Invalid email → rejected
Weak password → rejected
Integration Tests
اختبر:
POST /users
↓
Database
وتتأكد:
User persisted
Password hashed
Constraints enforced
Transaction works
Integration مع Messaging
لو عندك:
User Created
↓
Outbox
↓
Worker
↓
Email Provider
اختبر إن:
User transaction
+
Outbox message
بيتصرفوا بالشكل المطلوب.
E2E
اختبر الرحلة المهمة:
Open Register
↓
Fill form
↓
Submit
↓
See success
↓
Open verification flow
مش لازم تختبر كل validation rule من خلال browser.
⚠️ أخطاء شائعة
❌ 1. "نعمل Tests بعد ما نخلص"
الاختبارات مش decoration.
❌ 2. "عندي 95% Coverage إذن أنا ممتاز"
Coverage لا تقيس صحة assertions ولا تغطي كل المخاطر.
❌ 3. Mock كل شيء
لو عملت:
Mock DB
Mock Queue
Mock Payment
Mock Repository
Mock HTTP
Mock Everything
ممكن في النهاية تختبر عالمًا خياليًا لا يشبه production.
❌ 4. Integration test لكل شيء
Integration Tests قوية، لكنها أغلى وأبطأ.
❌ 5. E2E لكل شيء
هتعمل:
Slow CI
Brittle Tests
High Maintenance
❌ 6. تجاهل Flaky Tests
Flaky test خطير لأنه يدمر الثقة في الـ test suite.
❌ 7. Assertions ضعيفة
مثلاً:
Assert.NotNull(result);
لو المطلوب:
Price = 800
فأنت محتاج assertion يثبت الـ behavior المهم.
❌ 8. Test implementation بدل behavior
لو غيرت internal implementation والـ behavior ما اتغيرش، مش المفروض test suite كلها تقع.
❌ 9. عدم اختبار Architecture
ممكن الكود يفضل شغال لكن architecture تنهار تدريجيًا.
❌ 10. تجاهل Contracts في Microservices
كل Service ممكن تكون صحيحة منفردة، لكن communication بينهم مكسور.
🎯 إجابة انترفيو جاهزة
لو interviewer سألك:
"التيم خلص Feature، هل نكتب Tests بعدين؟"
ممكن تجاوب:
"أنا مش بتعامل مع testing كخطوة أخيرة. من البداية أحدد الـ behaviors والمخاطر المهمة وأختار مستوى الاختبار المناسب لكل واحدة."
وبعدين:
Business logic
→ Unit Tests
Database / infrastructure integration
→ Integration Tests
Critical user journeys
→ E2E
Microservice contracts
→ Contract Tests
Architecture rules
→ Architecture Tests
Weak test suite detection
→ Mutation Testing
Expected traffic
→ Load Testing
Breaking point
→ Stress Testing
وبالنسبة للـ Coverage:
"Coverage metric مفيدة، لكنها مش quality score. ممكن يكون عندي 95% coverage واختبارات ضعيفة جدًا. المهم إن الاختبارات تتحقق من behavior والـ business rules."
ولو اتسألت:
"هل تعمل Mock للـ Database؟"
تقول:
"في Unit Tests ممكن أستخدم mocks/fakes لعزل الـ business logic، لكن لو هدفي اختبار integration مع الـ database فأنا أفضل Database حقيقية في بيئة test، مثل Testcontainers، بدل Mock لطبقة الـ database نفسها."
ولو اتسألت:
"إزاي تعرف إن tests قوية؟"
تقول:
"مش بالـ Coverage فقط. ممكن أستخدم Mutation Testing عشان أشوف هل الاختبارات تكتشف تغييرات مقصودة في الـ business logic."
ولو عندك Microservices:
"هستخدم Contract Testing عشان أتأكد إن consumer/provider متفقين على شكل الـ messages والـ behavior المتوقع."
🧠 الفكرة الأهم
Testing مش هدفه:
Make CI Green
ولا:
Get 90% Coverage
الهدف:
Build Confidence
إنك لما تغير الكود:
أنا عارف إيه اللي ممكن يتكسر
ولما الـ test يفشل:
أنا واثق إن الفشل له معنى
ولما الـ deployment يحصل:
عندي evidence إن النظام behavior بتاعه لسه صحيح
وده الفرق بين:
Tests موجودة
و:
Testing Strategy
🏁 الخلاصة
المشروع المحترم مش عنده:
Lots of Tests
فقط.
عنده:
Right Tests
+
Right Scope
+
Reliable Tests
+
Useful Assertions
+
Fast Feedback
+
Realistic Integration
والـ Testing Strategy ممكن تبقى:
┌───────────────┐
│ E2E Tests │
│ Critical Flows│
└───────┬───────┘
│
┌──────────┴──────────┐
│ Integration / │
│ Contract / Arch │
└──────────┬──────────┘
│
┌──────────┴──────────┐
│ Unit Tests │
│ Business Decisions │
└─────────────────────┘
+ Mutation Testing
+ Load / Stress Testing
+ CI/CD
+ Observability
والقاعدة الذهبية:
اختبر behavior المهم، مش مجرد السطور.
والأهم:
Untestable Code غالبًا علامة إن التصميم محتاج مراجعة.
لكن خلي بالك: مش كل كود صعب الاختبار سيئ تلقائيًا، ومش كل كود سهل الاختبار ممتاز. الاختبار مجرد feedback على التصميم، وليس الحكم الوحيد عليه.
📚 مصادر رسمية
🧪 xUnit
- xUnit.net — Microsoft Testing Platform / xUnit v3
https://xunit.net/docs/getting-started/v3/microsoft-testing-platform citeturn0search16
🐳 Testcontainers
- Testcontainers for .NET
https://dotnet.testcontainers.org/ citeturn0search3
🎭 Playwright
-
Playwright .NET — Writing Tests
https://playwright.dev/dotnet/docs/writing-tests citeturn0search0 -
Playwright .NET — Installation & E2E
https://playwright.dev/dotnet/docs/intro citeturn0search5 -
Playwright .NET — API Testing
https://playwright.dev/dotnet/docs/api-testing citeturn0search6
🤝 Pact
-
Pact — Contract Testing
https://docs.pact.io/ citeturn0search2 -
PactNet / .NET
https://docs.pact.io/implementation_guides/net citeturn0search4
🏛️ Architecture Testing
-
NetArchTest
GitHub citeturn0search10 -
ArchUnitNET
https://archunitnet.readthedocs.io/en/stable/guide/ citeturn0search8
<div align="center">
🧪 Testing isn't about proving your code works.
It's about discovering how it can fail.
Test the behavior.
Test the boundaries.
Test the integrations.
Test the architecture.
Test under load.
منشورات مقترحة
مشاريع ذات صلة
منصة تعليمية متكاملة تتيح إجراء الاختبارات المحاكاة المؤقتة، واستعادة جلسة الامتحان من التخزين المحلي، وتقييم قدرات الطلاب بتقارير تحليلية.