به طور غم انگیزی برای توسعه دهندگان عادی است که وارد پروژه ای شوند که در آن آزمایش خودکار مناسب نادیده گرفته شده و همچنان نادیده گرفته خواهد شد. این وضعیتی است که توسعه دهنده ها، اغلب با آن مواجه می شوند. در این مقاله، ما به طور خلاصه دلایل نادیده گرفتن آزمایش را پوشش میدهیم و در نهایت «هک زندگی کدنویسی» را توضیح خواهیم داد تا از کنترل کیفیت حتی زمانی که نمیتوانید یک چارچوب آزمایشی را معرفی کنید، اطمینان حاصل کنید.
وقتی یک مجموعه آزمایشی امکان پذیر نیست چه باید کرد
مواقعی وجود دارد که ما برنامه نویسان و یا مشتریانمان منابع محدودی داریم که بتوانیم هم تحویل مورد انتظار و هم تست های خودکار آن قابل تحویل را بنویسیم. وقتی برنامه به اندازه کافی کوچک است، میتوانید گوشهها را کاهش دهید و آزمایشها را نادیده بگیرید، زیرا (بیشتر) به یاد میآورید که وقتی یک ویژگی را اضافه میکنید، یک اشکال را برطرف میکنید، یا Refactor را در جای دیگری از کد میافتد. گفته می شود، ما همیشه با برنامه های کوچک کار نخواهیم کرد، به علاوه، آنها با گذشت زمان بزرگتر و پیچیده تر می شوند. این امر تست دستی را دشوار و فوق العاده آزاردهنده می کند.
در یکی از چند پروژه آخرم، مجبور شدم بدون تست خودکار کار کنم و صادقانه بگویم، بسیار بد بود که مشتری پس از فشار کد به من ایمیل بزند و بگوید که برنامه در جاهایی که حتی کد را لمس نکرده بود، به باگ بر می خورد.
بنابراین، در مواردی که مشتری بودجه یا قصد اضافه کردن فریمورک تست خودکار را نداشت، با ارسال یک درخواست HTTP به هر صفحه جداگانه، تجزیه header های پاسخ و جستجوی پاسخ «200»، شروع به آزمایش عملکرد اصلی وبسایت کرده. ساده و ساده به نظر می رسد، اما کارهای زیادی وجود دارد که می توانید برای اطمینان از وفاداری بدون نیاز به نوشتن تست، واحد، تابعی یا ادغام انجام دهید.
تست خودکار
در توسعه وب، تستهای خودکار از سه نوع آزمون اصلی تشکیل میشوند: تستهای واحد، تستهای عملکردی و تستهای ادغام. ما اغلب تستهای واحد را با تستهای عملکردی و یکپارچهسازی ترکیب میکنیم تا مطمئن شویم که همه چیز بهعنوان یک برنامه کامل اجرا میشود. هنگامی که این تست ها به صورت هماهنگ یا متوالی اجرا می شوند (ترجیحا با یک فرمان یا کلیک)، شروع می کنیم به نام تست های خودکار، واحد یا غیر واحد.
عمدتاً هدف این آزمایشها (حداقل در توسعهدهندگان وب) این است که مطمئن شوند همه صفحات برنامه بدون مشکل و بدون خطاها یا باگهای مهلک (توقف برنامه) ارائه میشوند.
تست واحد
تست واحد یک فرآیند توسعه نرم افزار است که در آن کوچکترین بخش های کد، واحدها، به طور مستقل برای عملکرد صحیح آزمایش می شوند. در اینجا یک مثال در Ruby آورده شده است:
test “should return active users” do
active_user = create(:user, active: true)
non_active_user = create(:user, active: false)
result = User.active
assert_equal result, [active_user]
end
تست تابعی
تست تابعی تکنیکی است که برای بررسی ویژگیها و عملکرد سیستم یا نرمافزار استفاده میشود که برای پوشش تمام سناریوهای تعامل کاربر، از جمله مسیرهای خرابی و موارد مرزی طراحی شده است.
test "should get index" do
get :index
assert_response :success
assert_not_nil assigns(:object)
end
تست یکپارچه سازی
هنگامی که ماژول ها واحد آزمایش شدند، آنها یک به یک و به صورت متوالی یکپارچه می شوند تا رفتار ترکیبی را بررسی کنند و تأیید کنند که الزامات به درستی اجرا شده اند.
test "login and browse site" do
# login via https
https!
get "/login"
assert_response :success
post_via_redirect "/login", username: users(:david).username, password: users(:david).password
assert_equal '/welcome', path
assert_equal 'Welcome david!', flash[:notice]
https!(false)
get "/articles/all"
assert_response :success
assert assigns(:articles)
end
تست در دنیای ایده آل
آزمایش به طور گسترده در صنعت پذیرفته شده است و منطقی است. تست های خوب به شما اجازه می دهد:
- کیفیت کل برنامه شما را با کمترین تلاش انسانی تضمین می کند
- باگها را راحتتر شناسایی کنید، زیرا دقیقاً میدانید که کد شما از کجا شکسته شده است
- مستندات خودکار برای کد خود ایجاد کنید
- از «یبوست کدنویسی» اجتناب کنید، که به گفته برخی از دوستان در Stack Overflow، روشی طنز آمیز برای گفتن است، “وقتی نمیدانید بعد چه بنویسید، یا کار دلهرهآوری در پیش دارید، با نوشتن کوچک شروع کنید”.
من میتوانم در مورد اینکه تستها چقدر عالی هستند، و اینکه چگونه جهان را تغییر دادند، ادامه دهم، اما نکته را متوجه شدید. از نظر مفهومی، تست ها عالی هستند.
تست در دنیای واقعی
در حالی که هر سه نوع تست دارای امتیازاتی هستند، اما در اکثر پروژه ها نوشته نمی شوند. چرا؟ خوب، بگذارید آن را تجزیه کنم:
زمان / Deadlines
هرکسی Deadline دارد و نوشتن تست های تازه می تواند مانع از رسیدن به آن شود. نوشتن یک برنامه کاربردی و تست های مربوطه می تواند یک و نیم (یا بیشتر) زمان ببرد. اکنون، برخی از شما با این موضوع موافق نیستید، و در نهایت به صرفهجویی در زمان اشاره میکنید، اما فکر نمیکنم اینطور باشد و دلیل آن را در «تفاوت عقاید» توضیح خواهم داد.
مشکلات مشتری
اغلب، مشتری واقعاً نمیداند آزمایش چیست یا چرا برای برنامه ارزش دارد. مشتریان تمایل بیشتری به تحویل سریع محصول دارند و بنابراین آزمایش برنامهریزی شده را غیرمولد میدانند.
یا ممکن است به همین سادگی باشد که مشتری بودجه لازم برای پرداخت زمان اضافی لازم برای اجرای این آزمایش ها را ندارد.
عدم آگاهی
گروه بزرگی از توسعه دهندگان در دنیای واقعی وجود دارند که از وجود آزمایش اطلاعی ندارند. در هر کنفرانس، میتآپ، سر کار با توسعهدهندگانی ملاقات میکنم که نمیدانند چگونه تست بنویسند، نمیدانند چه چیزی را تست کنند، نمیدانند چگونه چارچوبی را برای آزمایش تنظیم کنند، و غیره. آزمایش دقیقاً در دوره های آموزشی داده نمیشود، و راهاندازی/یادگیری چارچوبی برای اجرای آنها میتواند دردسرساز باشد. بنابراین بله، یک مانع قطعی برای ورود وجود دارد.
“کار زیادی است”
تست های رایتینگ هم برای برنامه نویسان جدید و هم برای برنامه نویسان باتجربه، حتی برای آن دسته از افراد نابغه که دنیا را تغییر می دهند، می تواند طاقت فرسا باشد، و در نهایت، نوشتن تست ها هیجان انگیز نیست. ممکن است کسی فکر کند، "چرا باید درگیر کارهای پرمشغله غیر هیجان انگیز باشم، در حالی که می توانم یک ویژگی اصلی را با نتایجی که مشتری من را تحت تاثیر قرار می دهد پیاده سازی کنم؟" بحث سختی است.
آخرین، اما نه کم اهمیت، نوشتن تست سخت است و اکثرا برای آن آموزش ندیده اند.
و یک نکته دیگه که refactoring با تست واحد جالب نیست.
تفاوت در عقیده
به نظر من، تست واحد برای منطق الگوریتمی منطقی است، اما برای هماهنگ کردن کدهای زندگی چندان منطقی نیست.
بعضی ها ادعا میکنند که حتی اگر زمان بیشتری را برای نوشتن تستها صرف میکنید، ساعتها بعد هنگام اشکالزدایی یا تغییر کد در شما صرفهجویی میکند. من خواهش می کنم تفاوت داشته باشم و یک سوال ارائه کنم: آیا کد شما ثابت است یا همیشه تغییر می کند؟
برای بسیاری از ما، همیشه در حال تغییر است. اگر نرمافزار موفقی مینویسید، همیشه ویژگیها را اضافه میکنید، موارد موجود را تغییر میدهید، آنها را حذف میکنید، هر چیز دیگری، و برای تطبیق با این تغییرات، باید به تغییر تستهای خود ادامه دهید و تغییر تستهایتان زمان میبرد.
اما، شما به نوعی آزمایش نیاز دارید
هیچ کس استدلال نخواهد کرد که نبود هر نوع آزمایش بدترین حالت ممکن است. پس از ایجاد تغییرات در کد خود، باید تأیید کنید که واقعاً کار می کند. بسیاری از برنامه نویسان سعی می کنند اصول اولیه را به صورت دستی آزمایش کنند: آیا صفحه در مرورگر رندر می شود؟ آیا فرم ارسال می شود؟ آیا محتوای صحیح نمایش داده می شود؟ و غیره، اما به نظر من، این وحشیانه و ناکارآمد است.
چه چیزی به جای آن استفاده کنیم
هدف از آزمایش یک برنامه وب، چه به صورت دستی یا خودکار، تأیید این است که هر صفحه داده شده در مرورگر کاربر بدون باگ ارائه شده است و محتوای آن را به درستی نشان می دهد. یکی از راهها (و در بیشتر موارد، یک راه سادهتر) برای رسیدن به این هدف، ارسال درخواستهای HTTP به نقاط انتهایی برنامه و تجزیه پاسخ است. کد پاسخ به شما می گوید که آیا صفحه با موفقیت تحویل داده شده است یا خیر. با تجزیه بدنه پاسخ درخواست HTTP و جستجوی منطبقهای رشته متنی خاص، آزمایش محتوا آسان است، یا میتوانید یک قدم خوشبینتر باشید و از کتابخانههای اسکرپینگ وب مانند nokogiri استفاده کنید.
اگر برخی از نقاط پایانی نیاز به ورود کاربر دارند، میتوانید از کتابخانههای طراحیشده برای خودکارسازی تعاملات (ایدهآل برای انجام آزمایشهای یکپارچهسازی) مانند مکانیزه کردن برای ورود به سیستم یا کلیک کردن روی پیوندهای خاص استفاده کنید. در واقع، در تصویر بزرگ تست خودکار، این بسیار شبیه یکپارچه سازی یا آزمایش تابعی است (بسته به نحوه استفاده از آنها)، اما نوشتن آن بسیار سریعتر است و می تواند در یک پروژه موجود گنجانده شود، یا به پروژه جدید اضافه شود. ، با تلاش کمتری نسبت به تنظیم کل چارچوب تست.
موارد لبه هنگام برخورد با پایگاه داده های بزرگ با طیف وسیعی از مقادیر، مشکل دیگری را ایجاد می کنند. آزمایش اینکه آیا برنامه ما در تمام مجموعه داده های پیش بینی شده به خوبی کار می کند یا خیر، می تواند دلهره آور باشد.
یکی از راههای انجام آن این است که تمام موارد لبه را پیشبینی کنید (که صرفاً دشوار نیست، اغلب غیرممکن است) و برای هر یک تست بنویسید. این به راحتی می تواند صدها خط کد (وحشتناک را تصور کنید) و نگهداری آن دشوار است. با این حال، با درخواستهای HTTP و تنها یک خط کد، میتوانید چنین موارد لبهای را مستقیماً روی دادههای تولیدی که به صورت محلی در دستگاه توسعهدهندهتان یا روی یک سرور مرحلهای دانلود شده است، آزمایش کنید.
البته حالا این تکنیک تست یک گلوله نقره ای نیست و مانند هر روش دیگری دارای کاستی های زیادی است، اما من این نوع تست ها را سریعتر و آسانتر برای نوشتن و اصلاح می دانم.
در عمل: تست با درخواست های HTTP
از آنجایی که قبلاً ثابت کردهایم که نوشتن کد بدون هیچ نوع تست همراه ایده خوبی نیست، آزمایش اولیه من برای کل برنامه این است که درخواستهای HTTP را به تمام صفحات آن به صورت محلی ارسال کنم و هدر های پاسخ را برای یک کد 200 (یا دلخواه).
به عنوان مثال، اگر بخواهیم تست های بالا (آنهایی که به دنبال محتوای خاص و یک باگ مرگبار هستند) با یک درخواست HTTP به جای آن (در Ruby) بنویسیم، چیزی شبیه به این خواهد بود:
# testing for fatal error
http_code = `curl -X #{route[:method]} -s -o /dev/null -w "%{http_code}"
#{Rails.application.routes.url_helpers.
articles_url(host: 'localhost', port: 3000)
}`
if http_code !~ /200/
return “articles_url returned with #{http_code} http code.”
end
# testing for content
active_user = create(:user, name: “user1”, active: true)
non_active_user = create(:user, name: “user2”, active: false)
content = `curl #{Rails.application.routes.url_helpers.active_user_url(host: 'localhost', port: 3000)
}`
if content !~ /#{active_user.name}/
return “Content mismatch active user #{active_user.name} not found in text body”
#You can customise message to your liking
end
if content =~ /#{non_active_user.name}/
return “Content mismatch non active user
#{active_user.name} found in text body” #You can customise message to your liking
end
خط curl -X #{route[:method]} -s -o /dev/null -w "%{http_code}" #{Rails.application.routes.url_helpers.articles_url(host: 'localhost', port: 3000 ) } موارد آزمایشی زیادی را پوشش می دهد. هر روشی که باعث ایجاد خطا در صفحه مقاله شود، در اینجا مشاهده می شود، بنابراین صدها خط کد را در یک تست به طور موثر پوشش می دهد.
قسمت دوم که خطای محتوا را به طور خاص تشخیص می دهد، می تواند چندین بار برای بررسی محتوای یک صفحه استفاده شود. (با استفاده از مکانیز می توان به درخواست های پیچیده تر رسیدگی کرد، اما این فراتر از محدوده این مقاله است.)
اکنون، در مواردی که میخواهید آزمایش کنید که آیا یک صفحه خاص روی یک مجموعه بزرگ و متنوع از مقادیر پایگاه داده کار میکند (به عنوان مثال، الگوی صفحه مقاله شما برای همه مقالههای پایگاه داده تولید کار میکند)، میتوانید این کار را انجام دهید:
ids = Article.all.select { |post| `curl -s -o /dev/null -w “%{http_code}”
#{Rails.application.routes.url_helpers.article_url(post, host: 'localhost', port: 3000)
}`.to_i != 200).map(&:id)
return ids
با این کار آرایهای از id همه مقالات موجود در پایگاه داده که رندر نشدهاند را برمیگرداند، بنابراین اکنون میتوانید به صورت دستی به صفحه مقاله خاص بروید و مشکل را بررسی کنید.
اکنون، میدانم که این روش آزمایش ممکن است در موارد خاصی مانند آزمایش یک اسکریپت مستقل یا ارسال ایمیل کار نکند، و به طور غیرقابل انکاری کندتر از تستهای واحد است، زیرا ما در حال برقراری تماس مستقیم با یک نقطه پایانی برای هر آزمایش هستیم، اما زمانی که شما نمی توانید تست های واحد یا تست های تابعی یا هر دو را داشته باشید، این بهتر از هیچ است.
چگونه می خواهید ساختار این آزمون ها را انجام دهید؟ با پروژه های کوچک و غیر پیچیده، می توانید تمام تست های خود را در یک فایل بنویسید و هر بار آن فایل را قبل از انجام تغییرات خود اجرا کنید، اما اکثر پروژه ها به مجموعه ای از تست ها نیاز دارند.
من معمولاً دو تا سه تست در هر نقطه پایانی می نویسم، بسته به چیزی که آزمایش می کنم. همچنین میتوانید محتوای تکی را آزمایش کنید (مشابه آزمایش واحد)، اما من فکر میکنم که این کار اضافی و کند خواهد بود، زیرا برای هر واحد یک تماس HTTP برقرار خواهید کرد. اما، از سوی دیگر، آنها تمیزتر و قابل درک خواهند بود.
توصیه میکنم تستهای خود را در پوشه تست معمولی خود قرار دهید و هر نقطه پایانی اصلی فایل مخصوص به خود را داشته باشد (مثلاً در Rails، هر مدل/کنترلر هر کدام یک فایل دارد)، و این فایل را میتوان با توجه به آنچه که ما داریم به سه قسمت تقسیم کرد.
نتیجه
تست درخواست HTTP ممکن است بهترین گزینه برای شما باشد اگر:
شما با یک برنامه وب کار می کنید
شما در تنگنای زمانی هستید و می خواهید سریع چیزی بنویسید
شما در حال کار با یک پروژه بزرگ هستید، پروژه از قبل موجود که در آن تستها نوشته نشدهاند، اما هنوز راهی برای بررسی کد میخواهید.
کد شما شامل درخواست و پاسخ ساده است
شما نمی خواهید بخش زیادی از زمان خود را صرف نگهداری تست ها کنید (من در جایی مطالعه کردم تست واحد = جهنم تعمیر و نگهداری)
شما می خواهید آزمایش کنید که آیا یک برنامه کاربردی در تمام مقادیر موجود در پایگاه داده موجود کار می کند یا خیر
آزمایش سنتی زمانی ایده آل است که:
شما با چیزی غیر از یک برنامه وب، مانند اسکریپت ها سر و کار دارید
شما در حال نوشتن کدهای پیچیده و الگوریتمی هستید
شما زمان و بودجه ای دارید که باید به نوشتن تست ها اختصاص دهید
کسب و کار به نرخ بدون اشکال یا کم خطا (مالی، پایگاه کاربر بزرگ) نیاز دارد.
اکنون باید روشی برای تست داشته باشید که میتوانید بهطور پیشفرض روی آن استفاده کنید، روشی که میتوانید وقتی روی آن فشار میآورید حساب کنید.
