آموزش سوئیفت (Swift): نوشتن تست — بخش هفدهم

۱۰۶ بازدید
آخرین به‌روزرسانی: ۲۹ شهریور ۱۴۰۲
زمان مطالعه: ۶ دقیقه
آموزش سوئیفت (Swift): نوشتن تست — بخش هفدهم

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

فهرست مطالب این نوشته

نوشتن تست

تست کردن مهم است و ارتباط تنگاتنگی با رویکرد TDD دارد. TDD اختصاری برای عبارت «Test-driven Development» (توسعه تست-محور) است. توسعه تست-محور یک روش رایج برای نوشتن اپلیکیشن است و به‌خاطرسپاری این فرمول نیز آسان است.

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

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

ما در این مقاله صرفاً به پوشش روش TDD می‌پردازیم. پیش از آن که کار خود را آغاز کنیم باید با دو اصطلاح جدید آشنا شوید: «Test Ratio» (نسبت تست) و «Code Coverage» (پوشش کد).

نسبت تست به نسبت تعداد خطوط کد به خطوط تست‌های نوشته‌شده گفته می‌شود. افراد زیادی هستند که می‌گویند هر 1 خط کد باید با 3 خط تست شود، یعنی این نسبت باید 1:3 باشد.

پوشش کد یعنی چه میزان از کد برحسب درصد تست شده است. IDE-های زیادی به نمایش پوشش کد می‌پردازند که کدبیس را برحسب وضعیت تست نمایش می‌دهد. رنگ سبز به معنی وجود تست برای کد و رنگ قرمز به معنی عدم وجود تست است.

برخی افراد بر این باورند که به صورت پیش‌فرض همه کدها باید تست شوند. اما شاید این دیدگاه چندان صحیح نباشد، چون لزومی وجود ندارد که تابع‌های موجود در سوئیفت و دیگر کتابخانه‌ها که خودشان شامل تست هستند مجدداً تست شوند. برای نمونه لازم نیست برای یک گزاره print یا دیگر متدهای استاتیک مانند ()Date.init تست نوشت. در واقع صرفاً لازم است که کد خودتان را تست کنید. تنها استثنا در این مورد کدهای افراد دیگری است که تست نداشته باشند.

نحوه نوشتن تست چیزی شبیه زیر است:

1import XCTest
2@testable import ViewController
3
4class ViewControllerTests: XCTestCase {
5  // Testing properties here
6  override func setUp() {
7    super.setUp()
8    // Instantiate any classes
9    // to be used here
10  }
11  
12  override func tearDown() {
13        // De-initialize classes here
14        super.tearDown()
15    }
16  
17  func test_multiplyByTwoReturnsFour() {
18    XCTAssertEqual(multiply(2, by:2), 4)
19  }
20}

کد فوق را خط به خط بررسی می‌کنیم. import XCTest اقدام به ایمپورت کردن کتابخانه ارائه شده از سوی اپل برای تست کردن می‌کند. testable import ViewController @testable یک خصوصیت است که دامنه دسترسی این ماژول را افزایش می‌دهد. در واقع این دستور سطح دسترسی را از internal یا private به open تغیر می‌دهد اما تنها برای تست‌های لوکال کار می‌کند. دستور import ViewController گزاره ایمپورتی است که شامل کلاس مورد نظر برای تست است.

{ ... } class ViewControllerTests: XCTestCase  کلاسی تعریف می‌کند که همه کارکردهای خود را از XCTestCase به ارث می‌برد. اگر از هرکدام از مشخصه‌ها استفاده کنید، چه کلاس دیگر باشد و چه برخی از ثابت‌ها یا متغیرهایی که غالباً در کاربردهای تست استفاده می‌شود به جای Testing properties here// آن مشخصه‌ها را بنویسید.

()override func setUp را می‌توان به عنوان ()viewDidLoad برای حالت‌های تست تصور کرد. زمانی که شروع به تست کردن می‌کنید، این کد کلاس‌ها را ایجاد می‌کند یا از متغیرها وهله می‌سازد.

()override func tearDown معادل {} deinit در کنترلرهای نما است. هر چیزی را که ممکن است موجب ایجاد نشت حافظه شود پاک کنید، Timer مثال خوبی از آن چیزی است که باید حذف شود.

()func test_multiplyByTwoReturnsFour یکی از قراردادهای نامگذاری فراوانی است که وجود دارد، اما این نام گذاری مقصود تست را مشخص می‌کند. موارد تست را همواره با عبارت test آغاز کنید. استفاده از _ اختیاری است، اما به افزایش خوانایی کمک می‌کند. در ادامه multiplyByTwo آن چیزی است که قرار است انجام دهیم و ReturnsFour آن چیزی است که انتظار داریم دریافت کنیم. اگر موارد تست را به این ترتیب بنویسید همواره می‌دانید که هر مورد تست برای چه چیزی استفاده می‌شود و چه نتیجه‌ای از آن انتظار می‌رود. اما اگر در نهایت صرفاً اعداد فرد و گرد شده بازگشت یابند چطور؟ بدین ترتیب می‌توان مورد تستی مانند {} func test_getOddFromMultiplyByTwo_ReturnsFive نوشت.

در نهایت دستور (XCTAssertEqual(value، value را می‌بینیم که مورد تست واقعی است که برابر بودن هر دو مقدار را تست می‌کند. XCTAssert یک پیشوند رایج است و از این رو اگر شروع به نوشتن XCT بکنید امکان تکمیل خودکار در Xcode تعدادی از متدهایی که می‌توانید استفاده کنید را به شما پیشنهاد می‌کند. در این حالت اگر هر دو مقدار برابر باشند تست پاس می‌شود و در غیر این صورت ناموفق خواهد بود.

برخی تست‌های رایج دیگر شامل XCTAssertNotEqual ،XCTAssertNil و XCTAssertNotNil هستند.

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

1func test_getFactorialPerformance() {
2    measure {
3        _ = getFactorial(of: 45)
4    }
5}

برخلاف تست‌های تأییدی ما، نیازی نیست که بخش بازگشتی مورد انتظار را در اعلان متد بگنجانیم. به جای آن کافی است آن را با بلوک { }test_functionNamePerformance(). measure  عوض کنیم که باید صرفاً شامل کدی باشد که می‌خواهیم اندازه‌گیری کنیم. اگر قرار باشد یک متغیر به (:getFactorial(of ارسال کنیم، در این صورت آن را خارج از بلوک اندازه‌گیری وهله‌سازی می‌کنیم، مگر این که بخواهیم آن را در اندازه‌گیری‌های خود بگنجانیم.

(getFactorial(of: 45 =_ به فراخوانی متد getFactorial با ورودی 45 می‌پردازد. از آنجا که نیازی به ذخیره‌سازی مقدار نداریم از   =_ استفاده می‌کنیم که نوعی جابجایی نتایج به /dev/null محسوب می‌شود. برای ما مهم نیست که این مقدار چیست و در اینجا به آن اهمیتی نمی‌دهیم.

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

نوشتن تست
1class Factorial {
2    // Get the factorial of a number recursively
3    func getFactorial(of number: Int) -> Int {
4        
5        // if we are at 1 return 1
6        if number == 1 { return 1 }
7        // add our current number to the factorial of 
8        // the number minus 1
9        return number + getFactorial(of: number - 1)
10    }
11}

در اجرای نخست این کد تابع را در طی 0.00000263 اجرا می‌کند. این زمان کاملاً سریع است اما در طی این مدت بهینه‌سازی‌هایی رخ می‌دهند. در ادامه اجراهای بعد را می‌بینیم.

نوشتن تست

در اجرای دوم، ما این تابع را 39 درصد بهتر اجرا کرده‌ایم، اما هیچ چیز تغییر نیافته است. دلیل این امر آن است که بهینه‌سازی‌ها قبلاً صورت گرفته‌اند و صرفاً از آن‌ها مجدداً استفاده کرده‌ایم. این جا مکان خوبی برای تنظیم مبدأ جدید است بنابراین ویرایش را کلیک می‌کنیم و مبدأ جدید را پذیرفته و آن را ذخیره می‌کنیم. در اجراهای بعدی به ترتیب 6% عملکرد بدتر، 0% بهتر، 2% بهتر، و 7% بهتر بدون تغییر دادن هیچ خطی از کد به دست آمدند. بنابراین مبدأ خوبی برای ما محسوب می‌شود. از این جا می‌توانیم تغییرات خود را ایجاد کرده و سپس اندازه‌گیری‌ها را تست کنیم تا ببینیم آیا تغییرات ما موجب عملکرد بهتر یا بدتر می‌شوند.

با این که راهنمای صریحی در مورد آن چه بهینه‌سازی خوب شمرده می‌شود وجود ندارد اما تصور ما این است هر تغییری که موجب بهبود در طی 10 اندازه‌گیری (100 اجرا) شود مناسب است. اگر کدی در طی چند اجرای نخست بهبود مناسبی نشان دهد بهتر است آن تغییر را حفظ کنید.

زمانی که در مورد تست کردن Unit Testing صحبت می‌کنیم، در واقع همان فرایندی است که در بخش فوق توضیح دادیم. همچنان یک حالت تست کردن عمومی نیز وجود دارد که به تست کارکرد کلی عملکرد اپلیکیشن چنان که انتظار می‌رود می‌پردازید. UI Testing نوع دیگری از تست کردن است که بخش UI را در برمی‌گیرد، اما از آنجا که این راهنما صرفاً در مورد مفاهیم مقدماتی تست کردن است بررسی آن خارج از دامنه این مقاله خواهد بود. در نهایت باید اشاره کنیم که یک تست پذیرش کاربر (UAT) نیز وجود دارد که برای اطمینان یافتن از این که کاربر از امکانات اپلیکیشن راضی است اجرا می‌شود. این تست عموماً از طریق تیم‌های پرسش و پاسخ (QA) اجرا می‌شود و کسب‌وکار یا مخاطبان منتخب از کاربران نهایی را شامل می‌شود. این تست امکان اجرای سناریوهای بیشتری در اپلیکیشن را می‌دهد که برای تست کردن بیشتر استفاده می‌شوند و به سؤالاتی در مورد شیوه استفاده از اپلیکیشن و زمان عرضه نهایی آن پاسخ می‌دهد.

سخن پایانی

ما در این مقاله با مفاهیم مقدماتی تست کردن آشنا شدیم و دیدیم که چگونه می‌تواند به نوشتن کدهای بهتر کمک کند، چه اهمیتی در چرخه توسعه دارد و چگونه عملکرد کد را اندازه‌گیری می‌کند. تست‌های بیشتر شامل استفاده از ابزارهای Xcode است که می‌توان از آن‌ها برای اندازه‌گیری حافظه، CPU و استفاده از دیسک بهره گرفت، اما این‌ها جزء مباحث پیشرفته هستند. در بخش بعدی در مورد معماری Model View Controller صحبت می‌کنیم.

معماری مدل، نما-کنترلر یا به اختصار MVC به صورت گسترده‌ای برای یادگیری آموزش کدنویسی به افراد مبتدی استفاده می‌شود. این ساده‌ترین روش برای یادگیری شیوه استفاده از چندین فایل در یک پروژه است. با این که روش‌های دیگری نیز وجود دارند که می‌توان استفاده کرد، اما این ساده‌ترین نوع معماری محسوب می‌شود. زمانی که آماده انتقال از MVC باشید تقریباً به طور طبیعی شروع به نوشتن یک سبک معماری متفاوت برحسب نیازهای اپلیکیشن خود می‌کنید. برای مطالعه بخش بعدی (پایانی)  به اینک زیر مراجعه کنید:

آموزش سوئیفت (Swift): معماری MVC --- بخش هجدهم

اگر این مطلب برای شما مفید بوده است آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

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

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *