تست واحد روشی برای بررسی نرمافزار است که در آن صحت عملکرد ماژولها یا توابع کد بهصورت مجزا از بقیه سیستم آزمایش میشود. بر اساس Martin Fowler, 2026، تستهای واحد پایه CI/CD و بازسازی کد (refactoring) هستند و بازخورد سریعی درباره عملکرد صحیح کد فراهم میکنند. تست ماژولار به یافتن خطاها در مراحل اولیه توسعه کمک میکند و هزینه رفع آنها را تا دهها برابر کاهش میدهد.
نکات کلیدی
تست واحد فرآیندی برای بررسی واحدهای مجزا (unit) از کد منبع — توابع، متدها، کلاسها — بهصورت مستقل از بقیه برنامه است. هر تست یک سناریوی مشخص از استفاده از ماژول را اجرا میکند و بررسی میکند که نتیجه با انتظارات مطابقت دارد. تستهای واحد به همان زبان برنامهنویسی کد اصلی نوشته میشوند و بهصورت خودکار در محیط توسعه یا در خط لوله CI/CD اجرا میشوند. برخلاف تستهای یکپارچهسازی، تستهای واحد با پایگاههای داده واقعی، سیستم فایل یا سرویسهای شبکهای تعامل ندارند.
هدف اصلی بازخورد سریع درباره صحت کد پس از تغییرات است. اگر توسعهدهنده یک متد را بازسازی (refactor) کند، مجموعه تستهای واحد تأیید میکند که رفتار کد خراب نشده است. بر اساس Google Testing Blog (2025)، پروژههایی با پوشش تست واحد بالای ۶۰٪، ۲.۵ برابر حوادث تولید (production) کمتری دارند. مزایای دیگر: مستندسازی کد (تستها نشان میدهند که چگونه از API استفاده کنید)، سادهسازی بازسازی (میتوانید پیادهسازی را تغییر دهید و رفتار را حفظ کنید) و تشخیص سریع رگرسیونها.
هر تست خودکار یک تست واحد نیست. معیارها: یک ماژول (کلاس یا تابع) آزمایش میشود، وابستگیهای خارجی با مک یا استاب جایگزین شدهاند، تست در چند میلیثانیه اجرا میشود و نیازی به بالا آوردن سرور یا پایگاه داده ندارد. تستی که به پایگاه داده واقعی دسترسی دارد — تست یکپارچهسازی است. تستی که مرورگر را باز میکند — تست E2E است. درک مرزهای بین انواع تستها برای توزیع صحیح تلاشها در هرم تست اهمیت دارد.
تستهای واحد باکیفیت از اصول FIRST پیروی میکنند که توسط Robert C. Martin تدوین شده است. هر تست باید Fast (سریع — چند میلیثانیه)، Isolated (منزوی — به تستهای دیگر وابسته نیست)، Repeatable (تکرارپذیر — نتیجه یکسان روی هر ماشین)، Self-validating (خودسنج — نتیجه «passed» یا «failed» بدون بررسی دستی) و Timely (بهموقع — قبل یا همزمان با کد نوشته میشود) باشد. نقض هر یک از این اصول ارزش تست را کاهش میدهد.
الگوی استاندارد برای نوشتن تستهای واحد. Arrange — آمادهسازی دادهها و وابستگیها: ایجاد اشیا، پیکربندی مکها، تعیین پارامترهای ورودی. Act — اجرای عمل مورد آزمایش: فراخوانی متد یا تابع. Assert — بررسی نتیجه: مقایسه مقدار واقعی با مقدار مورد انتظار. تقسیم به سه بخش، تست را خوانا و قابلدرک میکند. اگر بخش Assert نیاز به منطق پیچیده داشته باشد، احتمالاً تست بیش از حد در یک زمان بررسی میکند.
// نمونه تست واحد بر اساس الگوی AAA در Kotlin با JUnit 5
class CalculatorTest {
private lateinit var calculator: Calculator
@BeforeEach
fun setUp() {
// ARRANGE — شیء مورد آزمایش را میسازیم
calculator = Calculator()
}
@Test
fun addition_shouldReturnCorrectSum() {
// ACT — عمل را اجرا میکنیم
val result = calculator.add(2, 3)
// ASSERT — نتیجه را بررسی میکنیم
Assertions.assertEquals(5, result)
}
}
نام تست باید توصیف کند که چه چیزی بررسی میشود و چه نتیجهای انتظار میرود. قالب: [methodName]_[scenario]_[expectedResult]. مثال: calculateTotal_whenDiscountApplied_shouldReturnDiscountedTotal. نام خوب تست جایگزین کامنت میشود و هنگام شکست بلافاصله نشان میدهد کدام قابلیت خراب شده است. از نامهایی مانند test1، checkSomething یا verify اجتناب کنید — آنها اطلاعاتی ندارند و تشخیص را دشوار میکنند.
برای جداسازی ماژول مورد آزمایش از وابستگیهای خارجی از دوبلورهای تستی (test doubles) استفاده میشود. انواع اصلی: مکها (mocks) — بررسی میکنند که یک متد مشخص با پارامترهای درست فراخوانی شده است؛ استابها (stubs) — هنگام فراخوانی متد، مقادیر از پیش تعیینشده را برمیگردانند؛ فیکها (fakes) — پیادهسازی سادهشدهای از یک کامپوننت واقعی (مثلاً InMemoryUserRepository بهجای UserRepository که با پایگاه داده کار میکند). انتخاب بستگی به این دارد که چه چیزی را میخواهید بررسی کنید: حالت (state) (استاب) یا تعامل (مک).
| دوبلور | چه چیزی را بررسی میکند | مثال |
|---|---|---|
| مک | فراخوانی متد با پارامترهای صحیح | userRepository.save(user) دقیقاً ۱ بار فراخوانی شد |
| استاب | مقدار بازگشتی | repository.findById(1) مقدار User(id=1, name="Test") را برمیگرداند |
| فیک | منطق از طریق پیادهسازی سادهشده | InMemoryMapUserRepository با HashMap بهجای پایگاه داده |
| جاسوس | مک کردن جزئی شیء واقعی | spy(repo).when(findById).thenReturn(user) |
Mockito محبوبترین فریمورک مک کردن در Java و Kotlin است. این امکان را میدهد که با mock() مک بسازید، با when().thenReturn() مقدار بازگشتی را تنظیم کنید و با verify() فراخوانیها را بررسی کنید. نسخههای مدرن Mockito (5.x) از مکهای استاتیک (mockStatic) و سینتکس سادهشده از طریق BDDMockito (given-willReturn) پشتیبانی میکنند. قانون مهم: چیزی را که مال شما نیست مک نکنید — برای value-objectها و کتابخانههای استاندارد مک نسازید.
// نمونه تست واحد با Mockito در Kotlin
class OrderServiceTest {
@Mock
private lateinit var paymentGateway: PaymentGateway
@Mock
private lateinit var userRepository: UserRepository
private lateinit var orderService: OrderService
@BeforeEach
fun init() {
MockitoAnnotations.openMocks(this)
orderService = OrderService(paymentGateway, userRepository)
}
@Test
fun processOrder_whenPaymentFails_shouldThrowException() {
// given
val user = User(id = 1, balance = 100.0)
val order = Order(amount = 200.0)
Mockito.`when`(paymentGateway.charge(any())).thenReturn(false)
Mockito.`when`(userRepository.findById(1)).thenReturn(user)
// when & then
assert Throws<PaymentException> {
orderService.processOrder(user.id, order)
}
// verify
Mockito.verify(paymentGateway).charge(any())
}
}
TDD (توسعه مبتنی بر تست) متدولوژیای است که در آن تست قبل از پیادهسازی کد نوشته میشود. چرخه «Red-Green-Refactor»: تستی بنویسید که شکست میخورد (Red)، حداقل کد را برای عبور از تست بنویسید (Green)، کد را بدون تغییر رفتار بهبود دهید (Refactor). TDD تضمین میکند که تمام کد با تست پوشش داده شده است (پوشش = ۱۰۰٪ برای قابلیت نوشتهشده) و کد قابلتست است — اگر تست کردن کد دشوار باشد، یعنی معماری نیاز به بهبود دارد.
بر اساس مطالعه IBM (2006-2026، مطالعه طولی)، تیمهایی که از TDD استفاده میکنند نسبت به تیمهایی که تست را بعد از کد مینویسند، ۴۰ تا ۸۰٪ نقصهای کمتری در production دارند. TDD همچنین معماری را بهبود میبخشد: توسعهدهنده مجبور است قبل از پیادهسازی به طراحی API فکر کند که منجر به اتصال سست (loose coupling) و انسجام بالا (high cohesion) میشود. اثر اضافی — مستندسازی با کد زنده: تستها بهعنوان مشخصات رفتار ماژول عمل میکنند که همیشه بهروز هستند.
TDD همیشه بهینه نیست. تست کردن کامپوننتهای UI بهصورت مجزا دشوار است — برای آنها تستهای snapshot یا تست رگرسیون بصری (Percy, Chromatic) مؤثرتر هستند. نمونهسازی اولیه و پژوهش (spike solutions) به تست نیاز ندارند. کد legacy بدون تست از طریق TDD بهسختی پوشش داده میشود — اینجا ابتدا به characterization tests (تستهایی که رفتار فعلی را قبل از بازسازی تثبیت میکنند) نیاز دارید. در این موارد TDD کاملاً کنار گذاشته نمیشود، بلکه تطبیق داده میشود — برای قابلیتهای در حال تغییر تست نوشته میشود، نه برای کل کد legacy.
توسعه موبایل ویژگی خاص خود را دارد: منطق کسبوکار اغلب با کد UI ترکیب میشود (Activity, ViewController, ViewModel) که تست واحد را پیچیده میکند. بهترین روش — Viewهای نازک، ViewModelهای ضخیم: تمام منطق را از کامپوننتهای UI به کلاسهای جداگانه (UseCase, Repository, ViewModel) منتقل کنید که بدون ایمولاتور بهراحتی قابلتست هستند. برای Android و iOS فریمورکهای بومی تست واحد وجود دارند که روی JVM/Native بدون اجرای دستگاه کار میکنند.
تستهای واحد Android روی JVM محلی بدون ایمولاتور اجرا میشوند که سرعت اجرا را تضمین میکند — یک تست معمولی کمتر از < 100ms طول میکشد. JUnit 5 runner اصلی است. برای تستهای ViewModel از kotlinx-coroutines-test برای تست کوروتینها و Turbine برای تست StateFlow استفاده کنید. Robolectric امکان تست کامپوننتهای وابسته به Android (Context, Resources) را بدون ایمولاتور فراهم میکند و کلاسهای shadow را بارگذاری میکند. برای تستهای Compose از Compose UI Test استفاده کنید — اما اینها تستهای UI هستند، نه unit.
تستهای واحد iOS با Swift و XCTest (که در Xcode تعبیه شده) نوشته میشوند. Quick + Nimble — فریمورکهای BDD برای تستهای خواناتر (describe/context/it). برای مک کردن از Cuckoo (تولید مک) یا SwiftyMocky استفاده کنید. Swift از پروتکلها و تزریق وابستگی (dependency injection) پشتیبانی میکند که جایگزینی وابستگیها را آسان میکند. نکته کلیدی: تستهای واحد iOS روی شبیهساز macOS اجرا میشوند، نه روی دستگاه واقعی. تستهایی که به قابلیتهای سختافزاری نیاز دارند (دوربین، بلوتوث) — تستهای یکپارچهسازی هستند.
تستهای واحد Flutter از پکیج flutter_test استفاده میکنند و روی Dart VM بدون ایمولاتور اجرا میشوند. برای مک کردن — پکیج mockito با تولیدکننده کد (build_runner). تستهای Widget (در همان پکیج) ویجتهای جداگانه را آزمایش میکنند، اما به رندر نیاز دارند و کندتر هستند — فقط برای بررسی منطق UI از آنها استفاده کنید. منطق خالص Dart (مدلها، ریپازیتوریها، بلابها) بهعنوان تستهای Dart معمولی بدون import کردن flutter_test آزمایش میشود.
// نمونه تست واحد در Flutter با mockito
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:mockito/annotations.dart';
@GenerateMocks([ApiClient])
import 'user_repository_test.mocks.dart';
void main() {
late MockApiClient mockApi;
late UserRepository repository;
setUp(() {
mockApi = MockApiClient();
repository = UserRepository(mockApi);
});
test('fetchUser returns user when API succeeds', () async {
// Arrange
final expectedUser = User(id: 1, name: 'Test');
when(mockApi.getUser(1))
.thenAnswer((_) async => expectedUser);
// Act
final result = await repository.fetchUser(1);
// Assert
expect(result, expectedUser);
verify(mockApi.getUser(1)).called(1);
});
}
تست واحد مؤثر نیازمند نظم و انضباط است. قانون اصلی: رفتار را تست کنید، نه پیادهسازی را. تست نباید بداند که ماژول در داخل چگونه پیادهسازی شده است (چه متدهای خصوصیای در چه ترتیبی فراخوانی میشوند). اگر تست به پیادهسازی وابسته باشد، در هر بازسازی میشکند و ارزش خود را از دست میدهد. تست قرارداد را بررسی میکند: با ورودی X باید خروجی Y به دست آید. استثنا — تستهای الگوریتمهایی با عملکرد حیاتی، جایی که ترتیب فراخوانیها مهم است.
پوشش ۱۰۰٪ — هدفی دستنیافتنی و غیرضروری است. بر اساس Google Testing Blog (2025)، سطح بهینه پوشش برای تستهای واحد — ۷۰ تا ۸۰٪ خطوط کد است. پوشش ۱۰۰٪ اغلب با تست getterها، setterها و سازندهها به دست میآید که ارزشی ندارد. روی منطق حیاتی کسبوکار تمرکز کنید: محاسبات پیچیده، اعتبارسنجی، مدیریت خطا، حالتهای مرزی (edge cases). برای اندازهگیری از JaCoCo (Java)، Coverage.py (Python)، Istanbul (JS) استفاده کنید و آستانه را در CI تنظیم کنید — شکست build در پوشش کمتر از ۶۰٪.
تستهای واحد — مرحله اول هر خط لوله CI/CD هستند. آنها در هر push به مخزن، قبل از build و deploy اجرا میشوند. میانگین زمان اجرای تستهای واحد در پروژه نباید از ۵ دقیقه بیشتر شود — اگر طولانیتر باشد، تستها دیگر «سریع» نیستند و توسعهدهندگان دیگر آنها را بهصورت محلی اجرا نمیکنند. تستها را به سریع (unit) و کند (یکپارچهسازی) تقسیم کنید و در استیجهای مختلف خط لوله اجرا کنید. برای تسریع از parallel execution و fail-fast استفاده کنید.
سوالات متداول
تست واحد یک ماژول را بهصورت مجزا بررسی میکند و وابستگیهای خارجی را با مکها جایگزین میکند. تست یکپارچهسازی تعامل بین چندین کامپوننت واقعی (پایگاه داده، API، سیستم فایل) را بررسی میکند. تستهای واحد در چند میلیثانیه اجرا میشوند، تستهای یکپارچهسازی — در چند ثانیه. در هرم تست، تستهای واحد ۷۰٪ را تشکیل میدهند.
انتخاب به پلتفرم بستگی دارد: JUnit 5 برای Java/Kotlin، XCTest برای iOS/Swift، pytest برای Python، Jest/Vitest برای JavaScript/TypeScript، flutter_test برای Flutter. برای مک کردن از Mockito (Java)، Cuckoo (iOS)، unittest.mock (Python) یا vitest.mock (JS) استفاده کنید. همه فریمورکهای مدرن از تستهای پارامتری، assertionهای داخلی و اجرای موازی پشتیبانی میکنند.
Fast — تست در چند میلیثانیه اجرا میشود. Isolated — به تستهای دیگر و سیستمهای خارجی وابسته نیست. Repeatable — روی هر ماشین نتیجه یکسان میدهد. Self-validating — نتیجه را بهصورت خودکار بررسی میکند. Timely — قبل یا همزمان با کد نوشته میشود. نقض حتی یکی از اصول، اثربخشی تست را کاهش میدهد.
بله، حتماً. ViewModel حاوی منطق کسبوکار است — پردازش رویدادها، تبدیل دادهها، مدیریت وضعیت. در Android از kotlinx-coroutines-test برای کوروتینها و Turbine برای تست StateFlow استفاده کنید. در iOS Publisherهای Combine یا async/await را در ViewModel تست کنید. تستهای ViewModel — تستهای واحد خالص هستند که روی JVM/macOS بدون ایمولاتور اجرا میشوند.
درخواستهای شبکهای در تستهای واحد اجرا نمیشوند — آنها با مکهای HTTP-کلاینت جایگزین میشوند. در Android از MockWebServer (OkHttp) استفاده کنید — یک سرور HTTP محلی راهاندازی میکند که از مکها بهتر است، زیرا تعامل شبکه واقعی را بازتولید میکند. MockWebServer ایزولاسیون را بدون از دست دادن واقعگرایی فراهم میکند. برای iOS — OHHTTPStubs یا URLProtocol برای رهگیری و جایگزینی پاسخها.
جمعبندی
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید