14. تمرين تطبيقي – الإصدار 6

لقد قمنا للتو بتنفيذ الهيكل الطبقي التالي:

كان نظام إدارة قواعد البيانات (DBMS) المستخدم في الأمثلة هو MySQL. في قسم "الرابط"، لاحظنا أنه لا يوجد في الفئة التي تنفذ طبقة [dao] ما يشير إلى استخدام نظام إدارة قواعد بيانات معين. سنقوم الآن بالتحقق من ذلك باستخدام نظام إدارة قواعد بيانات آخر، وهو PostgreSQL. تصبح البنية الطبقية كما يلي:

14.1. تثبيت نظام إدارة قواعد البيانات PostgreSQL
تتوفر توزيعات نظام إدارة قواعد البيانات PostgreSQL على [https://www.postgresql.org/download/] (مايو 2019). نعرض هنا تثبيت إصدار Windows 64 بت:


- في [1-4]، قم بتنزيل أداة تثبيت نظام إدارة قواعد البيانات؛
قم بتشغيل برنامج التثبيت الذي تم تنزيله:

- في [6]، حدد دليل التثبيت؛

- في [8]، خيار [Stack Builder] غير مطلوب لما نقوم به هنا؛
- في [10]، اترك القيمة الافتراضية؛

- في [12-13]، أدخلنا كلمة المرور [root] هنا. وستكون هذه هي كلمة مرور مسؤول نظام إدارة قواعد البيانات (DBMS)، الذي يُسمى [postgres]. ويُطلق PostgreSQL عليه أيضًا اسم «المستخدم المتميز»؛
- في [15]، اترك القيمة الافتراضية: هذا هو منفذ الاستماع لنظام إدارة قواعد البيانات (DBMS)؛

- في [17]، اترك القيمة الافتراضية؛
- في [19]، ملخص تكوين التثبيت؛


في نظام Windows، يتم تثبيت نظام إدارة قواعد البيانات PostgreSQL كخدمة Windows تبدأ تلقائيًا. في معظم الأحيان، هذا الأمر غير مرغوب فيه. سنقوم بتعديل هذا التكوين. اكتب [services] في شريط البحث في Windows [24-26]:

- في [29]، يمكنك أن ترى أن خدمة نظام إدارة قواعد البيانات PostgreSQL مضبوطة على التشغيل التلقائي. قم بتغيير هذا عن طريق الوصول إلى خصائص الخدمة [30]:

- في [31-32]، اضبط نوع بدء التشغيل على يدوي؛
- في [33]، أوقف الخدمة؛
عندما تريد بدء تشغيل نظام إدارة قواعد البيانات يدويًا، ارجع إلى تطبيق [services]، وانقر بزر الماوس الأيمن على خدمة [postgresql] (34)، وابدأ تشغيلها (35).
14.2. تمكين ملحق PDO لنظام إدارة قواعد البيانات PostgreSQL
سنقوم بتعديل ملف [php.ini] الذي يقوم بتكوين PHP (انظر القسم المرتبط):

- في [2]، تحقق من تمكين ملحق PostgreSQL PDO. بمجرد الانتهاء، احفظ التغيير وأعد تشغيل Laragon لضمان تفعيل التغيير. ثم تحقق من تكوين PHP مباشرة من Laragon [3-5].
14.3. إدارة PostgreSQL باستخدام أداة [pgAdmin]
ابدأ خدمة Windows لنظام إدارة قواعد البيانات PostgreSQL (انظر القسم المرتبط). ثم، تمامًا كما قمت بتشغيل أداة [services]، قم بتشغيل أداة [pgadmin]، التي تتيح لك إدارة نظام إدارة قواعد البيانات PostgreSQL [1-3]:

قد يُطلب منك إدخال كلمة مرور المستخدم المتميز في مرحلة ما. اسم المستخدم المتميز هو [postgres]. تقوم بتعيين كلمة المرور هذه أثناء تثبيت نظام إدارة قواعد البيانات. في هذا المستند، قمنا بتعيين كلمة المرور [root] للمستخدم المتميز أثناء التثبيت.
- في [4]، [pgAdmin] هو تطبيق ويب؛
- في [5]، قائمة خوادم PostgreSQL التي اكتشفها [pgAdmin]، وهنا 1؛
- في [6]، خادم PostgreSQL الذي قمنا بتشغيله؛
- في [7]، قواعد بيانات نظام إدارة قواعد البيانات (DBMS)، وهنا 1؛
- في [8]، تتم إدارة قاعدة بيانات [postgresql] بواسطة المستخدم المتميز [postgres]؛
أولاً، لنقم بإنشاء مستخدم باسم [admimpots] وكلمة مرور [mdpimpots]:


- في [17]، أدخلنا [mdpimpots]؛

- في [21]، رمز SQL الذي سترسله أداة [pgAdmin] إلى نظام إدارة قواعد البيانات PostgreSQL. هذه طريقة لتعلم لغة SQL الخاصة بـ PostgreSQL؛
- في [22]، بعد التأكيد باستخدام معالج [Save]، تم إنشاء المستخدم [admimpots]؛
الآن نقوم بإنشاء قاعدة البيانات [dbimpots-2019]:

انقر بزر الماوس الأيمن على [23]، ثم [24-25] لإنشاء قاعدة بيانات جديدة. في علامة التبويب [26]، حدد اسم قاعدة البيانات [27] ومالكها [admimpots] [28].

- في [30]، رمز SQL لإنشاء قاعدة البيانات؛
- في [31]، بعد التأكيد باستخدام معالج [حفظ]، يتم إنشاء قاعدة البيانات [dbimpots-2019]؛
الآن، سننشئ الجدول [tbtranches] مع الأعمدة [id, limites, coeffr, coeffn]. من السمات المميزة لـ PostgreSQL أن أسماء الأعمدة حساسة لحالة الأحرف (كبيرة/صغيرة)، وهو ما لا يحدث عادةً مع أنظمة إدارة قواعد البيانات الأخرى. وبالتالي، مع MySQL، ستعمل عبارة SQL [select limites, coeffR, coeffN from tbtranches] حتى لو كانت الأعمدة الفعلية في الجدول [tbtranches] هي [LIMITES, COEFFR, COEFFN]. مع PostgreSQL، لن تعمل عبارة SQL هذه. قد يكتب المرء عندئذٍ [select LIMITES, COEFFR, COEFFN from tbtranches]، لكنه لن يعمل أيضًا، لأن PostgreSQL سينفذ الاستعلام [select limites, coeffr, coeffn from tbtranches]: فهو يحول أسماء الأعمدة إلى أحرف صغيرة بشكل افتراضي. لمنع ذلك، يجب كتابة: [select "LIMITES", "COEFFR", "COEFFN" from tbtranches]، أي يجب وضع أسماء الأعمدة بين علامتي اقتباس. لهذه الأسباب، سنعطي الأعمدة أسماءً بأحرف صغيرة. يمكن أن تكون أسماء كائنات قاعدة البيانات مصدرًا لعدم التوافق بين أنظمة إدارة قواعد البيانات (DBMS)، حيث إن بعض الأسماء هي كلمات محجوزة في بعض أنظمة إدارة قواعد البيانات (DBMS) ولكنها ليست كذلك في أنظمة أخرى.
نقوم بإنشاء الجدول [tbtranches]:

- استخدم الزر [40] لإنشاء الأعمدة؛


- بعد إكمال معالج الإنشاء بالنقر على [حفظ]، يتم إنشاء الجدول [tbtranches] [52-53]؛
نحتاج إلى إخبار نظام إدارة قواعد البيانات (DBMS) بإنشاء المفتاح الأساسي [id] بنفسه عند إدراج صف في الجدول:

- في [56] نصل إلى خصائص المفتاح الأساسي [id]؛
- في [59]، نحدد أن العمود من النوع [Identity]. سيؤدي ذلك إلى قيام نظام إدارة قواعد البيانات (DBMS) بإنشاء قيم المفتاح الأساسي؛

- في [62]، كود SQL الذي تم إنشاؤه لهذه العملية؛
أصبح الجدول [tbtranches] جاهزًا الآن.
نكرر نفس الخطوات لإنشاء الجدول [tbconstantes]. وإليك النتيجة المتوقعة:



أصبحت قاعدة البيانات [dbimpots-2019] جاهزة الآن. سنقوم بتعبئتها بالبيانات.
كما فعلنا مع MySQL، يمكن تصدير قاعدة البيانات [dbimpots-2019] إلى ملف SQL. يمكننا بعد ذلك استيراد ملف SQL هذا لإعادة إنشاء قاعدة البيانات في حالة فقدانها أو تلفها. هنا، سنقوم بتصدير بنية قاعدة البيانات فقط وليس بياناتها:


الملف الذي تم إنشاؤه هو كما يلي:
--
-- PostgreSQL database dump
--
-- Dumped from database version 11.2
-- Dumped by pg_dump version 11.2
-- Started on 2019-07-04 08:20:31
SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET client_min_messages = warning;
SET row_security = off;
SET default_tablespace = '';
SET default_with_oids = false;
--
-- TOC entry 198 (class 1259 OID 16408)
-- Name: tbconstantes; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE public.tbconstantes (
plafond_qf_demi_part double precision NOT NULL,
id integer NOT NULL,
plafond_revenus_celibataire_pour_reduction double precision NOT NULL,
plafond_revenus_couple_pour_reduction double precision NOT NULL,
valeur_reduc_demi_part double precision NOT NULL,
plafond_decote_celibataire double precision NOT NULL,
plafond_decote_couple double precision NOT NULL,
plafond_impot_celibataire_pour_decote double precision NOT NULL,
plafond_impot_couple_pour_decote double precision NOT NULL,
abattement_dix_pourcent_max double precision NOT NULL,
abattement_dix_pourcent_min double precision NOT NULL
);
ALTER TABLE public.tbconstantes OWNER TO postgres;
--
-- TOC entry 199 (class 1259 OID 16411)
-- Name: tbconstantes_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
--
ALTER TABLE public.tbconstantes ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
SEQUENCE NAME public.tbconstantes_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
--
-- TOC entry 196 (class 1259 OID 16399)
-- Name: tbtranches; Type: TABLE; Schema: public; Owner: admimpots
--
CREATE TABLE public.tbtranches (
limites double precision NOT NULL,
id integer NOT NULL,
coeffr double precision NOT NULL,
coeffn double precision NOT NULL
);
ALTER TABLE public.tbtranches OWNER TO admimpots;
--
-- TOC entry 197 (class 1259 OID 16404)
-- Name: tbimpots_id_seq; Type: SEQUENCE; Schema: public; Owner: admimpots
--
ALTER TABLE public.tbtranches ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
SEQUENCE NAME public.tbimpots_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
--
-- TOC entry 2694 (class 2606 OID 16429)
-- Name: tbconstantes tbconstantes_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.tbconstantes
ADD CONSTRAINT tbconstantes_pkey PRIMARY KEY (id);
--
-- TOC entry 2692 (class 2606 OID 16403)
-- Name: tbtranches tbimpots_pkey; Type: CONSTRAINT; Schema: public; Owner: admimpots
--
ALTER TABLE ONLY public.tbtranches
ADD CONSTRAINT tbimpots_pkey PRIMARY KEY (id);
--
-- TOC entry 2821 (class 0 OID 0)
-- Dependencies: 198
-- Name: TABLE tbconstantes; Type: ACL; Schema: public; Owner: postgres
--
GRANT ALL ON TABLE public.tbconstantes TO admimpots;
-- Completed on 2019-07-04 08:20:32
--
-- PostgreSQL database dump complete
--
14.4. ملء الجدول [tbtranches]
لقد قمنا بذلك بالفعل مع نظام إدارة قواعد البيانات MySQL في القسم المرتبط. كل ما علينا فعله هو تعديل ملف [database.json] الذي يصف قاعدة البيانات:

يصبح ملف [database.json] كما يلي:
{
"dsn": "pgsql:host=localhost;dbname=dbimpots-2019",
"id": "admimpots",
"pwd": "mdpimpots",
"tableTranches": "public.tbtranches",
"colLimites": "limites",
"colCoeffR": "coeffr",
"colCoeffN": "coeffn",
"tableConstantes": "public.tbconstantes",
"colPlafondQfDemiPart": "plafond_qf_demi_part",
"colPlafondRevenusCelibatairePourReduction": "plafond_revenus_celibataire_pour_reduction",
"colPlafondRevenusCouplePourReduction": "plafond_revenus_couple_pour_reduction",
"colValeurReducDemiPart": "valeur_reduc_demi_part",
"colPlafondDecoteCelibataire": "plafond_decote_celibataire",
"colPlafondDecoteCouple": "plafond_decote_couple",
"colPlafondImpotCelibatairePourDecote": "plafond_impot_celibataire_pour_decote",
"colPlafondImpotCouplePourDecote": "plafond_impot_couple_pour_decote",
"colAbattementDixPourcentMax": "abattement_dix_pourcent_max",
"colAbattementDixPourcentMin": "abattement_dix_pourcent_min"
}
- السطر 2: تغيرت DSN؛ تشير [pgsql] إلى أننا نتعامل مع نظام إدارة قواعد البيانات Postgres؛
- السطران 5 و9: تمت إضافة اسم المخطط الذي تنتمي إليه الجداول [public] كبادئة لأسمائها. لم يكن هذا ضروريًا تمامًا لأن [public] هو المخطط الافتراضي عندما لا يتم تحديد مخطط في اسم الجدول؛
- الأسطر 6–8، 10–19: تغيرت أسماء الأعمدة؛
النص البرمجي [MainTransferAdminDataFromJsonFile2PostgresDatabase.php] لملء قاعدة البيانات [dbimpots-2019] هو كما يلي:
<?php
// strict adherence to declared types of function parameters
declare (strict_types=1);
// namespace
namespace Application;
// error handling by PHP
// ini_set("display_errors", "0");
// interface and class inclusion
require_once __DIR__ . "/../../version-05/Entities/BaseEntity.php";
require_once __DIR__ . "/../../version-05/Entities/TaxAdminData.php";
require_once __DIR__ . "/../../version-05/Entities/TaxPayerData.php";
require_once __DIR__ . "/../../version-05/Entities/Database.php";
require_once __DIR__ . "/../../version-05/Entities/ExceptionImpots.php";
require_once __DIR__ . "/../../version-05/Utilities/Utilitaires.php";
require_once __DIR__ . "/../../version-05/Dao/InterfaceDao.php";
require_once __DIR__ . "/../../version-05/Dao/TraitDao.php";
require_once __DIR__ . "/../../version-05/Dao/InterfaceDao4TransferAdminData2Database.php";
require_once __DIR__ . "/../../version-05/Dao/DaoTransferAdminDataFromJsonFile2Database.php";
//
// definition of constants
const DATABASE_CONFIG_FILENAME = "../Data/database.json";
const TAXADMINDATA_FILENAME = "../Data/taxadmindata.json";
//
try {
// creation of the [dao] layer
$dao = new DaoTransferAdminDataFromJsonFile2Database(DATABASE_CONFIG_FILENAME, TAXADMINDATA_FILENAME);
// data transfer to the database
$dao->transferAdminData2Database();
} catch (ExceptionImpots $ex) {
// error is displayed
print "L'erreur suivante s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// end
print "Terminé\n";
exit;
تعليقات
تتغير فقط الأسطر 12–21، التي تقوم بتحميل الملفات اللازمة لتشغيل التطبيق. تتغير هذه الأسطر لأن قيمة [__DIR__] تتغير: فهي تشير الآن إلى المجلد [version-07/Main].
عند تشغيل هذا البرنامج النصي، يتم الحصول على النتيجة التالية في الجدول [tbtranches]:

- انقر بزر الماوس الأيمن على [1]، ثم على [2-3]؛
- في [4]، نرى بيانات الشرائح الضريبية؛
نكرر نفس العملية بالنسبة لجدول الثوابت [tbconstantes]:



لاحظ أنه لتشغيل البرنامج النصي، لا يلزم أن يكون تطبيق Laragon نشطًا: فلا حاجة إلى خادم Apache ولا إلى نظام إدارة قواعد البيانات MySQL. نحتاج فقط إلى نظام إدارة قواعد البيانات PostgreSQL، الذي قمنا بتشغيل الخدمة الخاصة به في Windows.
14.5. حساب الضرائب

تمت كتابة طبقتي [dao] (3) و[business] (2) بالفعل. لقد كتبنا بالفعل البرنامج النصي الرئيسي لنظام إدارة قواعد البيانات MySQL في القسم المرتبط. نحتاج فقط إلى أخذ البرنامج النصي [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase.php] وتكييفه مع نظام إدارة قواعد البيانات PostgreSQL. يُسمى الآن [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase.php]:

النص البرمجي [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase.php] هو كما يلي:
<?php
// strict adherence to declared types of function parameters
declare (strict_types=1);
// namespace
namespace Application;
// error handling by PHP
//ini_set("display_errors", "0");
// interface and class inclusion
require_once __DIR__ . "/../../version-05/Entities/BaseEntity.php";
require_once __DIR__ . "/../../version-05/Entities/TaxAdminData.php";
require_once __DIR__ . "/../../version-05/Entities/TaxPayerData.php";
require_once __DIR__ . "/../../version-05/Entities/Database.php";
require_once __DIR__ . "/../../version-05/Entities/ExceptionImpots.php";
require_once __DIR__ . "/../../version-05/Utilities/Utilitaires.php";
require_once __DIR__ . "/../../version-05/Dao/InterfaceDao.php";
require_once __DIR__ . "/../../version-05/Dao/TraitDao.php";
require_once __DIR__ . "/../../version-05/Dao/DaoImpotsWithTaxAdminDataInDatabase.php";
require_once __DIR__ . "/../../version-05/Métier/InterfaceMetier.php";
require_once __DIR__ . "/../../version-05/Métier/Metier.php";
//
// definition of constants
const DATABASE_CONFIG_FILENAME = "../Data/database.json";
const TAXADMINDATA_FILENAME = "../Data/taxadmindata.json";
const RESULTS_FILENAME = "../Data/resultats.json";
const ERRORS_FILENAME = "../Data/errors.json";
const TAXPAYERSDATA_FILENAME = "../Data/taxpayersdata.json";
try {
// creation of the [dao] layer
$dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
// creation of the [business] layer
$métier = new Metier($dao);
// tax calculation in batch mode
$métier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
// error is displayed
print "Une erreur s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// end
print "Terminé\n";
exit;
تعليقات
تتغير فقط الأسطر 12–22، التي تقوم بتحميل الملفات اللازمة لتشغيل التطبيق. تتغير هذه الأسطر لأن قيمة [__DIR__] تتغير: فهي تشير الآن إلى المجلد [version-07/Main].
نتائج التنفيذ
نفس النتائج التي تم الحصول عليها في الإصدارات السابقة.
14.6. اختبارات [Codeception]
كما هو الحال مع الإصدارات السابقة، نقوم بالتحقق من صحة هذا الإصدار باستخدام اختبارات [Codeception]:

14.6.1. اختبار طبقة [DAO]
اختبار [DaoTest.php] هو كما يلي:
<?php
// strict adherence to declared types of function parameters
declare (strict_types=1);
// namespace
namespace Application;
// root directories
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-06");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// interface and class inclusion
require_once ROOT . "/../version-05/Entities/BaseEntity.php";
require_once ROOT . "/../version-05/Entities/TaxAdminData.php";
require_once ROOT . "/../version-05/Entities/TaxPayerData.php";
require_once ROOT . "/../version-05/Entities/Database.php";
require_once ROOT . "/../version-05/Entities/ExceptionImpots.php";
require_once ROOT . "/../version-05/Utilities/Utilitaires.php";
require_once ROOT . "/../version-05/Dao/InterfaceDao.php";
require_once ROOT . "/../version-05/Dao/TraitDao.php";
require_once ROOT . "/../version-05/Dao/DaoImpotsWithTaxAdminDataInDatabase.php";
// third-party libraries
require_once VENDOR . "/autoload.php";
// definition of constants
const DATABASE_CONFIG_FILENAME = ROOT ."../Data/database.json";
class DaoTest extends \Codeception\Test\Unit {
// TaxAdminData
private $taxAdminData;
public function __construct() {
parent::__construct();
// creation of the [dao] layer
$dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
$this->taxAdminData = $dao->getTaxAdminData();
}
// tests
public function testTaxAdminData() {
…
}
}
تعليقات
- الأسطر 9–28: تعريف بيئة الاختبار. نستخدم نفس البيئة، بدون طبقة [business]، التي يستخدمها البرنامج النصي الرئيسي [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase] الموصوف في القسم المرتبط؛
- الأسطر 34–39: إنشاء طبقة [dao]؛
- السطر 38: تحتوي السمة [$this→taxAdminData] على البيانات المراد اختبارها؛
- الأسطر 42–44: طريقة [testTaxAdminData] هي الطريقة الموصوفة في القسم المرتبط؛
نتائج الاختبار هي كما يلي:

14.6.2. اختبار طبقة [business]
اختبار [MetierTest.php] هو كما يلي:
<?php
// strict adherence to declared types of function parameters
declare (strict_types=1);
// namespace
namespace Application;
// root directories
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-06");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// interface and class inclusion
require_once ROOT . "/../version-05/Entities/BaseEntity.php";
require_once ROOT . "/../version-05/Entities/TaxAdminData.php";
require_once ROOT . "/../version-05/Entities/TaxPayerData.php";
require_once ROOT . "/../version-05/Entities/Database.php";
require_once ROOT . "/../version-05/Entities/ExceptionImpots.php";
require_once ROOT . "/../version-05/Utilities/Utilitaires.php";
require_once ROOT . "/../version-05/Dao/InterfaceDao.php";
require_once ROOT . "/../version-05/Dao/TraitDao.php";
require_once ROOT . "/../version-05/Dao/DaoImpotsWithTaxAdminDataInDatabase.php";
require_once ROOT . "/../version-05/Métier/InterfaceMetier.php";
require_once ROOT . "/../version-05/Métier/Metier.php";
// third-party libraries
require_once VENDOR . "/autoload.php";
// definition of constants
const DATABASE_CONFIG_FILENAME = ROOT . "../Data/database.json";
class MetierTest extends \Codeception\Test\Unit {
// business layer
private $métier;
public function __construct() {
parent::__construct();
// creation of the [dao] layer
$dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
// creation of the [business] layer
$this->métier = new Metier($dao);
}
// tests
public function test1() {
…
}
--------------------------------------------------------------------
public function test11() {
…
}
}
تعليقات
- الأسطر 9–28: تعريف بيئة الاختبار. نستخدم نفس البيئة المستخدمة في البرنامج النصي الرئيسي [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase] الموصوف في القسم المرتبط؛
- الأسطر 34–40: إنشاء طبقتي [dao] و[business]؛
- السطر 39: تشير السمة [$this→business] إلى طبقة [business]
- الأسطر 43–49: الطرق [test1، test2…، test11] هي تلك الموصوفة في القسم المرتبط؛
نتائج الاختبار هي كما يلي:
