محمدرضا برجیان
محمدرضا برجیان
خواندن ۷ دقیقه·۱ سال پیش

تست نویسی به زبان ساده(۲) - توضیحاتی در مورد تست نویسی

در مقاله قبلی (تست نویسی اندروید به زبان ساده - تست نویسی چی هست و چرا تست بنویسیم؟) در مورد اینکه تست نویسی چی هست و چرا باید تست بنویسیم صحبت کردیم و چند تا از دلایل تست نوشتن رو بررسی کردیم. حالا میخوایم بریم سراغ نکات تکمیلی تر تا آماده بشیم برای پیاده سازی...

انواع تست:

انواع مختلفی از تست داریم مثل:

  • چیزی که داریم تست میکنیم آیا هدفی که برای انجام داره رو درست انجام میده؟(Functional testing)
  • چیزی که داریم تست میکنیم آیا به سرعت و درست انجام میده؟(Performance testing)
  • چیزی که داریم تست میکنیم آیا با accessibility services همکاری خوبی داره؟ بخشی از اندروید که خدمات ویژه ای داره مثل تاک بک که برای نابینایان هست(Accessibility testing)
  • چیزی که داریم تست میکنیم آیا با در هر دستگاه و Api های مختلف به درستی عمل میکند؟(Compatibility testing)

نمیخوام در مورد انواع مختلف تست صحبت کنم و اونهارو دسته بندی کنم اما شاید در آینده بتونیم چنین مقاله ای هم داشته باشیم. بیشتر میخوام در این مقاله در مورد تست هایی صحبت کنیم که در روند اتومیشن هستن و با کدنویسی انجام میشن.

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

  • تست واحد(Unit Test): تست هایی هستند که فقط بخش کوچکی از برنامه رو وریفای میکنند، مثل وریفای یک متد.
  • تست End-To-End یا تست های بزرگ: این تست ها بخش بزرگی از اپ رو تست میکنه، مثل تست فلو کاربر یا یک صفحه.
  • تست Medium: این تست ها معمولا دو یا چند یونیت تست رو بررسی میکنه.

محیطی که قرار هست تست هارو در اون محیط اجرا کنیم...

تست های ابزاری(Instrumented tests): این تست ها روی یک دیوایس اندرویدی اجرا میشن(دیوایس خودمون یا شبیه ساز). در این تست، نسخه آزمایشی برنامه نصب میشه و دستوراتی که گفته شده اجرا خواهند شد. این نوع تست ها معمولا تست های UI هستند که برنامه رو اجرا میکنند و با رابط کاربری تعامل دارند.

تست های محلی(Local Test): این تست ها روی سیستم یا سرور اجرا میشن و بهشون تست های سمت میزبان هم گفته میشه. این تست ها معمولا کوچک و سریع هستن و بخش تست را از بقیه قسمت های برنامه جدا در نظر میگیرن و اون بخش رو تست میکنند.

نکته ی خیلی مهم: همه تست های واحد لوکال نیستن و همه تست های end-to-end روی دستگاه فیزیکی اجرا نمیشن.

نمونه ای از کد instrumented Ui تست که باعث تعامل با رابط کاربری میشه:

// When the Continue button is clicked Then the Welcome screen is displayed onView(withText(&quotContinue&quot)).perform(click()) onView(withText(&quotWelcome&quot)).check(matches(isDisplayed()))

خب کد بالا میگه عملیات کلیک روی ویو ای که تکست Continue رو داره انجام بشه و بعد از انجام اون چک کن که ویو ای که متن Welcome رو داره نمایش داده بشه. پس در حالت معمول برای اجرای این کد نیاز داریم که شبیه ساز اجرا بشه و عملیات بالا اجرا بشه...

نمونه ای از unit test

// Given an instance of MyViewModel val viewModel = MyViewModel(myFakeDataRepository) // When data is loaded viewModel.loadData() // Then it should be exposing data assertTrue(viewModel.data != null)

در این کد میخوایم یک ویومدل رو تست کنیم که بعد از زمانی که تابع loadData() رو فراخوانی کردیم، متغیر data در ویومدل خالی نباشد.

چقدر باید تست بنویسیم؟

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

Flaky tests

