.NET 8 Preview 3 is now available. It includes changes to build paths, workloads, Microsoft.Extensions, and containers. It also includes performance improvements in the JIT, for Arm64, and dynamic PGO. If you missed the March preview, you may want to read the Preview 2 post.
سناریویی را در نظر بگیرید که یک برنامه وب نوشته شده، قرار است به چندین مستاجر (مشتری یا tenant) خدماتی را ارائه کند. در این حالت اطلاعات هر مشتری به صورت کاملا جدا شده از دیگر مشتریان در سیستم قرار دارد و فقط به همان قسمتها دسترسی دارد.
مثلا یک برنامه مدیریت رستوران را در نظر بگیرید که برای هر مشتری، در دامین
مخصوص به خود قرار دارد و همه آنها به یک سیستم متمرکز متصل شده و اطلاعات
خود را از آنجا دریافت میکنند.
در معماری Multi-Tenancy، چندین کاربر میتوانند از یک نمونه (Single
Instance) از اپلیکیشن نرمافزاری استفاده کنند. یعنی این نمونه روی سرور
اجرا میشود و به چندین کاربر سرویس میدهد. هر کاربر را یک Tenant
مینامیم. میتوان به Tenantها امکان تغییر و شخصیسازی بخشی از اپلیکیشن
را داد؛ مثلا امکان تغییر رنگ رابط کاربری و یا قوانین کسبوکار، اما آنها نمیتوانند
کدهای اپلیکیشن را شخصیسازی کنند.
خوشبختانه اوضاع با وجود OWIN بهتر شده و ما در این مطلب قصد استفاده از یک تولکیت را به نام SaasKit، برای پیاده سازی این معماری در ASP.NET Core داریم. هدف از این toolkit، سادهتر کردن هر چه بیشتر ساخت برنامههای SaaS (Software as a Service) هست. با استفاده از OWIN ما قادریم که بدون در نظر گرفتن فریم ورک مورد استفاده، رفتار مورد نظر خودمان را مستقیما در یک چرخه درخواست HTTP پیاده سازی کنیم و البته به لطف طراحی خاص ASP.NET Core 1.0 و استفاده از میان افزارهایی مشابه OWIN در برنامه، کار ما با SaasKit باز هم راحتتر خواهد بود.
شروع کار
یک پروژه ASP.NET Core جدید را ایجاد کنید و سپس ارجاعی را به فضای نام SaasKit.Multitenancy (موجود در Nuget) بدهید.PM> Install-Package SaasKit.Multitenancy
شناسایی مستاجر (tenant)
اولین جنبه در معماری multi-tenant، شناسایی مستاجر بر اساس اطلاعات درخواست جاری میباشد که میتواند از hostname ، کاربر جاری یا یک HTTP header باشد.ابتدا به تعریف کلاس مستاجر میپردازیم:
public class AppTenant { public string Name { get; set; } public string[] Hostnames { get; set; } }
public class AppTenantResolver : ITenantResolver<AppTenant> { IEnumerable<AppTenant> tenants = new List<AppTenant>(new[] { new AppTenant { Name = "Tenant 1", Hostnames = new[] { "localhost:6000", "localhost:6001" } }, new AppTenant { Name = "Tenant 2", Hostnames = new[] { "localhost:6002" } } }); public async Task<TenantContext<AppTenant>> ResolveAsync(HttpContext context) { TenantContext<AppTenant> tenantContext = null; var tenant = tenants.FirstOrDefault(t => t.Hostnames.Any(h => h.Equals(context.Request.Host.Value.ToLower()))); if (tenant != null) { tenantContext = new TenantContext<AppTenant>(tenant); } return tenantContext; } }
سیم کشی کردن
بعد از پیاده سازی این اینترفیس نوبت به سیم کشیهای SaasKit میرسد. من در اینجا سعی کردم که مثل الگوی برنامههای ASP.NET Core عمل کنم. ابتدا نیاز داریم که وابستگیهای SaasKit را ثبت کنیم. فایل startups.cs را باز کنید و کدهای زیر را در متد ConfigureServices اضافه نمایید:public void ConfigureServices(IServiceCollection services) { services.AddMultitenancy<AppTenant, AppTenantResolver>(); }
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { // after .UseStaticFiles() app.UseMultitenancy<AppTenant>(); // before .UseMvc() }
دریافت مستاجر جاری
حالا هر جا که نیاز به وهلهای از شیء مستاجر جاری داشتید، میتوانید به روش زیر عمل کنید:public class HomeController : Controller { private AppTenant tenant; public HomeController(AppTenant tenant) { this.tenant = tenant; } }
@inject AppTenant Tenant;
<a asp-controller="Home" asp-action="Index">@Tenant.Name</a>
اجرای نمونه مثال
فایل project.json را باز کنید و مقدار web را به شکل زیر مقدار دهی کنید: (در اینجا برای سایت خود 3 آدرس را نگاشت کردیم)"commands": { "web": "Microsoft.AspNet.Server.Kestrel --server.urls=http://localhost:6000;http://localhost:6001;http://localhost:6002", },
dotnet run
و اگر آدرس http://localhost:6002 را وارد کنیم، مستاجر 2 را مشاهده میکنیم:
قابل پیکربندی کردن مستاجر ها
از آنجائیکه نوشتن مشخصات مستاجرها در کد زیاد جالب نیست، برای همین
تصمیم داریم که این مشخصات را با استفاده از قابلیتهای ASP.NET Core از
فایل appsettings.json دریافت کنیم. تنظیمات مستاجرها را مطابق اطلاعات زیر به این فایل اضافه کنید:
"Multitenancy": { "Tenants": [ { "Name": "Tenant 1", "Hostnames": [ "localhost:6000", "localhost:6001" ] }, { "Name": "Tenant 2", "Hostnames": [ "localhost:6002" ] } ] }
public class MultitenancyOptions { public Collection<AppTenant> Tenants { get; set; } }
services.Configure<MultitenancyOptions>(Configuration.GetSection("Multitenancy"));
public class AppTenantResolver : ITenantResolver<AppTenant> { private readonly IEnumerable<AppTenant> tenants; public AppTenantResolver(IOptions<MultitenancyOptions> options) { this.tenants = options.Value.Tenants; } public async Task<TenantContext<AppTenant>> ResolveAsync(HttpContext context) { TenantContext<AppTenant> tenantContext = null; var tenant = tenants.FirstOrDefault(t => t.Hostnames.Any(h => h.Equals(context.Request.Host.Value.ToLower()))); if (tenant != null) { tenantContext = new TenantContext<AppTenant>(tenant); } return Task.FromResult(tenantContext); } }
در آخر
اولین قدم در پیاده سازی یک معماری multi-tenant، تصمیم گیری درباره این
موضوع است که شما چطور مستاجر خود را شناسایی کنید. به محض این شناسایی شما میتوانید عملیاتهای بعدی خود را مثل تفکیک بخشی از برنامه، فیلتر کردن دادهای، نمایش یک view خاص برای هر مستاجر و یا بازنویسی قسمتهای مختلف
برنامه بر اساس هر مستاجر، انجام دهید.
_ سورس مثال بالا در گیت هاب قابل دریافت میباشد.
_ منبع: اینجا
پیش نیازها
- شروع یک پروژهی جدید وب با پشتیبانی از Web API
- نصب دو بستهی نیوگت مرتبط با Structure map 3
PM>install-package structuremap PM>install-package structuremap.web
پیاده سازی IHttpControllerActivator توسط Structure map
using System; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using StructureMap; namespace WebApiDISample.Core { public class StructureMapHttpControllerActivator : IHttpControllerActivator { private readonly IContainer _container; public StructureMapHttpControllerActivator(IContainer container) { _container = container; } public IHttpController Create( HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { var nestedContainer = _container.GetNestedContainer(); request.RegisterForDispose(nestedContainer); return (IHttpController)nestedContainer.GetInstance(controllerType); } } }
نکتهی مهم آن استفاده از NestedContainer آن است. معرفی آن به متد request.RegisterForDispose سبب میشود تا کلیه کلاسهای IDisposable نیز در پایان کار به صورت خودکار رها سازی شده و نشتی حافظه رخ ندهد.
معرفی StructureMapHttpControllerActivator به برنامه
فایل WebApiConfig.cs را گشوده و تغییرات ذیل را در آن اعمال کنید:
using System.Web.Http; using System.Web.Http.Dispatcher; using StructureMap; using WebApiDISample.Core; using WebApiDISample.Services; namespace WebApiDISample { public static class WebApiConfig { public static void Register(HttpConfiguration config) { // IoC Config ObjectFactory.Configure(c => c.For<IEmailsService>().Use<EmailsService>()); var container = ObjectFactory.Container; GlobalConfiguration.Configuration.Services.Replace( typeof(IHttpControllerActivator), new StructureMapHttpControllerActivator(container)); // Web API routes config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); } } }
تهیه سرویسی برای آزمایش برنامه
namespace WebApiDISample.Services { public interface IEmailsService { void SendEmail(); } } using System; namespace WebApiDISample.Services { /// <summary> /// سرویسی که دارای قسمت دیسپوز نیز هست /// </summary> public class EmailsService : IEmailsService, IDisposable { private bool _disposed; ~EmailsService() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } public void SendEmail() { //todo: send email! } protected virtual void Dispose(bool disposeManagedResources) { if (_disposed) return; if (!disposeManagedResources) return; //todo: clean up resources here ... _disposed = true; } } }
نکتهی مهم آن استفاده از IDisposable در این کلاس خاص است (ضروری نیست؛ صرفا جهت بررسی بیشتر اضافه شدهاست). اگر در کدهای برنامه، یک چنین کلاسی وجود داشت، نیاز است متد Dispose آن نیز توسط IoC Container فراخوانی شود. برای آزمایش آن یک break point را در داخل متد Dispose قرار دهید.
استفاده از سرویس تعریف شده در یک Web API Controller
using System.Web.Http; using WebApiDISample.Services; namespace WebApiDISample.Controllers { public class ValuesController : ApiController { private readonly IEmailsService _emailsService; public ValuesController(IEmailsService emailsService) { _emailsService = emailsService; } // GET api/values/5 public string Get(int id) { _emailsService.SendEmail(); return "_emailsService.SendEmail(); called!"; } } }
تزریق وهلهی مورد نیاز آن، به صورت خودکار توسط StructureMapHttpControllerActivator که در ابتدای بحث معرفی شد، صورت میگیرد.
فراخوانی متد Get آنرا نیز توسط کدهای سمت کاربر ذیل انجام خواهیم داد:
<h2>Index</h2> @section scripts { <script type="text/javascript"> $(function () { $.getJSON('/api/values/1?timestamp=' + new Date().getTime(), function (data) { alert(data); }); }); </script> }
اکنون برنامه را اجرا کنید. هنگام فراخوانی متد Get، وهلهی سرویس مورد نظر، نال نیست. همچنین متد Dispose نیز به صورت خودکار فراخوانی میشود.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید
WebApiDISample.zip
Dependency Injection
در ادامه مباحث بهتر کد بنویسیم و الگوهایی که در این رابطه معرفی شدند، اخیرا کتابی از انتشارات manning منتشر شده تحت عنوان Dependency Injection . هر چند به ظاهر این کتاب برای جاوا کارها تهیه شده اما قسمت عمدهای از آن برای سایر زبانهای برنامه نویسی دیگر نیز قابل استفاده است.
In object-oriented programming, a central program normally controls other objects in a module, library, or framework. With dependency injection, this pattern is inverted—a reference to a service is placed directly into the object which eases testing and modularity. Spring or Google Guice use dependency injection so you can focus on your core application and let the framework handle infrastructural concerns.
Dependency Injection explores the DI idiom in fine detail, with numerous practical examples that show you the payoffs. You'll apply key techniques in Spring and Guice and learn important pitfalls, corner-cases, and design patterns. Readers need a working knowledge of Java but no prior experience with DI is assumed.
WHAT'S INSIDE:
◊ How to apply it (Understand it first!)
◊ Design patterns and nuances
◊ Spring, Google Guice, PicoContainer, and more
◊ How to integrate DI with Java frameworks
راستی، این کتاب تر و تازه رو میتونید از همین کتاب فروشیهای دور و اطراف نیز تهیه کنید! در سایت booktraining دات ارگ در قسمت graphics-and-design به تاریخ 4 آگوست.
متدی تحت عنوان ValidateEmail را تصور کنید. این متد از حیث بازگشت نتیجه به عنوان خروجی میتواند به اشکال مختلفی پیاده سازی شود که در ادامه مشاهده میکنیم:
متد ValidateEmail با خروجی Boolean
public bool ValidateEmail(string email) { var valid = true; if (string.IsNullOrWhiteSpace(email)) { valid = false; } var isValidFormat = true;//todo: using RegularExpression if (!isValidFormat) { valid = false; } var isRealDoamin = true;//todo: Code here that confirms whether domain exists. if (!isRealDoamin) { valid = false; } return valid; }
همانطور که در تکه کد زیر مشخص میباشد، استفاده کننده از متد بالا، امکان بررسی خروجی آن را در قالب یک شرط خواهد داشت و علاوه بر اینکه پیاده سازی آن ساده میباشد، خوانایی کد را نیز بالا میبرد؛ ولی با این حال نمیتوان متوجه شد مشکل اصلی آدرس ایمیل ارسالی به عنوان آرگومان، دقیقا چیست.
var email = "email@example.com"; var isValid = ValidateEmail(email); if(isValid) { //do something }
متد ValidateEmail با صدور استثناء
public void ValidateEmail(string email) { if (string.IsNullOrWhiteSpace(email)) throw new ArgumentNullException(nameof(email)); var isValidFormat = true;//todo: using RegularExpression if (!isValidFormat) throw new ArgumentException("email is not in a correct format"); var isRealDoamin = true;//todo: Code here that confirms whether domain exists. if (!isRealDoamin) throw new ArgumentException("email does not include a valid domain.") }
روش بالا هم جواب میدهد ولی بهتر است کلاس Exception سفارشی به عنوان مثال ValidationException برای این قضیه در نظر گرفته شود تا بتوان وهلههای صادر شده از این نوع را در لایههای بالاتر مدیریت کرد.
متد ValidateEmail با چندین خروجی
برای این منظور چندین راه حل پیش رو داریم.
با استفاده از پارامتر out:
public bool ValidateEmail(string email, out string message) { var valid = true; message = string.Empty; if (string.IsNullOrWhiteSpace(email)) { valid = false; message = "email is null."; } if (valid) { var isValidFormat = true;//todo: using RegularExpression if (!isValidFormat) { valid = false; message = "email is not in a correct format"; } } if (valid) { var isRealDoamin = true;//todo: Code here that confirms whether domain exists. if (!isRealDoamin) { valid = false; message = "email does not include a valid domain."; } } return valid; }
var email = "email@example.com"; var isValid = ValidateEmail(email, out string message); if (isValid) { //do something }
Tuple<bool, List<string>> result = Tuple.Create<bool, List<string>>(true, new List<string>());
public class OperationResult { public bool Success { get; set; } public IList<string> Messages { get; } = new List<string>(); public void AddMessage(string message) { Messages.Add(message); } }
public OperationResult ValidateEmail(string email) { var result = new OperationResult(); if (string.IsNullOrWhiteSpace(email)) { result.Success = false; result.AddMessage("email is null."); } if (result.Success) { var isValidFormat = true;//todo: using RegularExpression if (!isValidFormat) { result.Success = false; result.AddMessage("email is not in a correct format"); } } if (result.Success) { var isRealDoamin = true;//todo: Code here that confirms whether domain exists. if (!isRealDoamin) { result.Success = false; result.AddMessage("email does not include a valid domain."); } } return result; }
این بار خروجی متد مذکور از نوع OperationResult ای میباشد که هم موفقیت آمیز بودن یا عدم آن را مشخص میکند و همچنین امکان دسترسی به لیست پیغامهای مرتبط با اعتبارسنجیهای انجام شده، وجود دارد.
استفاده از Exception برای نمایش پیغام برای کاربر نهایی
با صدور یک استثناء و مدیریت سراسری آن در بالاترین (خارجی ترین) لایه و نمایش پیغام مرتبط با آن به کاربر نهایی، میتوان از آن به عنوان ابزاری برای ارسال هر نوع پیغامی به کاربر نهایی استفاده کرد. اگر قوانین تجاری با موفقیت برآورده نشدهاند یا لازم است به هر دلیلی یک پیغام مرتبط با یک اعتبارسنجی تجاری را برای کاربر نمایش دهید، این روش بسیار کارساز میباشد و با یکبار وقت گذاشتن برای توسعه زیرساخت برای این موضوع به عنوان یک Cross Cutting Concern تحت عنوان Exception Management آزادی عمل زیادی در ادامه توسعه سیستم خود خواهید داشت.
به عنوان مثال داشتن یک کلاس Exception سفارشی تحت عنوان UserFriendlyException در این راستا یک الزام میباشد.
[Serializable] public class UserFriendlyException : Exception { public string Details { get; private set; } public int Code { get; set; } public UserFriendlyException() { } public UserFriendlyException(SerializationInfo serializationInfo, StreamingContext context) : base(serializationInfo, context) { } public UserFriendlyException(string message) : base(message) { } public UserFriendlyException(int code, string message) : this(message) { Code = code; } public UserFriendlyException(string message, string details) : this(message) { Details = details; } public UserFriendlyException(int code, string message, string details) : this(message, details) { Code = code; } public UserFriendlyException(string message, Exception innerException) : base(message, innerException) { } public UserFriendlyException(string message, string details, Exception innerException) : this(message, innerException) { Details = details; } }
و همچنین لازم است در بالاترین لایه سیستم خود به عنوان مثال برای یک پروژه ASP.NET MVC یا ASP.NET Core MVC میتوان یک ExceptionFilter سفارشی نیز تهیه کرد که هم به صورت سراسری استثناءهای سفارشی شما را مدیریت کند و همچنین خروجی مناسب Json برای استفاده در سمت کلاینت را نیز مهیا کند. به عنوان مثال برای درخواستهای Ajax ای لازم است در سمت کلاینت نیز پاسخهای رسیده از سمت سرور به صورت سراسری مدیریت شوند و برای سایر درخواستها همان نمایش صفحات خطای پیغام مرتبط با استثناء رخ داده شده کفایت میکند.
یک مدل پیشنهادی برای تهیه خروجی مناسب برای ارسال جزئیات استثنا رخ داده در درخواستهای Ajax ای
[Serializable] public class MvcAjaxResponse : MvcAjaxResponse<object> { public MvcAjaxResponse() { } public MvcAjaxResponse(bool success) : base(success) { } public MvcAjaxResponse(object result) : base(result) { } public MvcAjaxResponse(ErrorInfo error, bool unAuthorizedRequest = false) : base(error, unAuthorizedRequest) { } } [Serializable] public class MvcAjaxResponse<TResult> : MvcAjaxResponseBase { public MvcAjaxResponse(TResult result) { Result = result; Success = true; } public MvcAjaxResponse() { Success = true; } public MvcAjaxResponse(bool success) { Success = success; } public MvcAjaxResponse(ErrorInfo error, bool unAuthorizedRequest = false) { Error = error; UnAuthorizedRequest = unAuthorizedRequest; Success = false; } /// <summary> /// The actual result object of AJAX request. /// It is set if <see cref="MvcAjaxResponseBase.Success" /> is true. /// </summary> public TResult Result { get; set; } } public class MvcAjaxResponseBase { public string TargetUrl { get; set; } public bool Success { get; set; } public ErrorInfo Error { get; set; } public bool UnAuthorizedRequest { get; set; } public bool __mvc { get; } = true; }
[Serializable] public class ErrorInfo { public int Code { get; set; } public string Message { get; set; } public string Detail { get; set; } public Dictionary<string, string> ValidationErrors { get; set; } public ErrorInfo() { } public ErrorInfo(string message) { Message = message; } public ErrorInfo(int code) { Code = code; } public ErrorInfo(int code, string message) : this(message) { Code = code; } public ErrorInfo(string message, string details) : this(message) { Detail = details; } public ErrorInfo(int code, string message, string details) : this(message, details) { Code = code; } }
public async Task CheckIsDeactiveAsync(long id) { if (await _organizationalUnits.AnyAsync(a => a.Id == id && !a.IsActive).ConfigureAwait(false)) throw new UserFriendlyException("واحد سازمانی جاری غیرفعال میباشد."); }
روش نام گذاری متدهایی که امکان بازگشت خروجی Null را دارند
public User GetById(long id);
[Serializable] public class EntityNotFoundException : Exception { public Type EntityType { get; set; } public object Id { get; set; } public EntityNotFoundException() { } public EntityNotFoundException(string message) : base(message) { } public EntityNotFoundException(string message, Exception innerException) : base(message, innerException) { } public EntityNotFoundException(SerializationInfo serializationInfo, StreamingContext context) : base(serializationInfo, context) { } public EntityNotFoundException(Type entityType, object id) : this(entityType, id, null) { } public EntityNotFoundException(Type entityType, object id, Exception innerException) : base($"There is no such an entity. Entity type: {entityType.FullName}, id: {id}", innerException) { EntityType = entityType; Id = id; } }
یک مثال واقعی
public async Task<UserOrganizationalUnitInfo> GetCurrentOrganizationalUnitInfoOrNullAsync(long userId) { return (await _setting.GetSettingValueForUserAsync( UserSettingNames.CurrentOrganizationalUnitInfo, userId).ConfigureAwait(false)) .FromJsonString<UserOrganizationalUnitInfo>(); }
NET Framework 4.6.1. منتشر شد
The .NET Framework 4.6.1 includes new features in the following areas:
-
Cryptography
-
ADO.NET
-
Windows Presentation Foundation (WPF)
-
Windows Workflow Foundation
-
Profiling
-
NGen
For more information on the .NET Framework 4.6.1, see the following topics:
-
The .NET Framework API diff (on GitHub)
Rust 1.69.0 منتشر شد
با توجه به فاکتورهای موجود در Defensive Coding، یکی از مواردی که کیفیت کد شما را تضمین خواهد کرد، استفاده کردن از (ACT) Automated Code Test میباشد. در این قسمت قصد داریم مزایای تست اتوماتیک و Unit Test را به عنوان یکی دیگر از ابعاد Defensive Coding ذکر کنیم.
به عنوان برنامه نویسی که در حال توسعه کد هستید و قابلیتهای جدیدی را به کد خود اضافه میکنید، باید کدی را که در آن تغییر ایجاد میکنید، مرتب تست کنید، تا بررسی کنید که آیا به هدف خود رسیدهاید و توانستهاید قابلیت جدیدی را در نرم افزار اضافه کنید. روشی که توسط اکثر برنامه نویسان انجام میشود به این صورت است که بهصورت متوالی برنامه را اجرا میکنند، اجرا میکنند، اجرا میکنند و باز هم اجرا میکنند، تا اینکه مطمئن شوند همه چیز درست است. بیشتر زمانیکه در این فرآیند صرف میشود، صرف کارهای تکراری میشود. این مسئله بدون شک برای بسیار از برنامه نویسان پیش آمده است و ممکن است بعضی از این برنامه نویسان به دنبال راه چارهای بوده باشند و بعضیها هم با انجام دادن این روش تست، هیچ مشکلی نداشته باشند. اما مسئلهای که غیر قابل چشم پوشی است، این است که این روش انرژی و زمان بسیار زیادی را از برنامه نویسان میگیرد؛ راه حل چیست؟ راه حل همان ACT میباشد.
ACT به معنی نوشتن کد، جهت تست قابلیتهای نرم افزار میباشد. به این معنی که شما جهت تست کد خود، یکسری کد مینویسید که این کدها وظیفه دارند کدهای جدیدی را که به نرم افزار خود اضافه کرده اید، تست کنند و اجرای آنها توسط زیرساختهای موجود (Test Frameworks) به صورت اتوماتیک انجام میشود.
حال قصد داریم اجزای ACT را که در شکل ذیل نمایش داده شدهاند، تشریح کنیم.
· structured: برای بیان این مسئله، از مفهوم AAA استفاده میشود. A اول به معنی Arrange اطلاعاتی است که برای تست مورد نیاز است. A دوم به معنی Act یا اجرای متد در حالت تست است و A سوم بمعنی Assert یا بررسی نتایج تست میباشد. این ساختار، ساختاری است که در ادامه برای ایجاد تستها از آن استفاده میکنیم.
· Self-documented: ساختار تست به گونهای است که خود مستند میباشد و با بررسی کلی ساختار آن میتوان به هدف تست پی برد.
· Automatic: با استفاده از Test Framework ها، فرآیند تست اتوماتیک میشود.
· Repeatable: یکی از مزیتهای ACT این است که میتوان آن را برای دفعات مکرر تکرار کرد.
· TARDIS: مخفف Time And Relative Dimension In Space میباشد؛ با توجه به این مسئله ACT از کد شما در میان زمان و فضا محافظت میکند. ACT عملکرد اصلی کد شما را در حال حاضر و در زمانی در آینده تایید میکند؛ زمانیکه کد شما در حال توسعه میباشید و هر لحظه قابلیتهای جدیدی به آن اضافه میشود، ACT تضمین میکند که این تغییرات، قابلیتهای موجود در سیستم را تحت تاثیر قرار نمیدهند. بنابراین ACT از کد شما در مقابل زمان و فضا محافظت میکند.
روشهای مختلفی برای انجام دادن ACT وجود دارند که در این مقاله بر روی Unit Test تمرکز خواهیم کرد. Unit Test یکسری تستها هستند که توسط برنامه نویس نوشته و اجرا میشود. هدف این روش این است که کد به قسمتهای کوچکی تقسیم شود و بررسی شود که این قسمتها آن گونه که انتظار میرود، عمل میکنند.
برای رسیدن به این هدف باید کد را به صورت متدهای Clean و Testable نوشت. این متدها قسمتهای مستقلی از کد هستند که میتوانند تست شوند. همان طور که در شکل زیر مشاهده میکنید، برای هر متد میتوان تستهای مختلفی نوشت و حالتهای مختلف مربوط به ورودیهای معتبر، ورودیهای نامعتبر و بروز Exception را تست کرد.
بسیاری از برنامه نویسان و مدیران پروژه درمقابل مسئله استفاده از Unit Test در توسعه نرم افزار حساسیتهای خاصی نشان میدهند. بسیاری از آنها اظهار میکنند که برای این کار زمان کافی نداریم و استفاده کردن از این روش برای ما هزینه بر میباشد. اما ما در جواب این دسته از افراد باید موارد زیر را که بیشتر هم بر جنبه زمانی تاکید دارند، بیان کنیم.
· Save time:
استفاده کردن از Unit Test از هدر رفتن زمان شما جلوگیری میکند. هر برنامه نویسی میداند که حتی چند خط کد ساده هم نیاز به تست و باز بینی دارد. بنابراین برنامه نویس مجبور است آن ماژول اصلی از نرم افزار را که چند خط کد در آن نوشته است، به گونه ای اجرا کرده و فرآیند بیزینسی این ماژول را برای سناریوهای مختلف، بصورت دستی تست کند. حال فرض کنید در ادامهی این کار، شخص برنامه نویس مجبور شود کد را بطور مرتب تغییر دهد. بنابراین در این حالت مجبور است این فرآیند را چندین و چند بار تکرار کند (نرم افزار را اجرا کند، به منوی X برود، فرم Y را باز کند، حالتهای مختلف را در فرم بررسی کند).
راه حلی که Unit Test برای حل این مشکل ارئه میدهد این است که برای انجام این فرآیند میتوان کد نوشت و آن را بارها و بارها اجرا کرد. وظیفهی Unit Test ها این است که اطلاعات مورد نیاز متد یا واحدی که میخواهند آن را تست کنند، فرآهم میآورند، متد را با اطلاعات فرآهم شده زیر تست میبرند و سپس نتایج بدست آمده را بررسی خواهند کرد. شما میتوانید در صورت تغییراتی در متدها یا واحدها، Unit Test را بارها و بارها برای تست عملکرد صحیح آن متد، بعد از تغییرات اجرا کنید. همان طور که میبینید تبدیل کردن این فرآیند دستی به یک فرآیند سیستمی و اتوماتیک میتواند در جلوگیری از هدر رفت زمان بسیار تاثیر گذار باشد.
· Find Bugs Faster:
با استفاده از Unit Test شما میتوانید فرآیند پیدا کردن خطاها را بسیار سریعتر انجام دهید. برای مثال فرض کنید که شما گزارش یکسری خطاها را در نرم افزار، دریافت کردهاید. به جای اینکه سعی کنید بصورت دستی، فرآیندها را در نرم افزار مرور کنید تا دوباره شرایط بروز خطا یا شرایطی را که خطا در آن رخ داده است، جهت درک دلیل خطا یا خطاها ایجاد کنید، با استفاده از Unit Test میتوانید به راحتی و در سریعترین زمان ممکن و بصورت اتوماتیک خطاها را پیدا کنید.
· Refactor Safely:
Unit Test به شما اجازه میدهد که به راحتی کد خود را Refactor کنید. فرض کنید که میخواهید کدی را که دارای یکسری پیچیدگیها میباشد و نگهداری و توسعه آن سخت است، Refactor کنید. بدون استفاده از Unit Test، این Refactor کردن دارای ریسک بسیار زیادی است و ممکن است منجر به بروز خطاهای زیادی شود؛ در حالیکه با استفاده از Unit Test، بعد از Refactor کردن کد، میتوان Test ها را اجرا کرده و از عدم وجود خطا در کدها به راحتی مطمئن شد.
· Enhance Your Value:
با نوشتن Unit Test برای کدهای خود میتوانید یک ارزش افزوده را به کدهای خود اضافه کنید. به دلیل اینکه نوشتن Unit Test ویژگی Self-documented کد شما را افزایش میدهد و به افرادی که در تیم هستند کمک میکنند Business نرم افزار را بهتر درک کنند.
· Minimize Interruptions:
داشتن مجموعهای مناسب از Unit Test ها باعث میشود تا Interrupt های ناخواسته در Code شما بوجود نیاید. برای مثال حالتی را در نظر بگیرید که بدلیل ورود دادههای ناخواسته، نرم افزار دچار خطا میشود. دراین وضعیت در صورتیکه از Unit Test استفاده شود، هندل کردن این شرایط ناخواسته و Interrupt، بسیار راحتتر خواهد بود.
همه چیز درباره الگوی طراحی Singleton
Singleton design pattern is one of the simplest design patterns: it involves only one class throughout the application which is responsible to instantiate itself, to make sure it creates not more than one instance; in the same time it provides a global point of access to that instance. In this case the same instance can be used from everywhere, being impossible to invoke directly the constructor each time.