using UAParser; ... string uaString = "Mozilla/5.0 (iPhone; CPU iPhone OS 5_1_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9B206 Safari/7534.48.3"; // get a parser with the embedded regex patterns var uaParser = Parser.GetDefault(); // get a parser using externally supplied yaml definitions // var uaParser = Parser.FromYamlFile(pathToYamlFile); // var uaParser = Parser.FromYaml(yamlString); ClientInfo c = uaParser.Parse(uaString); Console.WriteLine(c.UserAgent.Family); // => "Mobile Safari" Console.WriteLine(c.UserAgent.Major); // => "5" Console.WriteLine(c.UserAgent.Minor); // => "1" Console.WriteLine(c.OS.Family); // => "iOS" Console.WriteLine(c.OS.Major); // => "5" Console.WriteLine(c.OS.Minor); // => "1" Console.WriteLine(c.Device.Family); // => "iPhone"
پروژه LINQKit
static string[] QueryCustomers (Expression<Func<Purchase, bool>> purchaseCriteria) { var data = new MyDataContext(); var query = from c in data.Customers.AsExpandable() where c.Purchases.Any (purchaseCriteria.Compile()) select c.Name; return query.ToArray(); }
یک نکتهی تکمیلی: روش نمایش خودکار آرگومانهای نامدار در Rider
اگر از Rider استفاده میکنید و علاقمندید تا خودش کار تکمیل و نمایش آرگومانهای نامدار را انجام دهد، روش کار به صورت زیر است:
الف) ویژگی فرمت کردن کدها را در حالت ذخیره سازی تغییرات، فعال کنید:
با اینکار، هربار که تغییرات را ذخیره میکنید، تنظیمات کدنویسی، به صورت خودکار به فایلهای ذخیره نشده، اعمال میشوند.
ب) به قسمت Settings -> Editor -> Cody Style -> C# -> Syntax Style مراجعه کرده و در قسمت تنظیمات آرگومانها، حداقل گزینههای Literal values و String literal values را بر روی named argumets قرار دهید تا نکات مطلب جاری، به صورت خودکار اعمال شوند:
همانطور که در مثال before/after تصویر فوق هم مشخص است، مزیت اینکار، مفهوم پیدا کردن اعداد و رشتههای وارد شده به عنوان آرگومانهای متدها هستند.
private static string ParsPDFToString()
{
PdfReader reader = new PdfReader("c:/2v.pdf");
PdfReaderContentParser parser = new PdfReaderContentParser(reader);
StringBuilder sb = new StringBuilder();
Console.WriteLine("reader.NumberOfPages : " + reader.NumberOfPages.ToString());
Console.ReadKey();
for (int i = 1; i <= reader.NumberOfPages; i++)
{
ITextExtractionStrategy strategy = parser.ProcessContent(
i , new SimpleTextExtractionStrategy()
);
sb.Append(strategy.GetResultantText());
}
return sb.ToString();
}
private static string ParsPDFToString()
{
PdfReader reader = new PdfReader("c:/2v.pdf");
PdfReaderContentParser parser = new PdfReaderContentParser(reader);
StringBuilder sb = new StringBuilder();
Console.WriteLine("reader.NumberOfPages : " + reader.NumberOfPages.ToString());
Console.ReadKey();
for (int i = 1; i <= reader.NumberOfPages; i++)
{
ITextExtractionStrategy strategy = parser.ProcessContent(
i , new SimpleTextExtractionStrategy()
);
sb.Append(strategy.GetResultantText());
}
return sb.ToString();
}
inline type name(parameters) { ... }
public class Vector { public double X { get; set; } public double Y { get; set; } public double Z { get; set; } // ... }
public class MyClass { public int A { get; set; } public int C; }
static void Main() { MyClass target = new MyClass(); int a = target.A; Console.WriteLine("A = {0}", a); int c = target.C; Console.WriteLine("C = {0}", c); }
int a = target.A; 0000003e mov ecx,edi 00000040 cmp dword ptr [ecx],ecx 00000042 call dword ptr ds:[05FA29A8h] 00000048 mov esi,eax 0000004a mov dword ptr [esp+4],esi int c = target.C; 00000098 mov edi,dword ptr [edi+4] MyClass.get_A() looks like this: 00000000 push esi 00000001 mov esi,ecx 00000003 cmp dword ptr ds:[03B701DCh],0 0000000a je 00000011 0000000c call 76BA6BA7 00000011 mov eax,dword ptr [esi+0Ch] 00000014 pop esi 00000015 ret
int a = target.A; 00000024 mov ebx,dword ptr [edi+0Ch]
- متد هایی که حجم کد IL آنها بیشتر از 32 بایت است.
- متدهای بازگشتی.
- متدهایی که با اتریبیوت MethodImpl علامتگذاری شدند و MethodImplOptions.NoInlining اعمال شده بر آن
- متدهای virtual
- متدهایی که دارای کد مدیریت خطا هستند
- Methods that take a large value type as a parameter
- Methods with complicated flowgraphs
using System; using System.Diagnostics; using System.Runtime.CompilerServices; class Program { const int _max = 10000000; static void Main() { // ... Compile the methods. Method1(); Method2(); int sum = 0; var s1 = Stopwatch.StartNew(); for (int i = 0; i < _max; i++) { sum += Method1(); } s1.Stop(); var s2 = Stopwatch.StartNew(); for (int i = 0; i < _max; i++) { sum += Method2(); } s2.Stop(); Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns")); Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns")); Console.Read(); } static int Method1() { // ... No inlining suggestion. return "one".Length + "two".Length + "three".Length + "four".Length + "five".Length + "six".Length + "seven".Length + "eight".Length + "nine".Length + "ten".Length; } [MethodImpl(MethodImplOptions.AggressiveInlining)] static int Method2() { // ... Aggressive inlining. return "one".Length + "two".Length + "three".Length + "four".Length + "five".Length + "six".Length + "seven".Length + "eight".Length + "nine".Length + "ten".Length; } } Output 7.34 ns No options 0.32 ns MethodImplOptions.AggressiveInlining
مطالعه بیشتر:
این مرورگرها در صورتیکه پیاده سازی پروتکل Open Search را در سایت شما پیدا کنند، به صورت خودکار امکان افزودن آنرا به عنوان منبع جستجوی جدیدی جهت جعبه متنی جستجوی خود ارائه میدهند. در ادامه قصد داریم با جزئیات پیاده سازی آن آشنا شویم.
تهیه OpenSearchResult سفارشی
برنامه باید بتواند محتوای XML ایی ذیل را مطابق پروتکل Open Search به صورت پویا تهیه و در اختیار مرورگر قرار دهد:
<?xml version="1.0" encoding="UTF-8" ? /> <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"> <ShortName>My Site's Asset Finder</ShortName> <Description>Find all your assets</Description> <Url type="text/html" method="get" template="http://MySite.com/Home/Search/?q=searchTerms"/> <InputEncoding>UTF-8</InputEncoding> <SearchForm>http://MySite.com/</SearchForm> </OpenSearchDescription>
using System; using System.Text; using System.Web; using System.Web.Mvc; using System.Xml; namespace WebToolkit { public class OpenSearchResult : ActionResult { public string ShortName { set; get; } public string Description { set; get; } public string SearchForm { set; get; } public string FavIconUrl { set; get; } public string SearchUrlTemplate { set; get; } public override void ExecuteResult(ControllerContext context) { if (context == null) throw new ArgumentNullException("context"); var response = context.HttpContext.Response; writeToResponse(response); } private void writeToResponse(HttpResponseBase response) { response.ContentEncoding = Encoding.UTF8; response.ContentType = "application/opensearchdescription+xml"; using (var xmlWriter = XmlWriter.Create(response.Output, new XmlWriterSettings { Indent = true })) { xmlWriter.WriteStartElement("OpenSearchDescription", "http://a9.com/-/spec/opensearch/1.1/"); xmlWriter.WriteElementString("ShortName", ShortName); xmlWriter.WriteElementString("Description", Description); xmlWriter.WriteElementString("InputEncoding", "UTF-8"); xmlWriter.WriteElementString("SearchForm", SearchForm); xmlWriter.WriteStartElement("Url"); xmlWriter.WriteAttributeString("type", "text/html"); xmlWriter.WriteAttributeString("template", SearchUrlTemplate); xmlWriter.WriteEndElement(); xmlWriter.WriteStartElement("Image"); xmlWriter.WriteAttributeString("width", "16"); xmlWriter.WriteAttributeString("height", "16"); xmlWriter.WriteString(FavIconUrl); xmlWriter.WriteEndElement(); xmlWriter.WriteEndElement(); xmlWriter.Close(); } } } }
تهیه OpenSearchController
در ادامه برای استفاده از Action Result سفارشی تهیه شده، نیاز است یک کنترلر را نیز به برنامه اضافه کنیم:
using System.Web.Mvc; namespace Readers { public partial class OpenSearchController : Controller { public virtual ActionResult Index() { var fullBaseUrl = Url.Action(result: MVC.Home.Index(), protocol: "http"); return new OpenSearchResult { ShortName = ".NET Tips", Description = ".NET Tips Contents Search", SearchForm = fullBaseUrl, FavIconUrl = fullBaseUrl + "favicon.ico", SearchUrlTemplate = Url.Action(result: MVC.Search.Index(), protocol: "http") + "?term={searchTerms}" }; } } }
الف) آدرسهای مطرح شده در آن باید مطلق باشند و نه نسبی. به همین جهت پارامتر protocol در اینجا ذکر شده است تا سبب تولید یک چنین آدرسهایی گردد.
ب) Url.Action ایی که در اینجا استفاده شده است مطابق تعاریف T4MVC است؛ ولی کلیات آن با نمونه پیش فرض ASP.NET MVC تفاوتی نمیکند. توسط T4MVC بجای ذکر نام اکشن متد و کنترلر مد نظر به صورت رشتهای، میتوان به صورت Strongly typed به این موارد ارجاع داد.
ج) تنها نکته مهم این کلاس، خاصیت SearchUrlTemplate است. قسمت انتهایی آن یعنی ={searchTerms} همیشه ثابت است. اما ابتدای این آدرس باید به کنترلر جستجوی شما که قادر است پارامتری را به شکل کوئری استرینگ دریافت کند، اشاره نماید.
د) FavIconUrl به آدرس یک آیکن در سایت شما اشاره میکند. برای نمونه ذکر favicon.ico پیش فرض سایت میتواند مفید باشد.
معرفی OpenSearchController به Header سایت
<link href="@Url.Action(result: MVC.OpenSearch.Index(), protocol: "http")" rel="search" title=".NET Tips Search" type="application/opensearchdescription+xml" />
Mocking چیست؟
فرض کنید برنامهای را داریم که از تعدادی کلاس تشکیل شدهاست. در این بین میخواهیم تعدادی از آنها را به صورت ایزولهی از کل سیستم آزمایش کنیم. البته باید درنظر داشت که این کلاسها در حین اجرای واقعی برنامه، از تعدادی وابستگی خاص در همان سیستم استفاده میکنند. برای مثال کلاسی در این بین برای بررسی میزان اعتبار مالی یک کاربر، نیاز دارد تا با یک وب سرویس خارجی کار کند. اما چون میخواهیم این کلاس را به صورت ایزولهی از کل سیستم آزمایش کنیم، اینبار بجای استفادهی از وابستگی واقعی این کلاس، آن وابستگی را با یک نمونهی تقلیدی یا Mock object در اینجا، جایگزین میکنیم.
بنابراین Mocking به معنای جایگزین کردن یک وابستگی واقعی سیستم که در زمان اجرای آن مورد استفاده قرار میگیرد، با نمونهی تقلیدی مختص زمان آزمایش برنامه، جهت بالابردن سهولت نوشتن آزمونهای واحد است.
دلایل و مزایای استفادهی از Mocking
- یکی از مهمترین دلایل استفادهی از Mocking، کاهش پیچیدگی تنظیمات اولیهی نوشتن آزمونهای واحد است. برای مثال اگر در برنامهی خود از تزریق وابستگیها استفاده میکنید و کلاسی دارای چندین وابستگی تزریق شدهی به آن است، برای آزمایش این کلاس نیاز به تدارک تمام این وابستگیها را خواهید داشت تا بتوان این کلاس را وهله سازی کرد و همچنین برنامه را نیز کامپایل نمود. اما در این بین ممکن است آزمایش متدی در همان کلاس، الزاما از تمام وابستگیهای تزریق شدهی در یک کلاس استفاده نکند. در این حالت، Mocking میتواند تنظیمات پیچیدهی وهله سازی این کلاس را به حداقل برساند.
- Mocking میتواند سبب افزایش سرعت اجرای آزمونهای واحد نیز شود. برای مثال با تقلید سرویسهای خارجی مورد استفادهی در برنامه (هر عملی که از مرزهای سیستم رد شود مانند کار با شبکه، بانک اطلاعاتی، فایل سیستم و غیره)، میتوان میزان I/O و همچنین زمان صرف شدهی به آنرا به حداقل رساند.
- از mock objects میتوان برای رهایی از مشکلات کار با مقادیر غیرمشخص استفاده کرد. برای مثال اگر در کدهای خود از DateTime.Now استفاده میکنید یا اعداد اتفاقی و امثال آن، هربار که آزمونهای واحد را اجرا میکنیم، خروجی متفاوتی را دریافت کرده و بسیاری از آزمونهای نوشته شده با مشکل مواجه میشوند. به کمک mocking میتوان بجای این مقادیر غیرمشخص، یک مقدار ثابت و مشخص را بازگشت دهد.
- چون به سادگی میتوان mock objects را تهیه کرد، میتوان کار توسعه و آزمایش برنامه را پیش از به پایان رسیدن پیاده سازی اصلی سرویسهای مدنظر، همینقدر که اینترفیس آن سرویس مشخص باشد، شروع کرد که میتواند برای کارهای تیمی بسیار مفید باشد.
- اگر وابستگی مورد استفاده ناپایدار و یا غیرقابل پیش بینی است، میتوان توسط mocking به یک نمونهی قابل پیش بینی و پایدار مخصوص آزمونهای برنامه رسید.
- اگر وابستگی خارجی مورد استفاده به ازای هر بار استفاده، هزینهای را شارژ میکند، میتوان توسط mocking، هزینهی آزمونهای برنامه را کاهش داد.
Unit test چیست؟
بدیهی است در کنار آزمایش ایزولهی قسمتهای مختلف برنامه توسط mocking، باید کل برنامه را جهت بررسی دستیابی به نتایج واقعی نیز آزمایش کرد که به این نوع آزمونها، آزمون یکپارچگی (Integration Tests)، API Tests ،UI Tests و غیره میگویند که در کنار Unit tests ما حضور خواهند داشت. بنابراین اکنون این سؤال مطرح میشود که یک Unit چیست؟
در برنامهای که از چندین کلاس تشکیل میشود، به یک کلاس، یک Unit گفته میشود. همچنین اگر در این سیستم، دو یا چند کلاس با هم کار میکنند (کلاسی که از چندین وابستگی استفاده میکند)، اینها با هم نیز یک Unit را تشکیل دهند. بنابراین تعریف Unit بستگی به نحوهی درک عملکرد یک سیستم و تعامل اجزای آن با هم دارد.
واژههای متناظر با Mock objects
در حین مطالعهی منابع مرتبط با آزمونهای واحد ممکن است با این واژههای تقریبا مشابه مواجه شوید: fakes ،stubs ،dummies و mocks. اما تفاوت آنها در چیست؟
- Fakes در حقیقت یک نمونه پیاده سازی واقعی، اما غیرمناسب محیط واقعی و اصلی پروژهاست. برای نمونه EF Core به همراه یک نمونه in-memory database هم هست که دقیقا با مفهوم Fakes تطابق دارد.
- از Dummies صرفا جهت تهیهی پارامترهای مورد نیاز برای اجرای یک آزمایش استفاده میشوند. این پارامترها، هیچگاه در آزمایشهای انجام شده مورد استفاده قرار نمیگیرند.
- از Stubs برای ارائهی پاسخهایی مشخص به فراخوانها استفاده میشود. برای مثال یک متد یا خاصیت، دقیقا چه چیزی را باید بازگشت دهند.
- از Mocks برای بررسی تعامل اجزای مختلف در حال آزمایش استفاده میشود. آیا متدی یا خاصیتی مورد استفاده قرار گرفتهاست یا خیر؟
باید درنظر داشت که زمانیکه یک شیء Mock را توسط کتابخانهی Moq تهیه میکنیم، هر سه مفهوم stubs ،dummies و mocks را با هم به همراه دارد. به همین جهت در این سری زمانیکه به یک mock object اشاره میشود، هر سه مفهوم مدنظر هستند.
واژهی دیگری که ممکن است در این گروه زیاد مشاهده شود، «Test double» نام دارد که ترکیب هر 4 مورد fakes ،stubs ،dummies و mocks میباشد. در کل هر زمانیکه یک شیء مورد استفادهی در زمان اجرای برنامه را جهت آزمایش سادهتر آن جایگزین میکنید، یک Test double را ایجاد کردهاید.
بررسی ساختار برنامهای که میخواهیم آنرا آزمایش کنیم
در این سری قصد داریم یک برنامهی وام دهی را آزمایش کنیم که قسمتهای مختلف آن دارای وابستگیهای خاصی میباشند. ساختار این برنامه را در ادامه مشاهده میکنید:
موجودیتهای برنامهی وام دهی
namespace Loans.Entities { public class Applicant { public int Id { set; get; } public string Name { set; get; } public int Age { set; get; } public string Address { set; get; } public decimal Salary { set; get; } } }
namespace Loans.Entities { public class LoanProduct { public int Id { set; get; } public string ProductName { set; get; } public decimal InterestRate { set; get; } } }
namespace Loans.Entities { public class LoanApplication { public int Id { set; get; } public LoanProduct Product { set; get; } public LoanAmount Amount { set; get; } public Applicant Applicant { set; get; } public bool IsAccepted { set; get; } } public class LoanAmount { public string CurrencyCode { get; set; } public decimal Principal { get; set; } } }
مدلهای برنامهی وام دهی
namespace Loans.Models { public class IdentityVerificationStatus { public bool Passed { get; set; } } }
سرویسهای برنامهی وام دهی
using Loans.Models; namespace Loans.Services.Contracts { public interface IIdentityVerifier { void Initialize(); bool Validate(string applicantName, int applicantAge, string applicantAddress); void Validate(string applicantName, int applicantAge, string applicantAddress, out bool isValid); void Validate(string applicantName, int applicantAge, string applicantAddress, ref IdentityVerificationStatus status); } }
namespace Loans.Services.Contracts { public interface ICreditScorer { int Score { get; } void CalculateScore(string applicantName, string applicantAddress); } }
using System; using Loans.Entities; using Loans.Services.Contracts; namespace Loans.Services { public class LoanApplicationProcessor { private const decimal MinimumSalary = 1_500_000_0; private const int MinimumAge = 18; private const int MinimumCreditScore = 100_000; private readonly IIdentityVerifier _identityVerifier; private readonly ICreditScorer _creditScorer; public LoanApplicationProcessor( IIdentityVerifier identityVerifier, ICreditScorer creditScorer) { _identityVerifier = identityVerifier ?? throw new ArgumentNullException(nameof(identityVerifier)); _creditScorer = creditScorer ?? throw new ArgumentNullException(nameof(creditScorer)); } public bool Process(LoanApplication application) { application.IsAccepted = false; if (application.Applicant.Salary < MinimumSalary) { return application.IsAccepted; } if (application.Applicant.Age < MinimumAge) { return application.IsAccepted; } _identityVerifier.Initialize(); var isValidIdentity = _identityVerifier.Validate( application.Applicant.Name, application.Applicant.Age, application.Applicant.Address); if (!isValidIdentity) { return application.IsAccepted; } _creditScorer.CalculateScore(application.Applicant.Name, application.Applicant.Address); if (_creditScorer.Score < MinimumCreditScore) { return application.IsAccepted; } application.IsAccepted = true; return application.IsAccepted; } } }
using System; using Loans.Models; using Loans.Services.Contracts; namespace Loans.Services { public class IdentityVerifierServiceGateway : IIdentityVerifier { public DateTime LastCheckTime { get; private set; } public void Initialize() { // Initialize connection to external service } public bool Validate(string applicantName, int applicantAge, string applicantAddress) { Connect(); var isValidIdentity = CallService(applicantName, applicantAge, applicantAddress); LastCheckTime = DateTime.Now; Disconnect(); return isValidIdentity; } private void Connect() { // Open connection to external service } private bool CallService(string applicantName, int applicantAge, string applicantAddress) { // Make call to external service, interpret the response, and return result return false; // Simulate result for demo purposes } private void Disconnect() { // Close connection to external service } public void Validate(string applicantName, int applicantAge, string applicantAddress, out bool isValid) { throw new NotImplementedException(); } public void Validate(string applicantName, int applicantAge, string applicantAddress, ref IdentityVerificationStatus status) { throw new NotImplementedException(); } } }
هدف از این برنامه، درخواست یک وام جدید است. Application در اینجا به معنای درخواست یا فرم جدید است و Applicant نیز شخصی است که این درخواست را دادهاست.
در اینجا بیشتر تمرکز ما بر روی کلاس LoanApplicationProcessor است که دارای دو وابستگی تزریق شدهی به آن نیز میباشد:
public LoanApplicationProcessor( IIdentityVerifier identityVerifier, ICreditScorer creditScorer) { _identityVerifier = identityVerifier ?? throw new ArgumentNullException(nameof(identityVerifier)); _creditScorer = creditScorer ?? throw new ArgumentNullException(nameof(creditScorer)); }
تمام این منطق نیز در متد Process آن قابل مشاهدهاست که هدف اصلی آن، بررسی قابل پذیرش بودن درخواست یک وام جدید است.
نوشتن اولین تست، برای برنامهی وام دهی
در اولین تصویر این قسمت، پروژهی class library دومی را نیز به نام Loans.Tests مشاهده میکنید. فایل csproj آن به صورت زیر برای کار با MSTest تنظیم شدهاست:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netcoreapp2.2</TargetFramework> </PropertyGroup> <ItemGroup> <ProjectReference Include="..\Loans\Loans.csproj" /> </ItemGroup> <ItemGroup> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" /> <PackageReference Include="MSTest.TestAdapter" Version="2.0.0" /> <PackageReference Include="MSTest.TestFramework" Version="2.0.0" /> </ItemGroup> </Project>
اکنون اولین آزمون واحد ما در کلاس جدید LoanApplicationProcessorShould چنین شکلی را پیدا میکند:
using Loans.Entities; using Loans.Services; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Loans.Tests { [TestClass] public class LoanApplicationProcessorShould { [TestMethod] public void DeclineLowSalary() { var product = new LoanProduct {Id = 99, ProductName = "Loan", InterestRate = 5.25m}; var amount = new LoanAmount {CurrencyCode = "Rial", Principal = 2_000_000_0}; var applicant = new Applicant {Id = 1, Name = "User 1", Age = 25, Address = "This place", Salary = 1_100_000_0}; var application = new LoanApplication {Id = 42, Product = product, Amount = amount, Applicant = applicant}; var processor = new LoanApplicationProcessor(null, null); processor.Process(application); Assert.IsFalse(application.IsAccepted); } } }
در این آزمایش، شخص درخواست کننده، حقوق کمی دارد و میخواهیم بررسی کنیم که آیا LoanApplicationProcessor میتواند آنرا بر اساس مقدار MinimumSalary، رد کند یا خیر؟
public class LoanApplicationProcessor { private const decimal MinimumSalary = 1_500_000_0;
در حین وهله سازی LoanApplicationProcessor، دو وابستگی آن به null تنظیم شدهاند؛ چون میدانیم که بررسی MinimumSalary پیش از سایر بررسیها صورت میگیرد و اساسا در این آزمایش، نیازی به این وابستگیها نداریم.
اما اگر سعی در اجرای این آزمایش کنیم (برای مثال با اجرای دستور dotnet test در خط فرمان)، آزمایش اجرا نشده و با استثنای زیر مواجه میشویم:
Test method Loans.Tests.LoanApplicationProcessorShould.DeclineLowSalary threw exception: System.ArgumentNullException: Value cannot be null. Parameter name: identityVerifier
public LoanApplicationProcessor( IIdentityVerifier identityVerifier, ICreditScorer creditScorer) { _identityVerifier = identityVerifier ?? throw new ArgumentNullException(nameof(identityVerifier)); _creditScorer = creditScorer ?? throw new ArgumentNullException(nameof(creditScorer)); }
نصب کتابخانهی Moq جهت برآورده کردن وابستگیهای کلاس LoanApplicationProcessor
در این آزمایش چون وجود وابستگیهای در سازندهی کلاس، برای ما اهمیتی ندارند و همچنین ذکر آنها نیز الزامی است، میخواهیم توسط کتابخانهی Moq، دو نمونهی تقلیدی از آنها را تهیه کرده (همان dummies که پیشتر معرفی شدند) و جهت برآورده کردن بررسی صورت گرفتهی در سازندهی کلاس LoanApplicationProcessor، آنها را ارائه کنیم.
کتابخانهی بسیار معروف Moq، با پروژههای مبتنی بر NETFramework 4.5. و همچنین NETStandard 2.0. به بعد سازگار است و برای نصب آن، میتوان یکی از دو دستور زیر را صادر کرد:
> dotnet add package Moq > Install-Package Moq
اما چرا کتابخانهی Moq؟
کتابخانهی Moq این اهداف را دنبال میکند: سادهاست، به شدت کاربردیاست و همچنین strongly typed است. این کتابخانه سورس باز بوده و تعداد بار دانلود بستهی نیوگت آن میلیونی است.
پس از نصب آن، اولین آزمایشی را که نوشتیم، به صورت زیر اصلاح میکنیم:
using Loans.Entities; using Loans.Services; using Loans.Services.Contracts; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; namespace Loans.Tests { [TestClass] public class LoanApplicationProcessorShould { [TestMethod] public void DeclineLowSalary() { var product = new LoanProduct {Id = 99, ProductName = "Loan", InterestRate = 5.25m}; var amount = new LoanAmount {CurrencyCode = "Rial", Principal = 2_000_000_0}; var applicant = new Applicant {Id = 1, Name = "User 1", Age = 25, Address = "This place", Salary = 1_100_000_0}; var application = new LoanApplication {Id = 42, Product = product, Amount = amount, Applicant = applicant}; var mockIdentityVerifier = new Mock<IIdentityVerifier>(); var mockCreditScorer = new Mock<ICreditScorer>(); var processor = new LoanApplicationProcessor(mockIdentityVerifier.Object, mockCreditScorer.Object); processor.Process(application); Assert.IsFalse(application.IsAccepted); } } }
کار با ذکر new Mock شروع شده و آرگومان جنریک آنرا از نوع وابستگیهایی که نیاز داریم، مقدار دهی میکنیم. سپس خاصیت Object آن، امکان دسترسی به این شیء تقلید شده را میسر میکند.
اکنون اگر مجددا این آزمون واحد را اجرا کنیم، مشاهده خواهیم کرد که بجای صدور استثناء، با موفقیت به پایان رسیدهاست:
گاهی از اوقات جایگزین کردن یک وابستگی null با نمونهی Mock آن کافی نیست
در مثالی که بررسی کردیم، اشیاء mock، کار برآورده کردن نیازهای ابتدایی آزمایش را انجام داده و سبب اجرای موفقیت آمیز آن شدند؛ اما همیشه اینطور نیست:
using Loans.Entities; using Loans.Services; using Loans.Services.Contracts; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; namespace Loans.Tests { [TestClass] public class LoanApplicationProcessorShould { [TestMethod] public void Accept() { var product = new LoanProduct {Id = 99, ProductName = "Loan", InterestRate = 5.25m}; var amount = new LoanAmount {CurrencyCode = "Rial", Principal = 2_000_000_0}; var applicant = new Applicant {Id = 1, Name = "User 1", Age = 25, Address = "This place", Salary = 1_500_000_0}; var application = new LoanApplication {Id = 42, Product = product, Amount = amount, Applicant = applicant}; var mockIdentityVerifier = new Mock<IIdentityVerifier>(); var mockCreditScorer = new Mock<ICreditScorer>(); var processor = new LoanApplicationProcessor(mockIdentityVerifier.Object, mockCreditScorer.Object); processor.Process(application); Assert.IsTrue(application.IsAccepted); } } }
اگر این آزمایش را اجرا کنیم، با شکست مواجه خواهد شد. علت اینجا است که هرچند در حال استفادهی از دو mock object به عنوان وابستگیهای مورد نیاز هستیم، اما تنظیمات خاصی را بر روی آنها انجام ندادهایم و به همین جهت خروجی مناسبی را در اختیار LoanApplicationProcessor قرار نمیدهند. برای مثال مرحلهی بعدی بررسی اعتبار شخص در کلاس LoanApplicationProcessor، فراخوانی سرویس identityVerifier و متد Validate آن است که خروجی آن بر اساس کدهای فعلی، همیشه false است:
_identityVerifier.Initialize(); var isValidIdentity = _identityVerifier.Validate( application.Applicant.Name, application.Applicant.Age, application.Applicant.Address);
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: MoqSeries-01.zip
آموزش TypeScript #5
export interface ILogger { log(message: string): void; }
حال نیاز به کلاسی داریم که این اینترفیس را پیاده سازی کند. این پیاده سازی به صورت زیر انجام میگیرد:
export class Logger implements ILogger { }
export class AnnoyingLogger implements ILogger { log(message: string): void{ alert(message); } }
export class MyClass implements IFirstInterface, ISecondInterface { }
IPerson { firstName: string; lastName: string; } class Person implements IPerson { constructor(public firstName: string, public lastName: string) { } } varpersonA: IPerson = newPerson('Masoud', 'Pakdel'); //expilict varpersonB: IPerson = { firstName: 'Ashkan', lastName: 'Shahram'}; // duck typing
پیاده سازی چند اینترفیس به صورت همزمان
همانند دات نت که یک کلاس فقط میتواند از یک کلاس ارث ببرد ولی میتواند n تا اینترفیس را پیاده سازی کند در TypeScript نیز چنین قوانینی وجود دارد. یعنی یک اینترفیس میتواند چندین اینترفیس دیگر را توسعه دهد(extend) و کلاسی که این اینترفیس را پیاده سازی میکند باید تمام توابع اینترفیسها را پیاده سازی کند. مثال:
interface IMover { move() : void; } interface IShaker { shake() : void; } interface IMoverShaker extends IMover, IShaker { } class MoverShaker implements IMoverShaker { move() { } shake() { } }
instanceof
از instanceof زمانی استفاده میکنیم که قصد داشته باشیم که یک instance را با یک نوع مشخص مقایسه کنیم. اگر instance مربوطه از نوع مشخص باشد یا از این نوع ارث برده باشد مقدار true برگشت داده میشود در غیر این صورت مقدار false خواهد بود.
یک مثال:
var isLogger = logger instanceof Utilities.Logger; var isLogger = logger instanceof Utilities.AnnoyingLogger; var isLogger = logger instanceof Utilities.Formatter;
در TypeScript میتوان مانند زبانهای شی گرای دیگر Method overriding را پیاده سازی کرد. یعنی میتوان متدهای کلاس پایه را در کلاس مشتق شده تحریف کرد. با یک مثال به شرح این مورد خواهم پرداخت.
فرض کنید یک کلاس پایه به صورت زیر داریم:
class BaseEmployee { constructor (public fname: string,public lname: string) { } sayInfo() { alert('this is base class method'); } }
class Employee extends BaseEmployee { sayInfo() { alert('this is derived class method'); } } window.onload = () => { var first: BaseEmployee= new Employee(); first.sayInfo(); var second: BaseEmployee = new BaseEmployee(); second.sayInfo(); }
*اگر در کلاس مشتق شده قصد داشته باشیم که به توابع و فیلدهای کلاس پایه اشاره کنیم باید از کلمه کلیدی super استفاده کنیم.(معادل base در #C).
مثال:
class Animal { constructor (public name: string) { } } class Dog extends Animal { constructor (public name: string, public age:number) { super(name); } sayHello() {
alert(super.name);
} }
public class Dog : Animal { public Dog (string name, int age):base(name) { } }
*دقت کنید که مباحث مربوط به interface و private modifier و Type safety که پیشتر در مورد آنها بحث شد، فقط در فایلهای TypeScript و در هنگام کد نویسی و طراحی معنی دار هستند، زیرا بعد از کامپایل فایلهای ts این مفاهیم در Javascript پشتیبانی نمیشوند در نتیجه هیچ مورد استفاده هم نخواهد داشت.
ادامه دارد...
فصل | عنوان | فایل مرتبط |
11 | آشنایی با MVVM Light Toolkit | + |
12 | اعمال قالبهای متفاوت به برنامههای Silverlight | + |
13 | استفاده از Web Services در Silverlight | + |
14 | آشنایی با مفاهیم مرتبط با شیء Application و مدیریت آن | + |
15 | امنیت در Silverlight | + |
16 | روشهای تعیین اعتبار ورودی کاربر در Silverlight | + |
17 | استفاده از تصاویر و فایلهای چند رسانهای در Silverlight | + |
18 | بررسی جامع تواناییهای کنترل DataGrid | + |
19 | معرفی سایر امکانات و ویژگیهای اختصاصی Silverlight 4 | + |
20 | بررسی اجرای خارج از مرورگر برنامههای Silverlight | + |