۳۰ روز با TDD: روز پانزدهم - ساده همیشه به معنی واضح نیست قسمت دوم

داستان ۳۰ روز با TDD

سپتامبر سال گذشته آقای James Bender در وبلاگ‌های تلریک یک مجموعه نوشته منتشر کرد به نام ۳۰ روز با TDD. در ترجمه‌ای آزاد در وبلاگ آرایه، با هم در طول یک ماه با Test Driven Development آشنا می‌شویم. لینک سایر نوشته‌های این سری را در صفحه ۳۰ روز با TDD می‌توانید مشاهده کنید.

و اما پانزدهمین روز: ساده همیشه به معنی واضح نیست قسمت دوم

در پایان مطلب قبلی این سری نوشته‌ها گفتیم که در تست‌های نوشته شده مشکلی وجود دارد. در این مطلب که نسخه زبان انگلیسی آن را در این آدرس می‌توانید مطالعه کنید، به بیان و رفع مشکل می‌پردازیم.

مشکل کجاست؟

در روز چهاردهم این تست کیس را دریافت کردیم:

وقتی کاربری سفارشی به تعداد صفر می‌دهد، خطایی از جنس InvalidOrderException می‌بایست برگردد.

از تست کیس بالا به کد تست زیر رسیدیم:

[Test]
[ExpectedException(typeof(InvalidOrderException))]
public void WhenAUserAttemptsToOrderAnItemWithAQuantityOfZeroThrowInvalidOrderException()
{
	//Arrange
	var shoppingCart = new ShoppingCart();
	shoppingCart.Items.Add(new ShoppingCartItem { ItemId = Guid.NewGuid(), Quantity = 0 });
	var customerId = Guid.NewGuid();
	var expectedOrderId = Guid.NewGuid();

	Mock.Arrange(() => _orderDataService.Save(Arg.IsAny()))
		.Returns(expectedOrderId)
		.OccursNever();

	//Act
	_orderService.PlaceOrder(customerId, shoppingCart);

	//Assert
	Mock.Assert(_orderDataService);
}
}

تست pass‌ شد، اما هنوز یک مشکل وجود دارد. می‌خواهیم مطمئن شویم که متد PlaceOrder در کلاس OrderService یک InvalidOrderException ایجاد می‌کند و متد Save کلاس OrderDataService را فراخوانی نخواهد کرد. به نظر می‌رسد تست این اعتبارسنجی را انجام می‌دهد. ما به تست گفته‌ایم که انتظار InvalidOrderException داشته باشد و stub را طوری تعریف کرده‌ایم که متد Save فراخوانی نشود. تست pass می‌شود، پس مشکل کجاست؟

مشکل اینجاست که تست pass می‌شود، اما فراخوانی Mock.Assert هرگز اتفاق نمی‌افتد. باور نمی‌کنید؟ بیایید یک آزمایش انجام دهیم. بیایید فراخوانی OccursNever را به OccursOnce تغییر دهیم.

[Test]
[ExpectedException(typeof(InvalidOrderException))]
public void WhenAUserAttemptsToOrderAnItemWithAQuantityOfZeroThrowInvalidOrderException()
{
//Arrange
var shoppingCart = new ShoppingCart();
shoppingCart.Items.Add(new ShoppingCartItem { ItemId = Guid.NewGuid(), Quantity = 0 });
var customerId = Guid.NewGuid();
var expectedOrderId = Guid.NewGuid();

Mock.Arrange(() => _orderDataService.Save(Arg.IsAny()))
.Returns(expectedOrderId)
.OccursOnce();

//Act
_orderService.PlaceOrder(customerId, shoppingCart);

//Assert
Mock.Assert(_orderDataService);
}
}

اگر تست آنطوری که انتظار داریم اجرا شود باید fail شود.

به نظر می‌رسد تست fail نشد. مشخصاً Mock.Assert فراخوانی نمی‌شود.

مشخص شد که مشکل از NUnit است یا به عبارت بهتر از خاصیت ExceptedException در NUnit. این خاصیت به تست می‌گوید که کل کد تست را درون یک بلاک try/catch قرار دهد و یک نوع خاص exception را بگیرد. فراخوانی PlaceOrder در خط ۱۶ باعث بروز exception می‌شود. از آنجایی که ما این exception را handle نکرده‌ایم به سطح اجرا کننده تست (NUnit) می‌رود. به محض اینکه اجرا کننده تست exception ای که منتظرش بوده را دریافت کند تست را pass می‌کند و هیچ خط کد دیگری بعد از بروز exception را اجرا نمی‌کند.

در بسیاری از موارد استفاده از ExpectedException خوب است. ما حالا به موقعیتی برخورد کردیم که استفاده از این خاصیت خوب نیست پس باید به دنبال راه جایگزین و دستی‌تری باشیم. اولین چیزی که لازم است انجام دهم، حذف ExpectedException از کد است. سپس باید فراخوانی PlaceOrder را در یک try/catch قرار بدهم.

[Test]
public void WhenAUserAttemptsToOrderAnItemWithAQuantityOfZeroThrowInvalidOrderException()
{
	//Arrange
	var shoppingCart = new ShoppingCart();
	shoppingCart.Items.Add(new ShoppingCartItem { ItemId = Guid.NewGuid(), Quantity = 0 });
	var customerId = Guid.NewGuid();
	var expectedOrderId = Guid.NewGuid();

	Mock.Arrange(() => _orderDataService.Save(Arg.IsAny()))
		.Returns(expectedOrderId)
		.OccursOnce();

	//Act
	try
	{
		_orderService.PlaceOrder(customerId, shoppingCart);
	}
	catch(InvalidOrderException ex)
	{
		//Assert
		Mock.Assert(_orderDataService);
		Assert.Pass();
	}

	//Assert
	Assert.Fail();
}

برای تایید کد من نوع خاص InvalidOrderExpection را در catch متد PlaceOrder می‌گیرم. بعد از گرفتن این exception نوبت به Mock.Assert می‌رسد. اگر این کار کند، باید به اجرا کننده تست بگویم که تست pass شده است پس از Assert.Pass استفاده می‌کنم. اگر نوع دیگری از exception ایجاد شد یا کد خطا نداد Assert.Fail باعث fail شدن تست می‌شود.

تست را اجرا می‌کنیم تا ببینیم fail می‌شود یا خیر؟

این fail به این خاطر است که setup مربوط به stub ما انتظار دارد که متد Save ذقیقاً یک بار اجرا شود. این را عمداً اصلاح نکردم تا fail شدن تست را ببینید. حالا کد را تغییر می‌دهیم تا مطمئن شویم متد Save هرگز فراخوانی نمی‌شود:

[Test]
public void WhenAUserAttemptsToOrderAnItemWithAQuantityOfZeroThrowInvalidOrderException()
{
	//Arrange
	var shoppingCart = new ShoppingCart();
	shoppingCart.Items.Add(new ShoppingCartItem { ItemId = Guid.NewGuid(), Quantity = 0 });
	var customerId = Guid.NewGuid();
	var expectedOrderId = Guid.NewGuid();

	Mock.Arrange(() => _orderDataService.Save(Arg.IsAny()))
		.Returns(expectedOrderId)
		.OccursNever();

	//Act
	try
	{
		_orderService.PlaceOrder(customerId, shoppingCart);
	}
	catch(InvalidOrderException ex)
	{
		//Assert
		Mock.Assert(_orderDataService);
		Assert.Pass();
	}

	//Assert
	Assert.Fail();
}

تست را دوباره اجرا می‌کنیم و می‌بینیم تست pass می‌شود.

ادامه دارد...

به اشتراک گذاری این نوشته در شبکه‌های اجتماعی

۳۰ روز با TDD: روز چهاردهم - ساده همیشه به معنی واضح نیست قسمت اول

داستان ۳۰ روز با TDD

سپتامبر سال گذشته آقای James Bender در وبلاگ‌های تلریک یک مجموعه نوشته منتشر کرد به نام ۳۰ روز با TDD. در ترجمه‌ای آزاد در وبلاگ آرایه، با هم در طول یک ماه با Test Driven Development آشنا می‌شویم. لینک سایر نوشته‌های این سری را در صفحه ۳۰ روز با TDD می‌توانید مشاهده کنید.

و اما چهاردهمین روز: ساده همیشه به معنی واضح نیست

در نوشته‌های قبلی دیدیم که چطور با استفاده از stub ها کد کلاسی وابسته زیر تست را mock کردیم. نوشته انگلیسی روز چهاردهم را در این آدرس می‌توانید مطالعه کنید.

یک روز دیگر، یک تست دیگر

برای این نوشته نیز مثال فروشگاه مطالب قبلی را ادامه می‌دهیم. تست بعدی زمانی است که کاربر یک آیتم را سفارش می‌دهد و تعداد سفارش صفر است و InvalidOrderException باید throw شود.

در ظاهر این یک تست ساده به نظر می‌رسد: هر سفارش لیستی از آیتم‌ها دارد اگر تعداد هر یک از آیتم‌ها برابر صفر بود یک exception را throw‌ کن. در اغلب موارد انتظار داریم همراه exception یک پیغام هم برگردد که مشخص کند مشکل کجا بوده است، اما حالا برای ساده نگه داشتن مثال این best practice را نادیده می‌گیریم (در نوشته بعدی به آن خواهیم رسید) به نظر ساده می‌آید، این طور نیست؟ اجازه بدهید تست را بنویسیم

[Test]
[ExpectedException(typeof(InvalidOrderException))]
public void WhenAUserAttemptsToOrderAnItemWithAQuantityOfZeroThrowInvalidOrderException()
{
	//Arrange

	//Act

	//Assert
}

کد بالا ساده‌ترین حالت ممکن است. اگر سری نوشته‌های ۳۰ روز با TDD را از ابتدا دنبال کرده‌اید، چیز جدیدی اینجا نیست. تست خود را با ویژگی Test و همچنین ExpectedExpection معرفی می‌کنیم که به NUnit می‌گوید که انتظار یک exception از نوع InvalidOrderException داشته باشد. تعدادی کامنت هم قرار دادیم که یادآور الگوی AAA باشد و بخش‌های Arrange و Act‌ و Assert را مشخص کند. اغلب توسعه‌دهندگان TDD از بخش Arrange شروع می‌کنند که مشکلی هم برای شروع از این قسمت نیست. شخصاً تمایل دارم از بخش Assert شروع کرده و به عقب بروم. من دوست دارم ابتدا assert را بنویسم که می‌توانم از کلاس تست مشتقش کنم. با داشتن assert از ابتدا می‌دانم که چه چیزی را در بخش Act باید فراخوانی کنم تا نتیجه مورد انتظار تست را داشته باشم و با نوشتن Act در مرحله دوم می‌توانم مطمئن شوم که در قسمت Arrange تنها متغیرها، mock و stub هایی که برای تست در بخش Act نیاز است را تنظیم می‌کنم.

برای این تست یک stub برای OrderDataService نیاز دارم. از آنجایی که انتظار دارم در این تست یک exception ایجاد شود و نه اینکه یک سفارش ثبت شود پس توقع دارم که متد Save اصلاً صدا زده نشود (چون در صورت فراخوانی دردسر بزرگی خواهم داشت!)


[Test]
[ExpectedException(typeof(InvalidOrderException))]
public void WhenAUserAttemptsToOrderAnItemWithAQuantityOfZeroThrowInvalidOrderException()
{
	//Arrange

	//Act

	//Assert
	Mock.Assert(orderDataService);
}

تا اینجا همه چیز خوب است، حالا باید قسمت Act را بنویسیم

[Test]
[ExpectedException(typeof(InvalidOrderException))]
public void WhenAUserAttemptsToOrderAnItemWithAQuantityOfZeroThrowInvalidOrderException()
{
	//Arrange

	//Act
	orderService.PlaceOrder(customerId, shoppingCart);

	//Assert
	Mock.Assert(orderDataService);
}

در مثال قبلی، من مقدار متد PlaceOrder را گرفتم و در بخش Assert استفاده کردم. در این مثال یک exception انتظار دارم، پس نتیجه بیهوده است. حالا نوبت به قسمت Arrange می‌رسد. با نگاه به کد اولین چیزی که لازم دارم مشخص می‌شود یک وهله (instance) از OrderService است. تست دیگرم نیز از OrderService استفاده می‌کنم. من مایلم تست‌هایم را تا حد ممکن DRY یا Don't Repeat Yourself نگه دارم و دلیلی نیست که گام‌های ایجاد این instance مانند مطلب قبلی به یک setup method منتقل نشود

[TestFixture]
class OrderServiceTests
{
	private OrderService _orderService;

	[TestFixtureSetUp]
	public void SetupTestFixture()
	{
		_orderService = new OrderService();
	}

به نیمه راه رسیدیم. اگر به یاد داشته باشید OrderService یک وابستگی به OrderDataService دارد که باید برایش یک mock ایجاد کنم. خوشبختانه می‌توانم mock را به عنوان یک متغیر تعریف کرده و به سازنده (constructor) مربوط به OrderService در TestFixtureSetup تزریق کنم.


[TestFixture]
class OrderServiceTests
{
	private OrderService _orderService;
	private IOrderDataService _orderDataService;

	[TestFixtureSetUp]
	public void SetupTestFixture()
	{
		_orderDataService = Mock.Create();
		_orderService = new OrderService(_orderDataService);
	}

وقتی این کار انجام شد می‌توانم کد موجود را برای استفاده از این متغیرها به جای local instance ها refactor کنم.

[Test]
public void WhenUserPlacesACorrectOrderThenAnOrderNumberShouldBeReturned()
{
	//Arrange
	var shoppingCart = new ShoppingCart();
	shoppingCart.Items.Add(new ShoppingCartItem { ItemId = Guid.NewGuid(), Quantity = 1 });
	var customerId = Guid.NewGuid();
	var expectedOrderId = Guid.NewGuid();

	Mock.Arrange(() => _orderDataService.Save(Arg.IsAny()))
		.Returns(expectedOrderId)
		.OccursOnce();
	
	//Act
	var result = _orderService.PlaceOrder(customerId, shoppingCart);

	//Assert
	Assert.AreEqual(expectedOrderId, result);
	Mock.Assert(_orderDataService);
}

همانطور که مشاهده می‌کنید خطوط مربوط به ایجاد mock های OrderDataService و OrderService ‌را حذف کردم. اما بعد از refactor برای استفاده از متغیرهای جدید کد من خطای کامپایلی دریافت می‌کند.

ما به InvalidOrderException تا حالا نیاز نداشتیم. اما حالا که تستی داریم که exception ای از این نوع ایجاد می‌کند باید یکی بسازیم.

من دوست دارم که پروژه را به شدت مرتب نگه دارم. به عنوان بخشی از این نگاه، exception های سفارشی را در پوشه مخصوصی در پروژه نگه می‌دارم که باعث می‌شود در فضا نام (namespace) مربوط به خود هم قرار بگیرند. در پروژه TddStore.Core پوشه‌ای به نام Exceptions ایجاد می‌کنم. یک کلاس جدید به نام InvalidOrderException در این پوشه می‌سازم و فعلاً پیاده‌سازی‌ام برای این کلاس به سادگی کد زیر خواهد بود:


[Test]
using System;
 
namespace TddStore.Core.Exceptions
{
    public class InvalidOrderException : Exception
    {
        
    }
}

تنها لازم است از using برای اشاره به فضا نام TddStore.Core.Exceptions استفاده کنم. بعد از انجام این کار تست اصلی پاس می‌شود و بنابراین می‌توانم به تست جدید برگردم. اول کمی refactor برای کدهای موجود نیاز داریم سپس لازم است روی بخش Arrange کار کنیم. ابتدا باید متغیرها را برای فراخوانی PlaceOrder تنظیم کنیم پس من customerId و shoppingCart را تعریف می‌کنم. در حالی که روی این موضوع کار می‌کنم آیتمی را به shoppingCart با مقدار صفر اضافه می‌کنم

[Test]
[ExpectedException(typeof(InvalidOrderException))]
public void WhenAUserAttemptsToOrderAnItemWithAQuantityOfZeroThrowInvalidOrderException()
{
	//Arrange
	var shoppingCart = new ShoppingCart();
	shoppingCart.Items.Add(new ShoppingCartItem { ItemId = Guid.NewGuid(), Quantity = 0 });
	var customerId = Guid.NewGuid();
	var expectedOrderId = Guid.NewGuid();

	Mock.Arrange(() => _orderDataService.Save(Arg.IsAny()))
		.Returns(expectedOrderId)
		.OccursOnce();

	//Act
	_orderService.PlaceOrder(customerId, shoppingCart);

	//Assert
	Mock.Assert(_orderDataService);
}

سپس لازم است تا mock را آماده کنم

[Test]
[ExpectedException(typeof(InvalidOrderException))]
public void WhenAUserAttemptsToOrderAnItemWithAQuantityOfZeroThrowInvalidOrderException()
{
	//Arrange
	var shoppingCart = new ShoppingCart();
	shoppingCart.Items.Add(new ShoppingCartItem { ItemId = Guid.NewGuid(), Quantity = 0 });
	var customerId = Guid.NewGuid();
	var expectedOrderId = Guid.NewGuid();

	Mock.Arrange(() => _orderDataService.Save(Arg.IsAny()))
		.Returns(expectedOrderId)
		.OccursNever();

	//Act
	_orderService.PlaceOrder(customerId, shoppingCart);

	//Assert
	Mock.Assert(_orderDataService);
}

بر خلاف تست قبلی می‌خواهم مطمئن شوم که متد Save در OrderDataService فراخوانی نمی‌شود. همانطور که در نوشته قبلی این سری نوشته‌ها دیدید Just Mock‌ (محصولی از شرکت Telerik) امکان اجرای یک بار stub ای که exception ایجاد می‌کند را می‌دهد. بنابراین به صورت مشابه از متد OccursNever استفاده می‌کنم که بدان معنی است که این متد در حوزه تست فراخوانی نشود.

اجرای تست به دلیل پیاده‌سازی نشدن منطق برنامه با شکست مواجه می‌شود. بر اساس ایده اجرای ساده‌ترین راه حل ممکن، کد زیر را خواهیم داشت.

public Guid PlaceOrder(Guid customerId, ShoppingCart shoppingCart)
{
	foreach (var item in shoppingCart.Items)
	{
		if (item.Quantity == 0)
		{
			throw new InvalidOrderException();
		}
	}
	var order = new Order();
	return _orderDataService.Save(order);
}

با اجرای تست‌ها می‌بینم که هر دو تست Pass می‌شوند. 

کار من تمام شد!

آیا کار تمام شده است؟

در نوشته بعدی خواهیم دید که یک اشکال در تست‌ها به ما حس اطمینان اشتباهی می‌دهد.

ادامه دارد...

به اشتراک گذاری این نوشته در شبکه‌های اجتماعی

۳۰ روز با TDD: روز سیزدهم - ویژگی‌های بیشتر stub

داستان ۳۰ روز با TDD

سپتامبر سال گذشته آقای James Bender در وبلاگ‌های تلریک یک مجموعه نوشته منتشر کرد به نام ۳۰ روز با TDD. در ترجمه‌ای آزاد در وبلاگ آرایه، با هم در طول یک ماه با Test Driven Development آشنا می‌شویم. لینک سایر نوشته‌های این سری را در صفحه ۳۰ روز با TDD می‌توانید مشاهده کنید.

و اما سیزدهمین روز: ویژگی‌های بیشتر stub

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

زمان بازبینی کد

من دوست دارم به صورت متناوب با تیم برای بازبینی کدها جلسه داشته باشم. بعضی تیم‌ها زمان‌های برنامه‌ریزی شده برای این کار دارند و بعضی ندارند. یکی از چیزهایی که دوست دارم پایه اصلی جلسات بازبینی کد باشد، بخش‌های مهم برنامه هستند که یا پیجیده‌اند یا بخش‌های زیاد دیگری با آن‌ها در ارتباطند. بازبینی کد این بخش‌ها کمک می‌کند مطمئن شویم که مشکلات پیچیده به ساده‌ترین شکل ممکن هستند و همه تیم (حتی شده به صورت کلی) بدانند آن بخش‌های پیچیده و مهم چطور کار می‌کنند. 

زمان دیگری که وقت بازبینی کد است موقع ورود یک برنامه‌نویس جدید به تیم است. این برنامه‌نویس جدید ممکن است یک برنامه‌نویس تازه‌کار که اخیراً از دانشگاه آمده باشد یا خیلی ساده یک نفر که به تازگی وارد تیم شما شده است. من دوست دارم که چند task اولی که انجام دادند را بازبینی کنم تا مطمئن شوم درک درستی از مساله دارند و با بخش‌هایی از برنامه که تا به حال نوشته شده آشنا هستند و از استانداردهای کدنویسی ما معطلند. از آنجایی که خیلی از خوانندگان این سری نوشته در TDD تازه‌وارد هستند، به نظر می‌رسد زمان مناسبی برای بازبینی کدها باشد!

در مطلب قبلی ما کد زیر را برای متد PlaceOrder در کلاس OrderService  نوشتیم:

public Guid PlaceOrder(Guid customerId, ShoppingCart shoppingCart)
        {
            var order = new Order();
            return _orderDataService.Save(order);
        }

کد خیلی ساده‌ای است. برای اینکه تایید کنیم مطابق نیاز تجاری است یک تست داریم (که پیش از کد بالا ایجاد شده است)


[Test]
        public void WhenUserPlacesACorrectOrderThenAnOrderNumberShouldBeReturned()
        {
            //Arrange
            var shoppingCart = new ShoppingCart();
            shoppingCart.Items.Add(new ShoppingCartItem { ItemId = Guid.NewGuid(), Quantity = 1 });
            var customerId = Guid.NewGuid();
            var expectedOrderId = Guid.NewGuid();
 
            var orderDataService = Mock.Create();
            Mock.Arrange(() => orderDataService.Save(Arg.IsAny())).Returns(expectedOrderId);
            OrderService orderService = new OrderService(orderDataService);
 
            //Act
            var result = orderService.PlaceOrder(customerId, shoppingCart);
 
            //Assert
            Assert.AreEqual(expectedOrderId, result);
        }

تست ما برای تایید صحت کار مورد نیاز خوب به نظر می‌رسد. اما به عنوان یک معمار، یک نکته قابل تامل می‌بینم. در طی سالیان با برنامه‌های متعددی کار کردم و مشکلات کارآیی (performance) گاه و بیگاه را دیده‌ام. دلایل متعددی برای مشکلات performance وجود دارد، اما مشکل مشترکی که من دیدم، فراخوانی‌های متعدد غیرضروری به منابع خارجی مانند دیتابیس بوده است. فراخوانی دیتابیس به صورت نسبی کند است، به خصوص به نسبت زمانی که از حافظه یا mock استفاده می‌کنید. این باعث می‌شود که پیدا کردن این طور مشکلات از طریق آزمون واحد مشکل باشد و البته هیچ جریمه‌ مرتبط با performance ای هم در کار نیست چرا که از mock استفاده می‌کنیم. به عنوان معمار/برنامه‌نویس ارشد/هر عنوان دیگر پروژه می‌خواهم مطمئن شوم که Save در OrderDataService یک بار فراخوانی می‌شو. خوشبختانه JustMock امکانی برای این منظور دارد که stub چند بار فراخوانی شود.


[Test]
        public void WhenUserPlacesACorrectOrderThenAnOrderNumberShouldBeReturned()
        {
            //Arrange
            var shoppingCart = new ShoppingCart();
            shoppingCart.Items.Add(new ShoppingCartItem { ItemId = Guid.NewGuid(), Quantity = 1 });
            var customerId = Guid.NewGuid();
            var expectedOrderId = Guid.NewGuid();
 
            var orderDataService = Mock.Create();
            Mock.Arrange(() => orderDataService.Save(Arg.IsAny()))
                .Returns(expectedOrderId)
                .OccursOnce();
            OrderService orderService = new OrderService(orderDataService);
 
            //Act
            var result = orderService.PlaceOrder(customerId, shoppingCart);
 
            //Assert
            Assert.AreEqual(expectedOrderId, result);
            Mock.Assert(orderDataService);
        }

چند تغییر در تست اولیه ایجاد کردم. اول، بخش Arrangement در mock را به سه خط تقسیم کردم تا خواندن این مثال راحت‌تر باشد. همچنین متد OccursOnce را به انتهای arrangement اضافه کردم که به JustMock می‌گوید این متد دقیقاً یک بار باید اجرا شود و هر تعداد دیگر اجرا یک خطا خواهد بود. همچنین فراخوانی Mock.Assert را اضافه کردم. این فراخوانی به mock می‌گوید که قوانین تعریف شده (مثل اجرای یک بار متد) را تایید کند. در این مثال ما تنها یک قانون داریم که متد Save باید دقیقاً یک بار فراخوانی شود. اگر متد Save بیش از یک بار فراخوانی شود تست ما fail خواهد شد.

برای نشان دادن fail شدن من فراخوانی متد Save را در PlaceOrder کامنت می‌کنم و همچنین Assert.Equal را نیز کامنت می‌کنم تا مطئن شوم که تایید قوانین mock اولین fail است که پیدا می‌شود. وقتی تست را اجرا می‌کنم تست fail می‌شود و دلیل fail شدن یکسان نبودن تعداد دفعات فراخوانی با تعداد دفعات فراخوانی مورد انتظار است.

JustMock انتظار دارد که متد یک بار فراخوانی شود (محدوده مورد انتظار بین ۱ بار و ۱بار است) و همانطور که در نوشته‌های بعدی خواهید دید حد پایین و بالای تعداد فراخوانی‌ها را می‌توانیم تنظیم کنیم. چون از OccursOnce استفاده کردیم محدوده ما بین یک و یک است.

بعد از برداشتن کامنت‌ها اگر دوباره تست را اجرا کنیم تست pass خواهد شد.

ادامه دارد...

به اشتراک گذاری این نوشته در شبکه‌های اجتماعی

۳۰ روز با TDD: روز دوازدهم - کار با Stub ها

داستان ۳۰ روز با TDD

سپتامبر سال گذشته آقای James Bender در وبلاگ‌های تلریک یک مجموعه نوشته منتشر کرد به نام ۳۰ روز با TDD. در ترجمه‌ای آزاد در وبلاگ آرایه، با هم در طول یک ماه با Test Driven Development آشنا می‌شویم. لینک سایر نوشته‌های این سری را در صفحه ۳۰ روز با TDD می‌توانید مشاهده کنید.

و اما دوازدهمین روز: کار با Stubs

نوشته به زبان انگلیسی روز دوازدهم را در این آدرس می‌توانید مطالعه کنید. در نوشته قبلی این سری یک پروتوتایپ از اینکه PlaceOrder برای یک OrderService در یک برنامه e-commerce چطور باید باشد به شما نشان دادم. برای نمایش mocking ما یک نسخه کاربردی از این منطق تجاری (business logic) را با TDD پیاده‌سازی خواهیم کرد. این به آن معنی است که با یک نیازمندی تجاری شروع می‌کنیم:

تصور کنید که کاربر به برنامه وارد شده (login کرده است) و آیتم‌هایی را در سبد خرید قرار داده است. برنامه باید این قابلیت را به کاربر بدهد که بر اساس آیتم‌های داخل سبد خرید یک سفارش را ثبت کند. کاربران باید بتوانند هر تعداد دلخواه از آیتم‌ها را سفارش دهند. کاربران نباید تعداد صفر یا کمتر از صفر هر یک از آیتم‌ها را سفارش دهند. اگر کاربری تعداد صفر یا کمتر از صفر را برای محصولی انتحاب کند برنامه باید یک استثناء (Exception) ایجاد کند و کل سفارش باید لغو شود (بدون اینکه سبد خرید خالی شود) به محض اینکه اعتبارسنجی تعداد کالاها انجام شد، سفارش باید در دیتابیس از طریق سرویس مربوطه ذخیره شده و مشتری هم به آن مرتبط شود. فراخوانی‌های سیستم فاکتور و ارسال سفارش بر اساس گردش کار ‌آن‌ها باید انجام شود. یک رکورد لاگ باید ایجاد شود که مشخص کند سفارش ثبت شده است. اگر سفارش به هر دلیلی غیر از اعتبارسنجی تعداد کالای سفارش داده شده ثبت نشود باید خطا لاگ شده و exception ایجاد شود. بعد از ثبت موفق سفارش، سبد خرید خالی شده و شناسه سفارش (order id) باید از دیتابیس برگردانده شود.

این یک نیازمندی تجاری (business requirement) طولانی است. طبیعتاً باعث می‌شود که چندین unit test بنویسیم ولی اجازه بدهید با ساده‌ترینشان شروع کنیم:

وقتی کاربر تلاش می‌کند که کالایی با تعداد بزرگتر از صفر را سفارش دهد، شناسه سفارش باید برگردانده شود.

این یک مورد ساده و در عین حال یک شروع خوب است. چون این اولین نیازمندی و اولین تست است، من هیچ کد یا حتی solution ای در Visual Studio ندارم. برای شروع من یک Solution خالی در Visual Studio برای پروژه‌ام ایجاد می‌کنم که TddStore نام دارد. سپس یک پروژه به نام TddStore.UnitTests برای تست‌ها ایجاد می‌کنیم و از Nuget برای ایجاد رفرنس به NUnit در پروژه TddStore.UnitTests استفاده می‌کنم. نحوه رفرنس دادن را در روز سوم یاد گرفتیم. بعد از تکمیل این مراحل Solution من اینطور باید به نظر برسد

 کلاس Class1 را به OrderServiceTests تغییر نام می‌دهیم. از خواص (attribute) مربوط به NUnit برای نوشتن اولین تست استفاده می‌کنیم


using System;
using System.Linq;
using NUnit.Framework;
 
namespace TddStore.UnitTests
{
    [TestFixture]
    class OrderServiceTests
    {
        [Test]
        public void WhenUserPlacesACorrectOrderThenAnOrderNumberShouldBeReturned()
        {
 
        }
    }
}

در این مثال فرض می‌کنیم تیم دیگری نیز بر روی برنامه در حال کار است و پیاده‌سازی ShoppingCart و Order و همچنین Interface های OrderDataService و BillingService و FulfillmentService  و LoggingService را در یک پروژه class library انجام داده است. این پروژه که TddStore.Core نام دارد را با استفاده از این فایل‌ها می‌توانید ایجاد کنید. بعد از ایجاد پروژه TddStore.Core باید رفرنس آن را به پروژه TddStore.UnitTests اضافه کنید.

برای این تست من نیاز دارم که با فراخوانی متدی که باید پیاده‌سازی شود از کلاسی که باید پیاده‌سازی شود یک سفارش ثبت کنم. مطابق معمول گردش کار TDD ابتدا تست را می‌نویسیم:


 [Test]
        public void WhenUserPlacesACorrectOrderThenAnOrderNumberShouldBeReturned()
        {
            //Arrange
            var shoppingCart = new ShoppingCart();
            shoppingCart.Items.Add(new ShoppingCartItem { ItemId = Guid.NewGuid(), Quantity = 1 });
            var customerId = Guid.NewGuid();
            var expectedOrderId = Guid.NewGuid();
            var orderService = new OrderService();
 
            //Act
            var result = orderService.PlaceOrder(customerId, shoppingCart);
 
            //Assert
            Assert.AreEqual(expectedOrderId, result);
        }

فهمیدن کد این تست باید خیلی ساده باشد. در قسمت Arrange من یک وهله (instance) از سبد خرید ایجاد می‌کنم و به آن یک آیتم اضافه می‌کنم. همچنین یک سفارش و شناسه مشتری فرضی برای تستم ایجاد می‌کنم. در نهایت یک OrderService برای تست ایجاد می‌کنم.

دو بخش Act و Assert هم نیازی به توضیح خاصی ندارند و مراحل ثبت سفارش و دریافت شناسه مورد آزمون قرار می‌گیرند. در این مرحله من تست را اجرا می‌کنم تا fail شدنش را ببینم و سپس شروع به نوشتن ساده‌ترین کد ممکن برای pass شدن تست می‌کنم. در نهایت به این کد می‌رسم:

 

public class OrderService
{
    public object PlaceOrder(Guid customerId, ShoppingCart shoppingCart)
    {
         throw new NotImplementedException();
    }
}

بر اساس شرایط من باید رفرنس به OrderDataService ایجاد کنم. کد را refactor کرده و از طریق تزریق وابستگی با سازنده آن را تغییر می‌دهم

using System;
 
namespace TddStore.Core
{
    public class OrderService
    {
        private IOrderDataService _orderDataService;
 
        public OrderService(IOrderDataService orderDataService)
        {
            _orderDataService = orderDataService;
        }
 
