مطالب
پیاده سازی Template تو در تو در AngularJS و ASP.NET MVC
در Angular می شود یک سری Template و ساختار از پیش تعریف شده داشت و در هر زمان که نیاز بود مدلی را به آنها پاس داد و نمای HTML مورد نظر را تحویل گرفت.
بطور مثال در فرم ساز‌ها یا همان فرم‌های داینامیک ما نیاز داریم که مدل یک فرم (مثلا در فرمت JSON) را برای View ارسال کنیم و با استفاده از توانایی‌های Angular بتوانیم فرم مورد نظر را نمایش دهیم و در صورت امکان تغییر دهیم.
ViewModel فرم شما در MVC میتواند چیزی شبیه این باشد
   public class Form
    {
        public string Name { get; set; }
        public string Title { get; set; }
        public List<BaseElement> Elements { get; set; }
    }

    public abstract class BaseElement
    {
        public string Name { get; set; }
        public string Title { get; set; }
    }
    public class Section : BaseElement
    {
        public List<TextBox> Elements { get; set; }
    }
    public class TextBox : BaseElement
    {
        public string Value { get; set; }
        public string CssClass { get; set; }
    }
یک کنترلر هم برای مدیریت فرم ایجاد میکنیم
  public class FormBuilderController : Controller
    {
        //
        // GET: /FormBuilder/

        public ActionResult Index()
        {
            var form = new Form();
            var section = new Section() { Title = "Basic Info", Name = "section01" };
            section.Elements.Add(new TextBox() { Name = "txt1", Title = "First Text Box" });
            form.Elements.Add(new TextBox() { Name = "txt1", Title = "Second Text Box" });
            var formJson=JsonConvert.SerializeObject(form);
            return View(formJson);
        }
    }
در این کنترلر ما تنها یک اکشن داریم که در آن یک فرم خام ساده ایجاد کرده و سپس با استفاده از کتابخانه Json.net آنرا سریال و تبدیل به فرمت Json می‌کنیم و سپس آنرا برای View ایی که از Angular قدرت گرفته است، ارسال می‌نمائیم.
پیاده سازی View با Angular به اشکال گوناگونی قابل پیاده سازی و استفاده است که در اینجا و اینجا  می‌توانید ببینید.
 اما برای اینکه مشکل کنترلرهای تودرتو(Section) را حل کنید باید بصورت بازگشتی Template را فراخوانی کنید.
  <script type="text/ng-template" id="ElementTemplate">  
    <div ng-if="control.Type == 'JbSection'">
    <h2>{{control.Title}}</h2>
    <ul>
        <li ng-repeat="control in control.Elements" ng-include="'ElementTemplate'"></li>
    </ul>
    </div>
    </script>
و یا
<script type="text/ng-template" id="element.html">
    {{data.label}}
    <ul>
        <li ng-repeat="element in data.elements" ng-include="'element.html'"></li>
    </ul>
</script>

<ul ng-controller="NestedFormCtrl">
    <li ng-repeat="field in formData" ng-include="'element.html'"></li>
</ul>
در اینجا صفحه element.html یک صفحه بیرونی است که Template ما در آن قرار دارد.
نظرات مطالب
احراز هویت و اعتبارسنجی کاربران در برنامه‌های Angular - قسمت ششم - کار با منابع محافظت شده‌ی سمت سرور
یک نکته‌ی تکمیلی: روش دیگری برای بهبود کنترل نمایش و مخفی سازی قسمت‌های مختلف صفحه

کدهای یک دایرکتیو سفارشی نمایش و یا مخفی سازی قسمت‌های مختلف صفحه را بر اساس سطوح دسترسی کاربر جاری، در IsVisibleForAuthUserDirective مشاهده کردید. روش دیگر انجام اینکار، نوشتن یک دایرکتیو ساختاری شبیه به ngIf توکار خود Angular است. کاری که ngIf انجام می‌دهد، مخفی کردن یک المان در صفحه نیست؛ بلکه کل آن‌را از DOM حذف می‌کند.

نکته‌ی اصلی پیاده سازی یک دایرکتیو ساختاری

اگر به سازنده‌ی IsVisibleForAuthUserDirective دقت کنید، تزریق وابستگی ElementRef را داریم:
@Directive({
  selector: "[isVisibleForAuthUser]"
})
export class IsVisibleForAuthUserDirective implements OnInit, OnDestroy {
  constructor(private elem: ElementRef, private authService: AuthService) { }
به این ترتیب Angular به صورت خودکار امکان دسترسی به المان جاری را با تزریق آن در سازنده‌ی کلاس، میسر می‌کند.
برای ایجاد یک دایرکتیور ساختاری، نیاز است از تزریق TemplateRef و ViewContainerRef استفاده کرد:
@Directive({
selector: "[hasAuthUserViewPermission]"
})
export class HasAuthUserViewPermissionDirective implements OnInit, OnDestroy {

  constructor(
  private templateRef: TemplateRef<any>,
  private viewContainer: ViewContainerRef,
  private authService: AuthService
) { }
در این حالت Angular تنها زمانی این وابستگی‌ها را به سازنده‌ی کلاس تزریق می‌کند که پیش از نام این دایرکتیو، یک * قرار دهید (مانند ngIf توکار آن):
<div *hasAuthUserViewPermission="['Admin','User']">
    *hasAuthUserViewPermission="['Admin','User']"
</div>
پس از آن می‌توان از viewContainer، برای نمایش المان و یا قالب مدنظر:
 this.viewContainer.createEmbeddedView(this.templateRef);
و یا حذف کامل آن المان از DOM کمک گرفت:
 this.viewContainer.clear();

اگر این نکات را کنار هم قرار دهیم و کدهای IsVisibleForAuthUserDirective فوق را بر این اساس اصلاح کنیم، به قطعه کد زیر می‌رسیم:
import { Directive, Input, OnDestroy, OnInit, TemplateRef, ViewContainerRef } from "@angular/core";
import { Subscription } from "rxjs/Subscription";

import { AuthService } from "./../../core/services/auth.service";

@Directive({
  selector: "[hasAuthUserViewPermission]"
})
export class HasAuthUserViewPermissionDirective implements OnInit, OnDestroy {
  private isVisible = false;
  private requiredRoles: string[] | null = null;
  private subscription: Subscription | null = null;

  @Input()
  set hasAuthUserViewPermission(roles: string[] | null) {
    this.requiredRoles = roles;
  }

  // Note, if you don't place the * in front, you won't be able to inject the TemplateRef<any> or ViewContainerRef into your directive.
  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef,
    private authService: AuthService
  ) { }

  ngOnInit() {
    this.subscription = this.authService.authStatus$.subscribe(status => this.changeVisibility(status));
    this.changeVisibility(this.authService.isAuthUserLoggedIn());
  }

  ngOnDestroy(): void {
    if (this.subscription) {
      this.subscription.unsubscribe();
    }
  }

