در مقاله قبلی (تست نویسی اندروید به زبان ساده - تست نویسی چی هست و چرا تست بنویسیم؟) در مورد اینکه تست نویسی چی هست و چرا باید تست بنویسیم صحبت کردیم و چند تا از دلایل تست نوشتن رو بررسی کردیم. حالا میخوایم بریم سراغ نکات تکمیلی تر تا آماده بشیم برای پیاده سازی...
انواع مختلفی از تست داریم مثل:
نمیخوام در مورد انواع مختلف تست صحبت کنم و اونهارو دسته بندی کنم اما شاید در آینده بتونیم چنین مقاله ای هم داشته باشیم. بیشتر میخوام در این مقاله در مورد تست هایی صحبت کنیم که در روند اتومیشن هستن و با کدنویسی انجام میشن.
تست های ابزاری(Instrumented tests): این تست ها روی یک دیوایس اندرویدی اجرا میشن(دیوایس خودمون یا شبیه ساز). در این تست، نسخه آزمایشی برنامه نصب میشه و دستوراتی که گفته شده اجرا خواهند شد. این نوع تست ها معمولا تست های UI هستند که برنامه رو اجرا میکنند و با رابط کاربری تعامل دارند.
تست های محلی(Local Test): این تست ها روی سیستم یا سرور اجرا میشن و بهشون تست های سمت میزبان هم گفته میشه. این تست ها معمولا کوچک و سریع هستن و بخش تست را از بقیه قسمت های برنامه جدا در نظر میگیرن و اون بخش رو تست میکنند.
نکته ی خیلی مهم: همه تست های واحد لوکال نیستن و همه تست های end-to-end روی دستگاه فیزیکی اجرا نمیشن.
// When the Continue button is clicked Then the Welcome screen is displayed onView(withText("Continue")).perform(click()) onView(withText("Welcome")).check(matches(isDisplayed()))
خب کد بالا میگه عملیات کلیک روی ویو ای که تکست Continue رو داره انجام بشه و بعد از انجام اون چک کن که ویو ای که متن Welcome رو داره نمایش داده بشه. پس در حالت معمول برای اجرای این کد نیاز داریم که شبیه ساز اجرا بشه و عملیات بالا اجرا بشه...
// 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 در هر آزمایش مجزا نتیجه یکسانی ندارند.تست پوسته پوسته(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:
شامل تست هایی هست که در ماشین محلی اجرا میشه مثل یونیت تست.
چه بخش هایی رو با یونیت تست، تست میکنیم؟
تست بخش هایی از برنامه با سناریو های نامعمول که تستر ها و تست های بزرگ ممکنه از دستشون بدن مثل:
و...
برای جایی که عملکرد یک کتابخانه هندل میشه نه عملکرد کد ما(این نکته خیلی مهمه... در آینده وقتی تست مینویسیم روی این نکته خیلی تاکید داریم)
برای اکتیویتی و فرگمنت و ویو... . چون برای نوشتن یونیت در اکتیویتی نیاز منابع زیادی هست و باید کانفیگ های خاصی اعمال بشه.
برخی از ابزار های تست آماری میدن از اینکه چه میزان کد توسط تست ها پوشش داده شده است. میتونیم از این آمار ها استفاده کنیم اما به عنوان یک استراتوژی نباید بهش نگاه کنیم. مثلا یکی از راه هایی که تست کاوریج کدهاتون رو داشته باشید این هست که میتونید روی پوشه تست لوکال کلیک راست کنید و run tests with coverage رو بزنید. بعد از اجرای تست هاتون، آمار خوبی در مورد هر پوشه یا هر کلاس بهتون ارائه میده.
خیلی از موارد ترجمه شده از سایت اندروید دولوپر هست که سعی کردم یه مقداری تجربه هم بهش اضافه کنم و بیشتر توضیح بدم که شاید مفهوم رو بتونم راحت تر منتقل کنم و کامل تر باشه...
اگر نکته ای بود خیلی خوشحال میشم که کامنت بزارید و بازخورد بهم بدید... پیشاپیش ازتون ممنونم