۱۳۸۷/۱۰/۲۱

آشنايي با آزمايش واحد (unit testing) در دات نت، قسمت 6


ادامه آشنايي با NUnit

فرض كنيد يك RSS reader نوشته‌ايد كه فيدهاي فارسي و انگليسي را دريافت مي‌كند. به صورت پيش فرض هم مشخص نيست كه كدام فيد اطلاعات فارسي را ارائه خواهد داد و كداميك انگليسي. تشخيص محتواي فارسي و از راست به چپ نشان دادن خودكار مطالب ‌آن‌ها به عهده‌ي برنامه نويس است. بهترين روش براي تشخيص اين نوع الگوها، استفاده از regular expressions است.
براي مثال الگوي تشخيص اينكه آيا متن ما حاوي حروف انگليسي است يا خير به صورت زير است:

[a-zA-Z]

كه بيان بصري آن به اين شكل است.
در مورد تشخيص وجود حروف فارسي در يك عبارت، يكي از دو الگوي زير بكار مي‌رود:

[\u0600-\u06FF]
[ا-یءئ]

در مورد اينكه بازه يونيكد فارسي استاندارد از كجا شروع مي‌شود مي‌توان به مقاله‌ي آقاي حاج‌لو مراجعه نمود (به صورت خلاصه، بازه مصوب عربي يونيكد، همان بازه يونيكد فارسي نيز مي‌باشد. يا به بيان بهتر، بازه‌ي فارسي، جزئي از بازه‌اي است كه عربي نام گرفته است). البته بازه‌ي مصوب ديگري هم در مورد ايران باستان وجود دارد به نام old Persian كه مورد استفاده‌ي روزمره‌اي ندارد!

كلاس زير را در مورد استفاده از اين الگوها تهيه كرده‌ايم:

using System.Text.RegularExpressions;

namespace sample
{
public static class CDetectFarsi
{
public static bool ContainsFarsiData(this string txt)
{
return !string.IsNullOrEmpty(txt) &&
Regex.IsMatch(txt, "[ا-یءئ]");
}

public static bool ContainsFarsi(this string txt)
{
return !string.IsNullOrEmpty(txt) &&
Regex.IsMatch(txt, @"[\u0600-\u06FF]");
}
}
}

همانطور كه ملاحظه مي‌كنيد در اينجا از extension methods سي شارپ 3 جهت توسعه كلاس پايه string استفاده شد.
اكنون مي‌خواهيم بررسي كنيم آيا اين الگوها مقصود ما را برآورده مي‌سازند يا خير.

using NUnit.Framework;
using sample;

namespace TestLibrary
{
[TestFixture]
public class TestFarsiClass
{
/*******************************************************************************/
[Test]
public void TestContainsFarsi1()
{
Assert.IsTrue("وحيد".ContainsFarsi());
}

[Test]
public void TestContainsFarsi2()
{
Assert.IsTrue("گردان".ContainsFarsi());
}

[Test]
public void TestContainsFarsi3()
{
Assert.IsTrue("سپيدTest".ContainsFarsi());
}

[Test]
public void TestContainsFarsi4()
{
Assert.IsTrue("123بررسي456".ContainsFarsi());
}

[Test]
public void TestContainsFarsi5()
{
Assert.IsFalse("Book".ContainsFarsi());
}

[Test]
public void TestContainsFarsi6()
{
Assert.IsTrue("۱۳۸۷".ContainsFarsi());
}

[Test]
public void TestContainsFarsi7()
{
Assert.IsFalse("Здравствуйте!".ContainsFarsi()); //Russian hello!
}


/*******************************************************************************/
[Test]
public void TestContainsFarsiData1()
{
Assert.IsTrue("وحيد".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData2()
{
Assert.IsTrue("گردان".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData3()
{
Assert.IsTrue("سپيدTest".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData4()
{
Assert.IsTrue("123بررسي456".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData5()
{
Assert.IsFalse("Book".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData6()
{
Assert.IsTrue("۱۳۸۷".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData7()
{
Assert.IsFalse("Здравствуйте!".ContainsFarsiData()); //Russian hello!
}
}
}

در كلاس فوق هر دو متد را با آزمايش‌هاي واحد مختلفي بررسي كرده‌ايم، انواع و اقسام حروف فارسي، تركيبي از فارسي و انگليسي، تركيبي از فارسي و اعداد انگليسي، عبارت كاملا انگليسي، عدد كاملا فارسي و يك عبارت روسي! (در يك كلاس عمومي با متدهاي عمومي بدون آرگومان از نوع void)
كلاس CDetectFarsi در برنامه اصلي قرار دارد و كلاس TestFarsiClass در يك پروژه class library ديگر قرار گرفته است (در اين مورد و جدا سازي آزمايش‌ها از پروژه اصلي در قسمت‌هاي قبل بحث شد)
همچنين به ازاي هر عبارت Assert يك متد ايجاد گرديد تا شكست يكي، سبب اختلال در بررسي ساير موارد نشود.
نتيجه اجراي اين آزمايش واحد با استفاده از امكانات مجتمع افزونه ReSharper به صورت زير است:



منهاي يك مورد، ساير آزمايشات ما با موفقيت انجام شده‌اند. موردي كه با شكست مواجه شده، بررسي اعداد كاملا فارسي است كه البته در الگوي دوم لحاظ نشده است و انتظار هم نمي‌رود كه آن‌را به اين شكل پشتيباني كند.
براي اينكه در حين اجراي آزمايشات بعدي اين متد در نظر گرفته نشود، مي‌توان ويژگي Test آن‌را به صورت زير تغيير داد:

[Test,Ignore]

نكته: مرسوم شده است كه نام متدهاي آزمايش واحد به صورت زير تعريف شوند (با Test شروع شوند، در ادامه نام متدي كه بررسي مي‌شود ذكر گردد و در آخر ويژگي مورد بررسي عنوان شود):

Test[MethodToBeTested][SomeAttribute]

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