  private changeVisibility(status: boolean) {
    const isInRoles = !this.requiredRoles ? true : this.authService.isAuthUserInRoles(this.requiredRoles);
    if (isInRoles && status) {
      if (!this.isVisible) {
        this.viewContainer.createEmbeddedView(this.templateRef);
        this.isVisible = true;
      }
    } else {
      this.isVisible = false;
      this.viewContainer.clear();
    }
  }
}

سپس تعریف آن‌را به قسمت‌های declarations و exports مربوط به SharedModule اضافه می‌کنیم:
import { HasAuthUserViewPermissionDirective } from "./directives/has-auth-user-view-permission.directive";

@NgModule({
  declarations: [
    HasAuthUserViewPermissionDirective
  ],
  exports: [
    HasAuthUserViewPermissionDirective
  ]
})
export class SharedModule {}

اکنون ماژول Dashboard برای استفاده‌ی از این امکانات تنها کافی است SharedModule را دریافت کند (یا هر ماژول دیگری نیز به همین ترتیب است):
import { SharedModule } from "../shared/shared.module";
@NgModule({
  imports: [
    SharedModule
  ]
})
export class DashboardModule { }

پس از آن برای مخفی سازی یک المان از دید کاربران وارد نشده‌ی به سیستم، فقط کافی است دایرکتیو ساختاری hasAuthUserViewPermission را به المان اعمال کنیم:
<div *hasAuthUserViewPermission="">
    *hasAuthUserViewPermission=""
</div>
و یا اگر نیاز به اعمال نقش‌ها نیز وجود داشت می‌توان به صورت زیر عمل کرد:
<div *hasAuthUserViewPermission="['Admin','User']">
    *hasAuthUserViewPermission="['Admin','User']"
</div>

خلاصه‌ی این تغییرات به کدهای نهایی این سری اعمال شده‌اند.
مطالب
نحوه ایجاد یک تصویر امنیتی (Captcha) با حروف فارسی در ASP.Net MVC
در این مطلب، سعی خواهیم کرد تا همانند تصویر امنیتی این سایت که موقع ورود نمایش داده می‌شود، یک نمونه مشابه به آنرا در ASP.Net MVC ایجاد کنیم. ذکر این نکته ضروری است که قبلا آقای پایروند در یک مطلب دو قسمتی کاری مشابه را انجام داده بودند، اما در مطلبی که در اینجا ارائه شده سعی کرده ایم تا تفاوتهایی را با مطلب ایشان داشته باشد.

همان طور که ممکن است بدانید، اکشن متدها در کنترلرهای MVC می‌توانند انواع مختلفی را برگشت دهند که شرح آن در مطالب این سایت به مفصل گذشته است. یکی از این انواع، نوع ActionResult می‌باشد. این یک کلاس پایه برای انواع برگشتی توسط اکشن متدها مثل JsonResult، FileResult می‌باشد. (اطلاعات بیشتر را اینجا بخوانید) اما ممکن است مواقعی پیش بیاید که بخواهید نوعی را توسط یک اکشن متد برگشت دهید که به صورت توکار تعریف نشده باشد. مثلا زمانی را در نظر بگیرید که بخواهید یک تصویر امنیتی را برگشت دهید. یکی از راه حل‌های ممکن به این صورت است که کلاسی ایجاد شود که از کلاس پایه ActionResult ارث بری کرده باشد. بدین صورت:

using System;
using System.Web.Mvc;

namespace MVCPersianCaptcha.Models
{
    public class CaptchaImageResult : ActionResult 
    {
        public override void ExecuteResult(ControllerContext context)
        {
            throw new NotImplementedException();
        }
    }
}
همان طور که مشاهده می‌کنید، کلاسی به اسم CaptchaImageResult تعریف شده که از کلاس ActionResult ارث بری کرده است. در این صورت باید متد ExecuteResult را override کنید. متد ExecuteResult به صورت خودکار هنگامی که از CaptchaImageResult به عنوان یک نوع برگشتی اکشن متد استفاده شود اجرا می‌شود. به همین خاطر باید تصویر امنیتی توسط این متد تولید شود و به صورت جریان (stream)  برگشت داده شود

کدهای اولیه برای ایجاد یک تصویر امنیتی به صورت خیلی ساده از کلاس‌های فراهم شده توسط +GDI ، که در دات نت فریمورک وجود دارند استفاده خواهند کرد. برای این کار ابتدا یک شیء از کلاس Bitmap با دستور زیر ایجاد خواهیم کرد:
// Create a new 32-bit bitmap image.
Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
پارامترهای اول و دوم به ترتبی عرض و ارتفاع تصویر امنیتی را مشخص خواهند کرد و پارامتر سوم نیز فرمت تصویر را بیان کرده است. Format32bppArgb یعنی یک تصویر که هر کدام از پیکسل‌های آن 32 بیت فضا اشغال خواهند کرد ، 8 بیت اول میزان آلفا، 8 بیت دوم میزان رنگ قرمز، 8 بیت سوم میزان رنگ سبز، و 8 تای آخر نیز میزان رنگ آبی را مشخص خواهند کرد 

سپس شیئی از نوع Graphics برای انجام عملیات ترسیم نوشته‌های فارسی روی شیء bitmap ساخته می‌شود:
// Create a graphics object for drawing.
Graphics gfxCaptchaImage = Graphics.FromImage(bitmap);
خصوصیات مورد نیاز ما از gfxCaptchaImage را به صورت زیر مقداردهی می‌کنیم:
gfxCaptchaImage.PageUnit = GraphicsUnit.Pixel;
gfxCaptchaImage.SmoothingMode = SmoothingMode.HighQuality;
gfxCaptchaImage.Clear(Color.White);
واحد اندازه گیری به پیکسل، کیفیت تصویر تولید شده توسط دو دستور اول، و در دستور سوم ناحیه ترسیم با یک رنگ سفید پاک می‌شود.

سپس یک عدد اتفاقی بین 1000 و 9999 با دستور زیر تولید می‌شود:
// Create a Random Number from 1000 to 9999
int salt = CaptchaHelpers.CreateSalt();
متد CreateSalt در کلاس CaptchaHelpers قرار گرفته است، و نحوه پیاده سازی آن بدین صورت است:
public int CreateSalt()
{
   Random random = new Random();
   return random.Next(1000, 9999);
}
سپس مقدار موجود در salt را برای مقایسه با مقداری که کاربر وارد کرده است در session قرار می‌دهیم:
HttpContext.Current.Session["captchastring"] = salt;
سپس عدد اتفاقی تولید شده باید تبدیل به حروف شود، مثلا اگر عدد 4524 توسط متد CreateSalt تولید شده باشد، رشته "چهار هزار و پانصد و بیست و چهار" معادل آن نیز باید تولید شود. برای تبدیل عدد به حروف، آقای نصیری کلاس خیلی خوبی نوشته اند که چنین کاری را انجام می‌دهد. ما نیز از همین کلاس استفاده خواهیم کرد:
string randomString = (salt).NumberToText(Language.Persian);
در دستور بالا، متد الحاقی NumberToText با پارامتر Language.Persian وظیفه تبدیل عدد salt را به حروف فارسی معادل خواهد داشت.

به صورت پیش فرض نوشته‌های تصویر امنیتی به صورت چپ چین نوشته خواهند شد، و با توجه به این که نوشته ای که باید در تصویر امنیتی قرار بگیرد فارسی است، پس بهتر است آنرا به صورت راست به چپ در تصویر بنویسیم، بدین صورت:
// Set up the text format.
var format = new StringFormat();
int faLCID = new System.Globalization.CultureInfo("fa-IR").LCID;
format.SetDigitSubstitution(faLCID, StringDigitSubstitute.National);
format.Alignment = StringAlignment.Near;
format.LineAlignment = StringAlignment.Near;
format.FormatFlags = StringFormatFlags.DirectionRightToLeft;
و همچنین نوع و اندازه فونت که در این مثال tahoma می‌باشد:
// Font of Captcha and its size
Font font = new Font("Tahoma", 10);
خوب نوشته فارسی اتفاقی تولید شده آماده ترسیم شدن است، اما اگر چنین تصویری تولید شود احتمال خوانده شدن آن توسط روبات‌های پردازش گر تصویر شاید زیاد سخت نباشد. به همین دلیل باید کاری کنیم تا خواندن این تصویر برای این روبات‌ها سخت‌تر شود، روش‌های مختلفی برای این کار وجود دارند: مثل ایجاد نویز در تصویر امنیتی یا استفاده از توابع ریاضی سینوسی و کسینوسی برای نوشتن نوشته‌ها به صورت موج. برای این کار اول یک مسیر گرافیکی در تصویر یا موج اتفاقی ساخته شود و به شیء gfxCaptchaImage نسبت داده شود. برای این کار اول نمونه ای از روی کلاس GraphicsPath ساخته می‌شود،
// Create a path for text 
GraphicsPath path = new GraphicsPath();
و با استفاده از متد AddString ، رشته اتفاقی تولید شده را با فونت مشخص شده، و تنظیمات اندازه دربرگیرنده رشته مورد نظرر، و تنظیمات فرمت بندی رشته را لحاظ خواهیم کرد.
path.AddString(randomString, 
                font.FontFamily, 
                (int)font.Style, 
                (gfxCaptchaImage.DpiY * font.SizeInPoints / 72), 
                new Rectangle(0, 0, width, height), format);
با خط کد زیر شیء path را با رنگ بنقش با استفاده از شیء gfxCaptchaImage روی تصویر bitmap ترسیم خواهیم کرد:
gfxCaptchaImage.DrawPath(Pens.Navy, path);
برای ایجاد یک منحنی و موج از کدهای زیر استفاده خواهیم کرد:
//-- using a sin ware distort the image
int distortion = random.Next(-10, 10);
using (Bitmap copy = (Bitmap)bitmap.Clone())
{
          for (int y = 0; y < height; y++)
          {
              for (int x = 0; x < width; x++)
              {
                  int newX = (int)(x + (distortion * Math.Sin(Math.PI * y / 64.0)));
                  int newY = (int)(y + (distortion * Math.Cos(Math.PI * x / 64.0)));
                  if (newX < 0 || newX >= width) newX = 0;
                 if (newY < 0 || newY >= height) newY = 0;
                 bitmap.SetPixel(x, y, copy.GetPixel(newX, newY));
              }
         }
 }
موقع ترسیم تصویر امنیتی است:
//-- Draw the graphic to the bitmap
gfxCaptchaImage.DrawImage(bitmap, new Point(0, 0));

gfxCaptchaImage.Flush();
تصویر امنیتی به صورت یک تصویر با فرمت jpg به صورت جریان (stream) به مرورگر باید فرستاده شوند:
HttpResponseBase response = context.HttpContext.Response;
response.ContentType = "image/jpeg";
bitmap.Save(response.OutputStream, ImageFormat.Jpeg);
و در نهایت حافظه‌های اشغال شده توسط اشیاء فونت و گرافیک و تصویر امنیتی آزاد خواهند شد:
// Clean up.
font.Dispose();
gfxCaptchaImage.Dispose();
bitmap.Dispose();
برای استفاده از این کدها، اکشن متدی نوشته می‌شود که نوع CaptchaImageResult را برگشت می‌دهد:
public CaptchaImageResult CaptchaImage()
{
     return new CaptchaImageResult();
}
اگر در یک View خصیصه src یک تصویر به آدرس این اکشن متد مقداردهی شود، آنگاه تصویر امنیتی تولید شده نمایش پیدا می‌کند:
<img src="@Url.Action("CaptchaImage")"/>
بعد از پست کردن فرم مقدار text box تصویر امنیتی خوانده شده و با مقدار موجود در session مقایسه می‌شود، در صورتی که یکسان باشند، کاربر می‌تواند وارد سایت شود (در صورتی که نام کاربری یا کلمه عبور خود را درست وارد کرده باشد) یا اگر از این captcha در صفحات دیگری استفاده شود عمل مورد نظر می‌تواند انجام شود. در مثال زیر به طور ساده اگر کاربر در کادر متن مربوط به تصویر امنیتی مقدار درستی را وارد کرده باشد یا نه، پیغامی به او نشان داده می‌شود.  
[HttpPost]
public ActionResult Index(LogOnModel model)
{
      if (!ModelState.IsValid) return View(model);

      if (model.CaptchaInputText == Session["captchastring"].ToString()) 
             TempData["message"] = "تصویر امنتی را صحیح وارد کرده اید";
      else 
             TempData["message"] = "تصویر امنیتی را اشتباه وارد کرده اید";

      return View();
}

کدهای کامل مربوط به این مطلب را به همراه یک مثال از لینک زیر دریافت نمائید:
MVC-Persian-Captcha
مطالب
بررسی مدیریت دسترسی در جوملا 1.6-2.5

مطابق با ویکی پدیا، سطوح دسترسی مشخص می‌کند که کدام کاربران یا سیستم پردازش اجازه دسترسی به اشیاء را دارند(Authentication)، همچنین چه عملیات‌هایی بر روی اشیاء مجازند که اجرا شوند(Authorization).

در مورد جوملا، ما دو جنبه جدا برای سطوح دسترسی داریم:

1.       کدام کاربران به چه بخش‌هایی می‌توانند دسترسی داشته باشند؟ برای مثال، انتخاب یک منو برای کدام کاربر فعال خواهد بود؟

2.       چه عملیات (یا اقداماتی) کاربر می‌تواند بر روی اشیاء داشته باشد؟ برای مثال، آیا کاربر می‌تواند یک مطلب را ارسال یا ویرایش کند؟

 

ماهیت‌های موجود در سیستم :

·         کاربران 

کاربر می‌تواند به گروه‌های مختلفی اختصاص یابد.

·         گروه‌ها کاربری

شامل مجوزهایی به صورت پیش فرض می‌باشند که این مجوزها را از سطوح بالایی نیز به ارث می‌برند.

·         سطوح دسترسی

شامل یک یا چند گروه کاربری می‌باشد و سطوح دسترسی به محتواهای سایت نسبت داده می‌شود یعنی اگر یک مطلب دارای سطح دسترسی عمومی باشد آنگاه تمامی گروه‌های کاربری که در عمومی وجود دارند می‌توانند مطلب را مشاهده کنند.

·         عملیات و مجوزها

به صورت پیش فرض یک سری عملیات در سیستم تعریف شده است شامل ویرایش ، حذف و غیره که برای هر گروه کاربری (تعدادی گروه کاربری به صورت پیش فرض در سیستم تعریف شده است) به صورت پیش فرض مجوزهایی در نظر گرفته شده است که این مجوزها قابلیت ارث بری از والد گروه به فرزند رانیز دارا میباشد پس با این حساب همیشه در جوملا والد از سطح دسترسی پایین‌تری نسبت به فرزند برخوردار می‌باشد.

اما باید گفت در جوملا به ازای هر کامپوننت نیز می‌توان این مجوزها را به ازای گروه‌های مختلف تغییر داد در این جا هم هر کامپوننت دارای مجوز‌های پیش فرضی می‌باشد که در هنگام نصب کامپوننت برای آن  در نظر گرفته می‌شود.

 

جداول این سیستم :

: users  جدول کاربران

