آزمون واحد کلاس نگاشت تهیه شده
در مورد آشنایی با آزمونهای واحد لطفا به برچسب مربوطه در سمت راست سایت مراجعه بفرمائید. همچنین در مورد اینکه چرا به این نوع API کلمه Fluent اطلاق میشود، میتوان به تعریف آن جهت مطالعه بیشتر مراجعه نمود.
در این قسمت قصد داریم برای بررسی وضعیت کلاس نگاشت تهیه شده یک آزمون واحد تهیه کنیم. برای این منظور ارجاعی را به اسمبلی nunit.framework.dll به پروژه UnitTests که در ابتدای کار به solution جاری در VS.Net افزوده بودیم، اضافه نمائید (همچنین ارجاعهایی به اسمبلیهای پروژه NHSample1 ، FluentNHibernate ، System.Data.SQLite ، NHibernate.ByteCode.Castle و Nhibernate نیز نیاز هستند). تمام اسمبلیهای این فریم ورکها از پروژه FluentNHibernate قابل استخراج هستند.
سپس سه کلاس زیر را به پروژه آزمون واحد اضافه خواهیم کرد.
کلاس TestModel : (جهت مشخص سازی محل دریافت اطلاعات نگاشت)
using FluentNHibernate;
using NHSample1.Domain;
namespace UnitTests
{
public class TestModel : PersistenceModel
{
public TestModel()
{
AddMappingsFromAssembly(typeof(CustomerMapping).Assembly);
}
}
}
کلاس FixtureBase : (جهت ایجاد سشن NHibernate در ابتدای آزمون واحد و سپس پاکسازی اشیاء در پایان کار)
using NUnit.Framework;
using NHibernate;
using FluentNHibernate;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
namespace UnitTests
{
public class FixtureBase
{
protected SessionSource SessionSource { get; set; }
protected ISession Session { get; private set; }
[SetUp]
public void SetupContext()
{
var cfg = Fluently.Configure().Database(SQLiteConfiguration.Standard.InMemory);
SessionSource = new SessionSource(
cfg.BuildConfiguration().Properties,
new TestModel());
Session = SessionSource.CreateSession();
SessionSource.BuildSchema(Session);
}
[TearDown]
public void TearDownContext()
{
Session.Close();
Session.Dispose();
}
}
}
و کلاس CustomerMapping_Fixture.cs : (جهت بررسی صحت نگاشت تهیه شده با کمک دو کلاس قبل)
using NUnit.Framework;
using FluentNHibernate.Testing;
using NHSample1.Domain;
namespace UnitTests
{
[TestFixture]
public class CustomerMapping_Fixture : FixtureBase
{
[Test]
public void can_correctly_map_customer()
{
new PersistenceSpecification<Customer>(Session)
.CheckProperty(c => c.Id, 1001)
.CheckProperty(c => c.FirstName, "Vahid")
.CheckProperty(c => c.LastName, "Nasiri")
.CheckProperty(c => c.AddressLine1, "Addr1")
.CheckProperty(c => c.AddressLine2, "Addr2")
.CheckProperty(c => c.PostalCode, "1234")
.CheckProperty(c => c.City, "Tehran")
.CheckProperty(c => c.CountryCode, "IR")
.VerifyTheMappings();
}
}
}
توضیحات:
اکنون به عنوان یک برنامه نویس متعهد نیاز است تا کار صورت گرفته در قسمت قبل را آزمایش کنیم.
کار بررسی صحت نگاشت تعریف شده در قسمت قبل توسط کلاس استاندارد PersistenceSpecification فریم ورک FluentNHibernate انجام خواهد شد (در کلاس CustomerMapping_Fixture). این کلاس برای انجام عملیات آزمون واحد نیاز به کلاس پایه دیگری به نام FixtureBase دارد که در آن کار ایجاد سشن NHibernate (در قسمت استاندارد SetUp آزمون واحد) و سپس آزاد سازی آن را در هنگام خاتمه کار ، انجام میدهد (در قسمت TearDown آزمون واحد).
این ویژگیها که در مباحث آزمون واحد نیز به آنها اشاره شده است، سبب اجرای متدهایی پیش از اجرا و بررسی هر آزمون واحد و سپس آزاد سازی خودکار منابع خواهند شد.
برای ایجاد یک سشن NHibernate نیاز است تا نوع دیتابیس و همچنین رشته اتصالی به آن (کانکشن استرینگ) مشخص شوند. فریم ورک Fluent NHibernate با ایجاد کلاسهای کمکی برای این امر، به شدت سبب ساده سازی انجام آن شده است. در این مثال، نوع دیتابیس به SQLite و در حالت دیتابیس در حافظه (in memory)، تنظیم شده است (برای انجام امور آزمون واحد با سرعت بالا).
جهت اجرای هر دستوری در NHibernate نیاز به یک سشن میباشد. برای تعریف شیء سشن، نه تنها نیاز به مشخص سازی نوع و حالت دیتابیس مورد استفاده داریم، بلکه نیاز است تا وهلهای از کلاس استاندارد PersistanceModel را نیز جهت مشخص سازی کلاس نگاشت مورد استفاده مشخص نمائیم. برای این منظور کلاس TestModel فوق تعریف شده است تا این نگاشت را از اسمبلی مربوطه بخواند و مورد استفاده قرار دهد (بر پایی اولیه این مراحل شاید در ابتدای امر کمی زمانبر باشد اما در نهایت یک پروسه استاندارد است). توسط این کلاس به سیستم اعلام خواهیم کرد که اطلاعات نگاشت را باید از کدام کلاس دریافت کند.
تا اینجای کار شیء SessionSource را با معرفی نوع دیتابیس و همچنین محل دریافت اطلاعات نگاشت اشیاء معرفی کردیم. در دو سطر بعدی متد SetupContext کلاس FixtureBase ، ابتدا یک سشن را از این منبع سشن تهیه میکنیم. شیء منبع سشن در این فریم ورک در حقیقت یک factory object است (الگوهای طراحی برنامه نویسی شیءگرا) که امکان دسترسی به انواع و اقسام دیتابیسها را فراهم میسازد. برای مثال اگر روزی نیاز بود از دیتابیس اس کیوال سرور استفاده شود، میتوان از کلاس MsSqlConfiguration بجای SQLiteConfiguration استفاده کرد و همینطور الی آخر.
در ادامه توسط شیء SessionSource کار ساخت database schema را نیز به صورت پویا انجام خواهیم داد. بله، همانطور که متوجه شدهاید، کار ساخت database schema نیز به صورت پویا توسط فریم ورک NHibernate با توجه به اطلاعات کلاسهای نگاشت، صورت خواهد گرفت.
این مراحل، نحوه ایجاد و بر پایی یک آزمایشگاه آزمون واحد فریم ورک Fluent NHibernate را مشخص ساخته و در پروژههای شما میتوانند به کرات مورد استفاده قرار گیرند.
در ادامه اگر آزمون واحد را اجرا نمائیم (متد can_correctly_map_customer در کلاس CustomerMapping_Fixture)، نتیجه باید شبیه به شکل زیر باشد:
توسط متد CheckProperty کلاس PersistenceSpecification ، امکان بررسی نگاشت تهیه شده میسر است. اولین پارامتر آن، یک lambda expression خاصیت مورد نظر جهت بررسی است و دومین آرگومان آن، مقداری است که در حین آزمون به خاصیت تعریف شده انتساب داده میشود.
نکته:
شاید سؤال بپرسید که در تابع can_correctly_map_customer عملا چه اتفاقاتی رخ داده است؟ برای بررسی آن در متد SetupContext کلاس FixtureBase ، اولین سطر آنرا به صورت زیر تغییر دهید تا عبارات SQL نهایی تولید شده را نیز بتوانیم در حین عملیات تست مشاهده نمائیم:
var cfg = Fluently.Configure().Database(SQLiteConfiguration.Standard.ShowSql().InMemory);
مطابق متد تست فوق، عبارات تولید شده به شرح زیر هستند:
NHibernate: select next_hi from hibernate_unique_key
NHibernate: update hibernate_unique_key set next_hi = @p0 where next_hi = @p1;@p0 = 2, @p1 = 1
NHibernate: INSERT INTO "Customer" (FirstName, LastName, AddressLine1, AddressLine2, PostalCode, City, CountryCode, Id) VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7);@p0 = 'Vahid', @p1 = 'Nasiri', @p2 = 'Addr1', @p3 = 'Addr2', @p4 = '1234', @p5 = 'Tehran', @p6 = 'IR', @p7 = 1001
NHibernate: SELECT customer0_.Id as Id0_0_, customer0_.FirstName as FirstName0_0_, customer0_.LastName as LastName0_0_, customer0_.AddressLine1 as AddressL4_0_0_, customer0_.AddressLine2 as AddressL5_0_0_, customer0_.PostalCode as PostalCode0_0_, customer0_.City as City0_0_, customer0_.CountryCode as CountryC8_0_0_ FROM "Customer" customer0_ WHERE customer0_.Id=@p0;@p0 = 1001
نکته جالب این عبارات، استفاده از کوئریهای پارامتری است به صورت پیش فرض که در نهایت سبب بالا رفتن امنیت بیشتر برنامه (یکی از راههای جلوگیری از تزریق اس کیوال در ADO.Net که در نهایت توسط تمامی این فریم ورکها در پشت صحنه مورد استفاده قرار خواهند گرفت) و همچنین سبب بکار افتادن سیستمهای کش دیتابیسهای پیشرفته مانند اس کیوال سرور میشوند (execution plan کوئریهای پارامتری در اس کیوال سرور جهت بالا رفتن کارآیی سیستم کش میشوند و اهمیتی هم ندارد که حتما رویه ذخیره شده باشند یا خیر).
ادامه دارد ...
In the last post, I looked at auto-property enhancements, with several comments pointing out some nicer usages. I recently went through the HtmlTags codebase, C# 6-ifying “all the things”, and auto property and expression bodied function members were used pretty much everywhere. This is a large result of the codebase being quite tightly defined, with small objects and methods doing one thing well.
وبلاگ بیل گیتس
این قسمت از مقاله به ایده اصلی برنامه نویسی تابعی و دلیل وجودی آن خواهد پرداخت. هیچ شکی نیست که بزرگترین چالش در توسعه نرم افزارهای بزرگ، پیچیدگی آن است. تغییرات همیش اجتناب ناپذیر هستند. به خصوص زمانی که صحبت از پیاده سازی امکان جدیدی باشد، پیچیدگی اضافه خواهد شد. در نتیجه منجر به سخت شدن فهمیدن کد میشود، زمان توسعه را بالاتر میبرد و باگهای ناخواسته را به وجود خواهد آورد. همچنین تغییر هر چیزی در دنیای نرم افزار بدون به وجود آوردن رفتارهای ناخواسته و یا اثرات جانبی، تقریبا غیر ممکن است. در نهایت همه این موارد میتوانند سرعت توسعه را پایین برده و حتی باعث شکست پروژههای نرم افزاری شوند. سبکهای کد نویسی دستوری (Imperative) مانند برنامه نویسی شیء گرا، میتوانند به کاهش این پیچیدگیها تا حد خوبی کمک کنند. البته در صورتیکه به طور صحیحی پیاده شوند. در واقع با ایجاد Abstraction در این مدل برنامه نویسی، پیچیدگیها را مخفی میکنیم.
سیر تکاملی الگوهای برنامه نویسی
برنامه نویسی شیء گرا در خون برنامه نویسهای سی شارپ جاری است؛ ما معمولا ساعتها درباره اینکه چگونه میتوانیم با استفاده از ارث بری و ترتیب پیاده کلاسها، یک هدف خاص برسیم، بر روی کپسوله سازی تمرکز میکنیم و انتزاع (Abstraction) و چند ریختی ( Polymorphism ) را برای تغییر وضعیت برنامه استفاده میکنیم. در این مدل همیشه احتمال این وجود دارد که چند ترد به صورت همزمان به یک ناحیه از حافظه دسترسی داشته باشند و تغییری در آن به وجود بیاورند و باعث به وجود آمدن شرایط Race Condition شوند. البته همگی به خوبی میدانیم که میتوانیم یک برنامهی کاملا Thread-Safe هم داشته باشیم که به خوبی مباحث همزمانی و همروندی را مدیریت کند؛ اما یک مساله اساسی در مورد کارآیی باقی میماند. گرچه Parallelism به ما کمک میکند که کارآیی برنامه خود را افزایش دهیم، اما refactor کردن کدهای موجود، به حالت موازی، کاری سخت و پردردسر خواهد بود.
برنامه نویسی تابعی، یک الگوی برنامه نویسی است که از یک ایده قدیمی (قبل از اولین کامپیوترها !) برگرفته شدهاست؛ زمانیکه دو ریاضیدان، یک تئوری به نام lambda calculus را معرفی کردند، که یک چارچوب محاسباتی میباشد؛ عملیاتی ریاضی را انجام میدهد و نتیجه را محاسبه میکند، بدون اینکه تغییری را در وضعیت دادهها و وضعیت، به وجود بیاورد. با این کار، فهمیدن کدها آسانتر خواهد بود و اثرات جانبی را کمتر خواهد کرد، همچین نوشتن تستها سادهتر خواهند شد.
جالب است اگر زبانهای برنامه نویسی را که از برنامه نویسی تابعی پشتیبانی میکنند، بررسی کنیم، مانند Lisp , Clojure, Erlang, Haskel، هر کدام از این زبانها جنبههای مختلفی از برنامه نویسی تابعی را پوشش میدهند. #F یک عضو از خانواده ML میباشد که بر روی دات نت فریمورک در سال 2002 پیاده سازی شده. ولی جالب است بدانید بیشتر زبانهای همه کاره مانند #C به اندازه کافی انعطاف پذیر هستند تا بتوان الگوهای مختلفی را توسط آنها پیاده کرد. از آنجایی که اکثرا ما از #C برای توسعه نرم افزارهایمان استفاده میکنیم، ترکیب ایدههای برنامه نویسی تابعی میتواند راهکار جالبی برای حل مشکلات ما باشد.
قبلا درباره توابع ریاضی صحت کردیم. در زبانهای برنامه نویسی هم ایده همان است؛ ورودیهای مشخص و خروجی مورد انتظار، بدون تغییری در حالت برنامه. به این مفاهیم شفافیت و صداقت توابع میگوییم که در ادامه با آن بیشتر آشنا میشویم. به این نکته توجه داشته باشید که منظور از تابع در #C فقط Method نیست؛ Func , Action , Delegate هم نوعی تابع هستند.
به طور ساده با نگاه کردن به ورودیهای تابع و نام آنها باید بتوانیم کاری را که انجام میدهد، حدس بزنیم. یعنی یک تابع باید بر اساس ورودیهای آن کاری را انجام دهد و نباید یک پارامتر Global آن را تحت تاثیر قرار دهد. پارامترهای Global میتوانند یک Property در سطح یک کلاس باشند، یا یک شیء که وضعیت آن تحت کنترل تابع نیست؛ مانند شی DateTime. به مثال زیر توجه کنید:
public int CalculateElapsedDays(DateTime from) { DateTime now = DateTime.Now; return (now - from).Days; }
آیا میتوانید این تابع را شفاف کنیم؟ بله!
چطور؟ به سادگی! با تغییر پارامترهای ورودی:
public static int CalculateElapsedDays(DateTime from, DateTime now) => (now - from).Days;
صداقت یک تابع یعنی یک تابع باید همه اطلاعات مربوط به ورودیها و خروجیها را پوشش دهد. به این مثال دقت کنید:
public int Divide(int numerator, int denominator) { return numerator / denominator; }
آیا این همه مواردی را که از آن انتظار داریم پوشش میدهد؟ احتمالا خیر!
اگر دو عدد صحیح را به این تابع بفرستیم، احتمالا مشکلی پیش نخواهد آمد. اما همانطور که حدس میزنید اگر پارامتر دوم 0 باشد چه اتفاقی خواهد افتاد؟
var result = Divide(1,0);
چگونه مشکل را حل کنیم؟ تایپ ورودی را به شکل زیر تغییر دهیم:
public static int Divide(int numerator, NonZeroInt denominator) { return numerator / denominator.Value; }
Functions as first-class values
ترجمه فارسی این کلمه ما را از معنی اصلی آن خیلی دور میکند؛ احتمالا یک ترجمه سادهی آم میتواند «تابع، ارزش اولیه کلاس» باشد!
وقتی توابع first-class values باشند، یعنی میتوانند به عنوان ورودی سایر توابع استفاده شوند، میتوانند به یک متغیر انتساب داده شوند، دقیقا مثل یک مقدار. برای مثال:
Func<int, bool> isMod2 = x => x % 2 == 0; var list = Enumerable.Range(1, 10); var evenNumbers = list.Where(isMod2);
توابع مرتبه بالا! یک یا چند تابع را به عنوان ورودی میگیرند و یک تابع را به عنوان نتیجه بر میگرداند. در مثال بالا Extension Method ، Where یک تابع High-Order میباشد.
پیاده سازی Where احتمالا به شکل زیر میباشد:
public static IEnumerable<T> Where<T>(this IEnumerable<T> ts, Func<T, bool> predicate) { foreach (T t in ts) if (predicate(t)) yield return t; }
2. ملاک تشخیص اینکه چه آیتمهایی در لیست باید وجود داشته باشند، به عهده متدی میباشد که آن را فراخوانی میکند.
در این مثال، تابع Where، تابع ورودی را به ازای هر المان، در لیست فراخوانی میکند. این تابع میتواند طوری طراحی شود که تابع ورودی را به صورت شرطی اعمال کند. آزمایش این حالت به عهده شما میباشد. اما به صورت کلی انتظار میرود که قدرت توابع High-Order را درک کرده باشید.
Microbenchmark testing Python, Numba, Mojo, Dart, C/gcc, Rust, Go, JavaScript, C#, Java, Kotlin, Pascal, Ruby, Haskell performance in Mandelbrot set generation
Benchmarking several languages/tools with Mandelbrot set generation. 1-to-1 translation of code from one language to another. No SIMD, no multithreading (except prange() trick with Numba), no tricks (e.g. skipping sqrt), a bare minimum of language specific adjustments to make the code nicer while keeping all loops and operations in place.
first chance exception چیست؟
Exception thrown: 'System.FormatException' in System.Private.CoreLib.dll
catch(Exception ex) { Console.WriteLine(ex);
System.Runtime.InteropServices.COMException (0x80210015): Exception from HRESULT: 0x80210015 at WIA.CommonDialogClass.ShowAcquireImage