این نوع تست،بدون تغییری در کد یا تست، هم نتایج قبولی و هم ناموفق را به همراه دارد. به عبارت دیگر، تست های Flaky در هر آزمایش مجزا نتیجه یکسانی ندارند.تست پوسته پوسته(Flaky) به خودی خود خطرناک نیست، اما اعتماد به نفسی که یک توسعه دهنده می تواند به کدهای تست شده خود بدهد را کاهش می دهد.معمولا مدتی بعد از نوشتن تست، متوجه میشیم که تست FLAKY هستند. مثلا فرض کنید تستی نوشته شده است بر پایه تاریخ سیستم. این تست درصورت اشتباه نوشته شدن، هنگام اجرا در برخی از تاریخ ها میتواند پاس نشود!!!

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

در صورتی که قابل تست نباشد، کلاس‌هایی که نمی‌توانند Unit test شوند، ممکن است توسط تست‌های integration یا تست‌های UI پوشش داده شوند.

به طور مثال، فرض کنید بخش های منطق برنامه در ویو نوشته شده باشد، ممکن است نیاز داشته باشیم از Ui تست برای آزمایش کد استفاده کنیم در حالی که میتونستیم این کار رو با یک unit test ساده انجام بدیم.

برای یک تست خوب، اندروید چه نکاتی رو گفته که رعایت کنیم؟

رویکردهای جداسازی

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

تکنیک های متداول جداسازی شامل موارد زیر است:

برنامه رو به لایه های Domain, Presentation, Data تقسیم کنیم. یا حتی برنامه رو میتونیم به ماژول های مختلف تقسیم کنیم. برای هر فیچر یک ماژول در نظر گرفته بشه.

منطق رو در موجودیت هایی که وابستگی زیادی دارند(framework dependencies) وارد نکنیم مثل فرگمنت ها و اکتیویتی ها.

در کلاس های ویومدل از framework dependencies مثل context استفاده نکنیم.به عنوان مثال، از Android Contexts در ViewModels استفاده نکنید.

به جای پیاده سازی های مشخص(کلاس های concrete)، از رابط ها(interface) استفاده کنید. حتی اگر از DI framework استفاده نمی کنید، از تزریق وابستگی استفاده کنید.

پوشه های مربوط به تست در اندروید:

پوشه androidTest:

این پوشه شامل تست هایی هست که نیاز داره در یک دستگاه واقعی اجرا بشه. مانند integration test, end-to-end tests و تست های دیگه ای که JVM نمیتونه به تنهایی انجام عملکرد رو ولیدیت کنه.

پوشه test:

شامل تست هایی هست که در ماشین محلی اجرا میشه مثل یونیت تست.

Unit Test:

چه بخش هایی رو با یونیت تست، تست میکنیم؟

  • کلاس های viewModel,Presenter
  • لایه data به ویژه Repository
  • بخش هایی از کد که به فریمورک اندروید وابسته نیست
  • تست برای کلاس های کاربردی مثل کلاس های utils برنامه(کلاس های ریاضی داخل برنامه و کلاس های مربوط به string ها و...)

تست Edge Cases:

تست بخش هایی از برنامه با سناریو های نامعمول که تستر ها و تست های بزرگ ممکنه از دستشون بدن مثل:

  • عملیات ریاضی با اعداد منفی، صفر و Boundary conditions
  • همه ی ارور های احتمالی شبکه
  • مشکل در تحلیل جیسون( جیسون اشتباهی که از سمت سرور میاد)

و...

یونیت تست رو برای چه چیزی نباید بنویسیم؟

برای جایی که عملکرد یک کتابخانه هندل میشه نه عملکرد کد ما(این نکته خیلی مهمه... در آینده وقتی تست مینویسیم روی این نکته خیلی تاکید داریم)

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

برای چه چیزهایی یوآی تست مینویسیم؟

  • تست یوآی صفحه مثل کلیک کاربر، تایپ کردن و... میتونیم یک تست در هرصفحه داشته باشیم.
  • تست های جریان کاربر(User Flow) که حرکت کاربر رو شبیه سازی میکند. این تست ها تست خوبی هستن برا چک کردن کرش های زمان اجرا(run time)

test coverage:

برخی از ابزار های تست آماری میدن از اینکه چه میزان کد توسط تست ها پوشش داده شده است. میتونیم از این آمار ها استفاده کنیم اما به عنوان یک استراتوژی نباید بهش نگاه کنیم. مثلا یکی از راه هایی که تست کاوریج کدهاتون رو داشته باشید این هست که میتونید روی پوشه تست لوکال کلیک راست کنید و run tests with coverage رو بزنید. بعد از اجرای تست هاتون، آمار خوبی در مورد هر پوشه یا هر کلاس بهتون ارائه میده.

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

تستunit testlocal testui testtest
فقط یک برنامه نویس اندروید...
شاید از این پست‌ها خوشتان بیاید