 usergroups : جدول گروه‌های کاری یا همان نقش‌های کاربری

user_usergroup_map :  جدول واسط بین کاربران و گروه‌های کاری به منظور ایجاد رابطه‌ی چند به چند (n:n)

assets : این جدول که از جوملا 1.6 به بعد به دیتابیس جوملا افزوده شده است مهمترین جدول در این سیستم می‌باشد . که در آن به ازای هر جز که سطح دسترسی باید برای آن لحاظ گردد یک سطر در نظر گرفته می‌شود که این سطر باتوجه به افزایش اجزا سیستم تغییر و به صورت داینامیک به جدول اضافه می‌گردد ضمنا این سطور قابلیت ارث بری از یکدیگر را نیز دارا می‌باشند. در هر بک از سطرها فیلدی به نام rulsوجود دارد محتوای این فیلد از نوع داده ای json می‌باشد با یک مثال شاید بهتر بتوان توضیح داد :

محتوای فیلد کامپوننت بنر :

{"core.admin":{"9":1,"7":1},"core.manage":{"6":1},"core.create":[],"core.delete":[],"core.edit}

در این جا “core.admin”   مجوز دسترسی مدیریتی به این کامپوننت می‌باشد که گروه‌های کاری شماره 7 و 9 دارای چنین دسترسی می‌باشند . ضمنا عملیات‌های "core.create" از سطوح بالاتر یا همان سطر والد خود ارث بری می‌کند.

Viewlevel :  در این جدول سطوح دسترسی تعریف شده اند مهمترین فیلد این جدول نیز rulsنام دارد و حاوی id  گروه هایی است که به این سطح دسترسی ، دسترسی دارند.

به طور مثال سطح دسترسی ثبت نام شده حاوی [6,2,8] می‌باشد یعنی گروه‌های کاری با id‌های مورد نظر می‌توانند به محتواهای با سطح دسترسی ثبت نام شده دسترسی داشته باشند.

 

دیاگرام جداول :

مطالب
آماده سازی Jasmine برای پروژه های Asp.Net MVC
با گسترش روز افزون برنامه‌های تحت وب، نیاز به یک سری ابزار برای تست و اطمینان از نحوه عملکرد صحیح کدهای نوشته شده احساس می‌شود. Jasmine یکی از این ابزار‌های قدرتمند برای تست کد‌های JavaScript است.
چندی پیش در سایت جاری چند مقاله خوب توسط یکی از دوستان درباره Qunit منتشر شد. Qunit یک ابزار قدرتمند و مناسب برای تست کد‌های جاوااسکریپت است و در اثبات صحت این گفته همین کافیست که بدانیم برای تست کد‌های نوشته شده در پروژه‌های متن بازی هم چون Backbone.Js و JQuery از این فریم ورک استفاده شده است. اما به احتمال قوی در ذهن شما این سوال مطرح شده است که خب! در صورت آشنایی با Qunit چه نیاز به یادگیری Jasmine یا خدای نکرده Mocha و FuncUnit است؟ هدف صرفا معرفی یک ابزار غیر برای تست کد است نه مقایسه و نتیجه گیری برای تعیین میزان برتری این ابزارها. اصولا مهم‌ترین دلیل برای انتخاب، علاوه بر امکانات و انعطاف پذیری، فاکتور راحتی و آسان بودن در هنگام استفاده است که به صورت مستقیم به شما و تیم توسعه نرم افزار بستگی دارد.

اما به عنوان توسعه دهنده نرم افزار که قرار است از این ابزار استفاده کنیم بهتر است با تفاوت‌ها و شباهت‌های مهم این دو فریم ورک آشنا باشیم:

»Jasmine یک فریم ورک تست کدهای جاوا اسکریپ بر مبنای Behavior-Driven Development است در حالی که Qunit بر مبنای Test-Driven Development است و همین مسئله مهم‌ترین تفاوت بین این دو فریم ورک می‌باشد.
»اگر قصد دارید که از Qunit نیز به روش BDD استفاده نمایید باید از ترکیب Pavlov به همراه Qunit استفاده کنید.
»Jasmine از مباحث مربوط به Spies و Mocking به خوبی پشتیبانی می‌کند ولی این امکان به صورت توکار در Qunit فراهم نیست. برای اینکه بتوانیم این مفاهیم را در Qunit پیاده سازی کنیم باید از فریم ورک‌های دیگر نظیر SinonJS به همراه Qunit استفاده کنیم.
»هر دو فریم ورک بالا به سادگی و راحتی کار معروف هستند 
»تمام موارد مربوط به الگوهای Matching در هر دو فریم ورک به خوبی تعبیه شده است
» هر دو فریم ورک بالا از مباحث مربوط به Asynchronous Testing  برای تست کد‌های Ajax ای به خوبی پشتیبانی می‌کنند.

بررسی چند مفهوم
قبل از شروع، بهتر است که با چند مفهوم کلی و در عین حال مهم این فریم ورک آشنا شویم
describe('JavaScript addition operator', function () {
 it('adds two numbers together', function () {
  expect(1 + 2).toEqual(3);
 });
});


در کد بالا یک نمونه از تست نوشته شده با استفاده از Jasmine را مشاهده می‌کنید. دستور describe  برای تعریف یک تابع تست مورد استفاده قرار می‌گیرد که دارای دو پارامتر ورودی است. ابتدا یک نام را به این تست اختصاص دهید(بهتر است که این عنوان به صورت یک جمله قابل فهم باشد). سپس یک تابع به عنوان بدنه تست نوشته می‌شود. به این تابع Spec گفته می‌شود.
در تابع it کد بالا شما می‌توانید کد‌های مربوط بدنه توابع تست خود را بنویسید. برای پیاده سازی Assert در توابع تست مفهوم expectation‌ها وجود دارد. در واقع expect برای بررسی مقادیر حقیقی با مقادیر مورد انتظار مورد استفاده قرار می‌گیرد و شامل مقادیر true یا false خواهد بود.
برای Setup و Teardown توابع تست خود باید از توابع beforeEach و afterEach که بدین منظور تعبیه شده اند استفاده کنید.
describe("A spec (with setup and tear-down)", function() {
  var foo;

  beforeEach(function() {
    foo = 0;
    foo += 1;
  });

  afterEach(function() {
    foo = 0;
  });

  it("is just a function, so it can contain any code", function() {
    expect(foo).toEqual(1);
  });

  it("can have more than one expectation", function() {
    expect(foo).toEqual(1);
    expect(true).toEqual(true);
  });
});
کاملا واضح است که در تابع beforeEach مجموعه دستورالعمل‌های مربوط به setup تست وجود دارد. سپس دو تابع it برای پیاده سازی عملیات Assertion نوشته شده است. در پایان هم دستورات تابع afterEach ایجاد می‌شوند.

اگر در کد تست خود قصد دارید که یک تابع describe یا it را غیر فعال کنید کافیست یک x به ابتدای آن‌ها اضافه کنید و دیگر نیاز به هیچ کار اضافه دیگری برای comment کردن کد نیست.
xdescribe("A spec", function() {
  var foo;

  beforeEach(function() {
    foo = 0;
    foo += 1;
  });

  xit("is just a function, so it can contain any code", function() {
    expect(foo).toEqual(1);
  });
});
توابع describe و it بالا در هنگام تست نادیده گرفته می‌شوند و خروجی آن‌ها مشاهده نخواهد شد.

درادامه قصد پیاده سازی یک مثال  را با استفاده از Jasmine و RequireJs در پروژه Asp.Net MVC دارم.
برای شروع آخرین نسخه Jasmine را از اینجا دریافت نمایید. یک پروژه Asp.Net MVC به همراه پروژه تست به صورت Empty ایجاد کنید(در هنگام ایجاد پروژه، گزینه create unit test را انتخاب نمایید). فایل دانلود شده را unzip نمایید و دو پوشه lib و spec ،به همراه فایل specRunner.html را در پروژه تست خود کپی نمایید. 
  • فولدر lib شامل فایل‌ها کد‌های Jasmine برای setup و tear down و spice و تست کد‌های شما می‌باشد.
  • فایل specRunner.html به واقع یک فایل برای نمایش فایل‌های تست و همچنین نمایش نتیجه تست است.
  • فولدر spec نیز شامل کد‌های Jasmine برای کمک به نوشتن تست می‌باشد.

در این مثال قصد داریم فایل‌های player.js و song.js که به عنوان نمونه به همراه این فریم ورک قرار دارد را در قالب یک پروژه MVC به همراه RequireJs، تست نماییم. در نتیجه این فایل‌ها را از فولدر src انتخاب نمایید و آن‌ها را در قسمت Scripts پروژه اصلی خود کپی کنید(ابتدا بک پوشه به نام App بسازید و فایل‌ها را در آن قرار دهید)

برای استفاده از requireJs باید دستور define را در ابتدا این فایل‌ها اضافه نماییم. در نتیجه فایل‌های Player.js و Song.js را باز کنید و تغییرات زیر را در ابتدای این فایل‌ها اعمال نمایید.

Song.js

define(function () {
    function Song() {
    }

    Song.prototype.persistFavoriteStatus = function (value) {
        // something complicated
        throw new Error("not yet implemented");
    };
});
Player.js
define(function () {
    function Player() {
    }
    Player.prototype.play = function (song) {
        this.currentlyPlayingSong = song;
        this.isPlaying = true;
    };

    Player.prototype.pause = function () {
        this.isPlaying = false;
    };

    Player.prototype.resume = function () {
        if (this.isPlaying) {
            throw new Error("song is already playing");
        }

        this.isPlaying = true;
    };

    Player.prototype.makeFavorite = function () {
        this.currentlyPlayingSong.persistFavoriteStatus(true);
    };
});
حال فایل SpecRunner.html را بازکنید و کد‌های مربوط به تگ script که به مسیر اصلی فایل‌های تست  اشاره می‌کند را Comment نمایید و به جای آن تگ Script مربوط به RequireJs را اضافه نمایید. برای پیکر بندی RequireJs باید از baseUrl و paths استفاده کرد.

baseUrl در پیکر بندی requireJs به مسیر فایل‌های پروژه که در پروژه اصلی MVC قرار دارد اشاره می‌کند. paths برای تعیین مسیر فایل‌های تست که در پوشه spec در پروژه تست قرار دارد اشاره می‌کند. اگر دقت کرده باشید به دلیل اینگه تگ‌های script مربوط به لود فایل‌های SpecHelper.js و PlayerSpec.js به صورت comment در آمده اند در نتیجه این فایل‌ها لود نخواهند شد و خروجی مورد نظر مشاهده نمی‌شود. در این جا باید از مکانیزم AMD موجود در RequireJs استفاده نماییم و فایل‌های مربوطه را لود کنیم. برای این کار نیاز به اضافه کردن دستور require در ابتدای تگ script به صورت زیر در این فایل است. در نتیجه فایل‌های PlayerSpec و SpecHelper نیز توسط RequireJs لود خواهند شد.



نیاز به یک تغییر کوچک دیگر نیز وجود دارد. فایل PlayerSpec را باز نمایید و وابستگی فایل‌های آن را تعیین نمایید. از آن جا که این فایل برای تست فایل‌های Player , Song ایجاد شده است در نتیجه باید از define برای تعیین این وابستگی‌ها استفاده نماییم.

یادآوری:

»دستور describe در فایل بالا برای تعریف تابع تست است. همان طور که می‌بینید بک نام به آن داده می‌شود به همراه بدنه تابع تست. 

»دستور beforeEach برای آماده سازی مواردی است که قصد داریم در تست مورد استفاده قرار گیرند. همانند متد‌های Setup در UnitTest.

» دستور expect نیز معادل Assert در UnitTest است و برای بررسی صحت عملکرد تست نوشته می‌شود.

اگر فایل SpecRunner.html را دوباره در مرورگر خود باز نمایید تصویر زیر را مشاهده خواهید کرد که به عنوان موفقیت آمیز بودن پیکر بندی پروژه و تست‌های آن می‌باشد.


 

 
اشتراک‌ها
Angular v13 منتشر شد
Angular v13 is now Available. We’re back with the brand new release… | by Mark Thompson (@marktechson) | Nov, 2021 | Angular Blog
Angular v13 منتشر شد