        public object PlaceOrder(Guid customerId, ShoppingCart shoppingCart)
        {
            // TODO: Implement this method
            throw new NotImplementedException();
        }
    }
}

من به متد PlaceOrder دسترسی به instance ای از کلاس OrderDataService (از طریق اینترفیس IOrderDataService) می‌دهم ولی الان تست من دیگر کامپایل نمی‌شود. دلیل آن این است که سازنده (Constructor) پیش فرض OrderService دیگر وجود ندارد، نیاز است تا چیزی که IOrderDataService را پیاده‌سازی کرده به OrderService پاس بدهم

قدم بعدی

همان‌طور که در نوشته‌های قبلی اشاره شد از JustMock شرکت Telerik استفاده می‌کنیم. می‌توانید نسخه Lite آن را از طریق Nuget به پروژه اضافه کنید. 

حالا که به JustMock دسترسی داریم، یک stub برای IOrderDataService ایجاد می‌کنیم:


using System;
using System.Linq;
using NUnit.Framework;
using TddStore.Core;
using Telerik.JustMock;
namespace TddStore.UnitTests
{
  [TestFixture]
  class OrderServiceTests
  {
         [Test]
		public void WhenUserPlacesACorrectOrderThenAnOrderNumberShouldBeReturned()
        {
			//Arrange
            var shoppingCart = new ShoppingCart();
            shoppingCart.Items.Add(new ShoppingCartItem { ItemId = Guid.NewGuid(), Quantity = 1 });
            var customerId = Guid.NewGuid();
            var expectedOrderId = Guid.NewGuid();
 
            var orderDataService = Mock.Create();
            OrderService orderService = new OrderService(orderDataService);
  
			//Act
             var result = orderService.PlaceOrder(customerId, shoppingCart);
  
			//Assert
             Assert.AreEqual(expectedOrderId, result);
       }
     }
}

اولین مرحله ایجاد شی mock است. قبل از این کار باید با استفاده از using فضانام Telerik.JustMock را به کلاس اضافه کنید. در خط ۲۱ من یک شی mock شده از اینترفیس IOrderDataService ایجاد کردم. چون این شی IOrderDataService را پیاده‌سازی می‌کند می‌توانم به عنوان آرگومان سازنده آن را به OrderService پاس بدهم همانطور که در خط ۲۲ می‌بینید. در ادامه کمی کد به متد PlaceOrder در کلاس OrderService اضافه می‌کنیم تا از IOrderDataService استفاده کند.


public object PlaceOrder(Guid customerId, ShoppingCart shoppingCart)
        {
            var order = new Order();
            return _orderDataService.Save(order);
        }

بر اساس تست کیس ما تنها نیاز داریم که به متد تست یک سبد خرید با حداقل یک آیتم پاس بدهیم و یک شناسه سفارش تحویل بگیریم. در این تست کیس هیچ توضیحی درباره اینکه اگر اعتبارسنجی به مشکل بخورد یا اینکه چطور بر اساس سبد خرید یک سفارش بسازیم وجود ندارد. unit test جاری پاسخگوی تست کیس تعریف شده است.

ما هنوز (و البته همیشه) در فازی هستیم که ساده‌ترین کد ممکن برای پاس شدن تست‌ها را بنویسیم. در این مرحله شما احتمالاً چند سوال خواهید داشت. اعتبارسنجی چطور انجام می‌شود؟ چطور مطمئن شویم که وهله (instance) سفارشی که ساخته شده درست ساخته شده است؟ جواب هر دو این سوالات این است که برای انجام نیازمندی‌های جدید باید تست بیشتری بنوسیم. وقتی تست‌ها را نوشتیم حالا می‌توانیم کد بنویسیم. این مساله را در نوشته‌های بعدی این سری خواهیم دید.

در این مرحله تست‌ها را دوباره اجرا می‌کنیم. تست باید fail شود چرا که با وجود اینکه همه وابستگی‌های مورد نیاز را تامین کردم، هنوز شناسه سفارش مورد انتظار را دریافت نمی‌کنم.

گرچه یک شی mock شده برای OrderDataService ایجاد کردیم اما هنوز نگفتیم وقتی فراخوانی شد چه کار باید انجام دهد. با اشاره به لیست انواع mock ها که در نوشته قبلی توضیح داده شدند، آنچه الان داریم یک Dummy است. ما باید آن را به یک Stub واقعی ارتقاء دهیم.

[Test]
public void WhenUserPlacesACorrectOrderThenAnOrderNumberShouldBeReturned()
{
	//Arrange
	 var shoppingCart = new ShoppingCart();
	  shoppingCart.Items.Add(new ShoppingCartItem { ItemId = Guid.NewGuid(), Quantity = 1 });
	 var customerId = Guid.NewGuid();
	 var expectedOrderId = Guid.NewGuid();




	  var orderDataService = Mock.Create();
	 Mock.Arrange(() => orderDataService.Save(Arg.IsAny())).Returns(expectedOrderId);
	 OrderService orderService = new OrderService(orderDataService);




	//Act
	 var result = orderService.PlaceOrder(customerId, shoppingCart);




	//Assert
	 Assert.AreEqual(expectedOrderId, result);
}

در خط ۱۴ من از دستور Mock.Arrange برای setup کردن شی mock شده orderDataService استفاده می‌کنم که در واقع این شی را به یک stub تبدیل می‌کند. متد Arrange یک عبارت Linq می‌گیرد که مشخص می‌کند برای کدام متد می‌خواهم رفتار تعریف کنم. در این مثال من به stub می‌گویم که به فراخوانی متد Save پاسخ دهد. به عنوان بخشی از این عبارت Linq می‌توانم یک لیست پارامتر برای stub تعریف کنم. می‌توانم یک مقدار مشخص را تعیین کنم. به عنوان مثال، اگر متد Save یک int بگیرد، می‌توانم مشخص کنم فقط زمانی که مقدار ۴۲ پاس داده می‌شود پاسخ بررسی شود. اگر mock را با مقداری غیر ز ۴۲ فراخوانی کنم مقدار پیش فرض آن نوع داده بازگشتی (مثلاً صفر برای int) را برمی‌گرداند. به این دلیل بود که وقتی قبلاً تست‌ها را اجرا کردم شی mock شده orderDataService یک guid خالی (همه صفر) برگرداند. این موضوع که loose mocking نیز نامیده می‌شود در نوشته‌های بعدی مورد بررسی قرار خواهد گرفت.

در این مثال من یک وهله (instance) از شی Order را پاس می‌دهم و از Matcher استفاده می‌کنیم. Matcher در واقع روشی است که به یک arrangement بگوییم نگران مشخصات پارامترها نباشد. من فقط می‌خواهم یک رفتار برای پارامترها تعریف کنم که از یک الگوی خاص پیروی می‌کنند. در این مثال به JustMock می‌گویم که فقط پاس داده شن شی Order برایم مهم است. برایم مهم نیست از کجا می‌آید یا چه چیزی داخلش است. Matcher ها ابزار قدرتمندی در mocking هستند که در نوشته‌های بعدی به آن اشاره خواهم کرد. حالا اگر تست را دوباره اجرا کنم pass می‌شود.

ادامه دارد...

به اشتراک گذاری این نوشته در شبکه‌های اجتماعی

ساختار تیم‌های مدرن نرم‌افزاری قسمت پنجم: جلسات نرم‌افزاری

ساختار تیم‌های مدرن نرم‌افزاری

پیشتر در یک سری نوشته هفت قسمتی درباره مهارت‌های کار تیمی نرم‌افزار صحبت کردیم. در سری نوشته ساختار تیم‌های مدرن نرم‌افزاری یک گام جلوتر رفته و به جای مقدمات کار تیمی، درباره اصول کار تیمی در دنیای مدرن تولید نرم‌افزار صحبت می‌کنیم. سایر نوشته‌های این سری را در این صفحه می‌توانید ببینید. 

در نوشته قبلی این سری درباره مدیریت ارتباطات بین اعضای تیم گفتیم، این نوشته دنباله‌ای بر نوشته قبل با تکیه بر مبحث جلسات در یک تیم نرم‌افزاری است.

جلسات نرم‌افزاری: خوب، بد، زشت

پیشتر در نوشته‌ای به صورت مفصل در این مورد صحبت کردیم که چرا جلسات کاری نرم‌افزاری می‌توانند اتلاف وقت باشند و چه کار کنیم که نباشند؟ پیش از شروع صحبت درباره جلسات در تیم‌های مدرن نرم‌افزاری خوب است آن مطلب یک بار دیگر مرور شود. صرفنظر از راهکارهایی که در آن نوشته مطرح شده بود، یکی از مهمترین دلایل برگزاری جلسات نرم‌افزاری که به آن اشاره شده بود سنجش پیشرفت و شفافیت کارهاست.

جلسات نرم‌افزاری برگزار می‌شوند تا مطمئن شویم همه اعضای تیم درک مشابهی از کلیت کار دارند و جزئیات وظایف خود را به درستی فهمیده‌اند. این خوبی جلسات است. بدی آن اتلاف وقتی است که به همراه می‌آورند و زشتی آن جایی است که اثربخش نیستند!

جلسات نرم‌افزاری در تیم‌های مدرن

مجدداً نقل قولی که از موسس گیت‌هاب (تام پرستون) داشتیم را با هم نگاه کنیم:

"در گیت هاب ما جلسه نداریم، روز یا ساعت کاری مشخصی نداریم، دنبال ثبت و رهگیری روزهای مرخصی یا بیماری افراد نیستیم، مدیر یا چارت سازمانی نداریم"

این نگاه به کار به نظر شما چقدر واقعی است؟ چقدر از آن در ایران امکان‌پذیر است؟ واقعیت این است که حذف ۱۰۰ درصد جلسات امکان‌پذیر نیست اما شما می‌توانید با دو راهکار استفاده بهتری از جلسات ببرید:

۱- شفاف‌سازی از طریقی غیر از برگزاری جلسه
۲- برگزاری جلسه با دقت به عوامل بهبود بازدهی جلسه

در واقع تیم‌های مدرن نرم‌افزاری، به جای برگزاری جلسه، هدف آن یعنی سنجش پیشرفت پروژه یا شفاف‌سازی وظایف را از طریق دیگری محقق می‌کنند. اما چطور؟ دو راه ساده این کار را با هم در ادامه بررسی می‌کنیم که با استفاده آن‌ها در کنار هم می‌توانید بسیاری از جلسات غیرضروری را حذف کنید.

شفاف سازی از طریق تخته فیزیکی

یکی از کارهایی که صرفنظر از روش مدیریت پروژه خود و حتی بدون توجه به اصول اسکرام می‌توانید انجام دهید، استفاده از یک تخته فیزیکی و sticky note برای نوشتن وظایف جاری و تعیین وضعیت آن‌هاست. روی این تخته‌ها معمولاً سه ستون ToDo, Doing و Done وجود دارد که هر وظیفه بین ‌‌آن‌ها جابجا می‌شود.


اگر از این روش استفاده می‌کنید، یک گزینه دیگر هم دارید: استفاده از نرم‌افزارهای مشابه مثل TFS یا سرویس آنلاین آن که مزیت آن این است که می‌توانید علاوه بر عنوان و زمان اجرا توضیحات بیشتری هم درباره task بنویسید یا فایل مرتبطی را پیوست کنید و همچنین آرشیو یکپارچه‌ای از وظایف هر دوره و زمان اجرای آن‌ها داشته باشید.

شفاف سازی از طریق کد

در این روش Code Review به عنوان راهکاری برای اعلام ضمنی اتمام وظایف و همچنین سنجش کیفیت کار انجام شده استفاده می‌کنیم. از آنجایی که محصول نهایی یک تیم نرم‌افزاری یک نرم‌افزار است و نرم‌افزار مجموعه‌ای از کدهای به هم پیوسته، بنابراین با تعیین وضعیت کدها در هر مرحله در طی پروسه بازبینی کد، می‌توانید مطمئن شوید که پروژه تا میزان مورد انتظار شما پیشرفت داشته یا خیر؟ 

همچنین اگر برنامه‌نویس، وظیفه‌ای را به درستی درک نکرده باشد، چون در جریان code review یک نفر دیگر اجرای آن وظیفه را بررسی می‌کند، می‌تواند نقص اجرا را به برنامه‌نویس اصلی گزارش کند و بنابراین بسیاری از جلسات و پیگیری‌ها برای اطمینان از فهم درست وظایف حذف می‌شوند.

ادامه دارد...

به اشتراک گذاری این نوشته در شبکه‌های اجتماعی

چطور برای شیرپوینت وب پارت بنویسیم؟ قسمت اول: سلام دنیا

این نوشته اولین قسمت از مجموعه نوشته‌های مرتبط با برنامه‌نویسی وب‌پارت‌ها در شیرپوینت است. در طول چند ماه آینده ضمن تکمیل این سری نوشته، به تدریج به سراغ سایر حوزه‌های برنامه‌نویسی شیرپوینت نیز خواهیم رفت.

وب پارت شیرپوینت چیست؟

وب پارت‌ها یکی از راهکارهای توسعه شیرپوینت به روش برنامه‌نویسی هستند. یک وب پارت در حقیقت یک ماژول قابل استفاده مجدد است که می‌توان آن را بر حسب شرایط به شکل‌های مختلف تنظیم کرد و در صفحه شیرپوینتی قرار داد. یک مثال برای وب‌پارت‌ها، وب پارت نمایش وضعیت آب و هواست. می‌توان وب پارتی نوشت که با اتصال به سرویس پیش بینی وضعیت آب و هوا، دما و سایر ویژگی‌های مرتبط با وضعیت آب و هوا را نمایش دهد. این وب پارت را می‌توان در محل دلخواه در سایت شیرپوینتی قرار داد و اگر تنظیمات شهر را به آن افزوده باشیم می‌توانیم وب پارت را به گونه‌ای تنظیم کنیم که به عنوان مثال وضعیت آب و هوا تهران را نمایش دهد.
در نوشته‌های بعدی درباره انواع تنظیمات وب پارت و scope آن‌ها صحبت خواهیم کرد اما خوب است بدانید اگر کاربران دسترسی لازم را داشته باشند و تنظیمات قرار داده شده در وب پارت قابلیت ست شدن به ازای هر کاربر را داشته باشد، هر کاربر می‌تواند تنظیمات مخصوص به خودش را داشته باشد. در مثال وب پارت آب و هوا گرچه یک وب پارت نوشته شده اما یک کاربر می‌تواند آن را برای نمایش آب و هوای تهران و دیگری برای نمایش آب و هوا شیراز تنظیم کند.

وب پارت سلام دنیا

هدف این نوشته، آشنایی اولیه با مفاهیم برنامه‌نویسی وب پارت در شیرپوینت است و در نوشته‌های بعدی مباحث پیشرفته‌تر در خصوص تنظیمات و برندینگ وب پارت را مطرح خواهیم کرد.

برای نوشتن اولین وب پارتمان به Visual Studio 2012 یا بالاتر احتیاج داریم. مطابق تصویر زیر یک پروژه جدید از نوع SharePoint 2010 Project ایجاد می‌کنیم. در نوشته‌های بعدی درباره Visual Web Part و Silverlight Web Part توضیح خواهیم داد. فعلاً می‌خواهیم یک پروژه ساده داشته باشیم.

بعد از انتخاب نام و مسیر پروژه در مرحله بعد ویزارد ایجاد پروژه شیرپوینتی آدرس سایت شیرپوینتی را داده و گزینه Deploy as a farm Solution را انتخاب می‌کنیم. درباره راهکارهای sandbox در نوشته‌های بعدی توضیح داده خواهد شد.

حالا پروژه ما ایجاد شده است. در نگاه اول خیلی خالی به نظر می‌رسد. موقع آن رسیده که یک وب پارت به این پروژه شیرپوینتی اضافه کنیم.

برای اضافه کردن یک وب پارت به پروژه جدید روی پروژه کلیک راست کرده و یک New Item از نوع Web Part به پروژه اضافه می‌کنیم. همانطور که در تصویر زیر نیز مشخص است انواع دیگری مثل Visual Web Part‌ و Silverlight Web Part نیز وجود دارند که در نوشته‌های بعدی درباره آن‌ها صحبت خواهیم کرد. نام Web Part را HelloWorld انتخاب کنید.

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

ابتدا پوشه Features: در این پوشه یک Feature جدید تحت عنوان Feature1 اضافه شده است. راهکارهای شیرپوینتی برای نصب می‌توانند Feature داشته باشند که با توجه به scope آن از بخش تنظیمات سایت یا تنظیمات مجموعه سایت (site collection) یا تنظیمات Web Application یا حتی تنظیمات Farm قابل فعالسازی هستند.

برای این Feature ها حتی می‌توان کد نوشت تا مثلاً در هنگام فعالسازی Feature نیازمندی‌های پروژه به سایت اضافه شود به عنوان مثال هنگام فعال کردن Feature ضمن اضافه شدن وب پارت به مجموعه وب پارت‌های سایت، یک لیست هم ساخته شود یا تغییری در تنظیمات سایت شیرپوینتی ایجاد شود و ... در خصوص Feature ها نیز در نوشته‌های بعدی صحبت خواهیم کرد. فعلاً Feature1 را به HelloWorldFeature تغییر نام داده و روی آن کلیک می‌کنیم تا پنجره مشخصات Feature باز شود. در این پنجره می‌توانیم عنوان یا شرح Feature را بنویسیم و مشخص کنیم این Feature کدامیک از بخش‌های پروژه را فعال خواهد کرد. در مثال جاری Feature ما وب پارت HelloWorld را فعال می‌کند.

یک نکته دیگر درباره عنوان و شرح Feature این است که می‌توان آن را برای چند زبانه بودن تنظیم کرد. یعنی عنوان و شرح بر حسب زبان سایت متفاوت باشد که این موضوع را نیز در مباحث برندینگ در نوشته‌های بعدی توضیح خواهیم داد.

فایل Elements.xml: در این فایل یک ویژگی وجود دارد که می‌توانیم آن را برای بهتر شدن نمایش وب پارت تغییر دهیم. آن هم دسته بندی است که وب پارت در آن قرار می‌گیرد. در شیرپوینت وقتی می‌خواهیم به یک صفحه، یک وب پارت اضافه کنیم آن را از میان دسته‌بندی‌های موجود انتخاب می‌کنیم. به صورت پیش فرض دسته‌بندی یک وب پارت جدید روی Custom قرار دارد که آن را به مقدار دلخواه تغییر می‌دهیم. ما برای وب پارت‌های آرایه دسته‌بندی Araye را انتخاب می‌کنیم. دقت کنید که این بخش را نیز می‌توان از طریق resource ها چند زبانه کرد.



  
    
      
    
  





فایل HelloWebpart.webpart: در این فایل مشخصات نام و شرح و حتی آیکن وب پارت را می‌توانیم تعریف کنیم. این فایل نیز به صورت xml است و می‌توان محتویات آن را به صورت دلخواه ویرایش کرد و از طریق resource ها چند زبانه نمود. 



  
    
      
      $Resources:core,ImportErrorMessage;
    
    
      
        Araye - HelloWorld
        Araye HelloWorld Web Part
      
    
  


فایل کد وب پارت (فایل HelloWorld.cs): در این فایل کد برنامه‌نویسی #C وب پارت نوشته می‌شود. تمام کاری که باید انجام دهیم override کردن متد CreateChildControls و افزودن کد دلخواه به آن است. در این مثال ما کد وب پارت را به صورت زیر ویرایش می‌کنیم تا متن Hello World from SharePoint Webpart در آن نمایش داده شود.

using System;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
namespace Araye.SharePoint.Webparts.HelloWorld.HelloWorld
{
    [ToolboxItemAttribute(false)]
    public class HelloWorld : WebPart
    {
        protected override void CreateChildControls()
        {
            this.Controls.Add(new Label(){Text = "Hello World from SharePoint Webpart!"});
        }
    }
}




مشاهده خروجی: برای مشاهده خروجی دو راه دارید. Publish کردن کد که یک فایل wsp به شما خواهد داد و می‌توانید مانند سایر فایل‌های wsp آن را به شیرپوینت اضافه کنید و یا اجرا در حالت debug.

خب این نخستین وب پارت ما بود. به زودی کد کامل را در صفحه گیت‌هاب آرایه قرار خواهیم داد. همانطور که مشاهده می‌کنید وب پارت‌های عادی دارای محدودیت‌هایی هستند. مثلاً اگر بخواهید یک UI پیشرفته داشته باشید نوشتن کد و align کردن کنترل‌ها از طریق کد #C بسیار مشکل و زمانبر است. در نوشته‌های بعدی ضمن مرور قابلیت‌های تنظیماتی در وب پارت‌ها به سراغ Visual Webpart ها می‌رویم جایی که می‌توانیم کنترل‌ها را به راحتی به وب پارت اضافه کرده و محل و نحوه نمایش آن‌ها را کنترل کنیم.

ادامه دارد...

به اشتراک گذاری این نوشته در شبکه‌های اجتماعی