הבלוג של ינון פרק

טיפים קצרים וחדשות למתכנתים

בדיקות אוטומטיות לאתרים עם Python ו Selenium

21/06/2018

הכלי הכי חשוב של מפתחי Web שעובדים בצוותים קטנים או עצמאיים הוא בדיקות אוטומטיות. ולא משנה מה המערכת שלכם בעזרת סלניום אפשר לכתוב את הבדיקות הראשונות תוך פחות משעה.

בשבוע הבא ביום חמישי בבוקר אעביר Webinar פתוח לקהל הרחב בנושא בדיקות אוטומטיות בו נכתוב יחד בדיקות אוטומטיות ב Python ו Selenium לאתרים קיימים. לינק להרשמה מופיע בסוף הפוסט. עד אז כמה דוגמאות קוד זריזות שתראו מה הולך להיות שם ואולי תשחקו עם זה גם לפני הוובינר ותבואו עם שאלות.

קובץ בדיקות אוטומטיות ב Python מכיל מחלקה היורשת מ unittest.TestCase. הנה דוגמא לקובץ עם הבדיקה הכי פשוטה שהצלחתי לחשוב עליה:

import unittest

class MyTestCase(unittest.TestCase):
    def test_something(self):
        self.assertEqual(True, True)


if __name__ == '__main__':
    unittest.main()

הרצה של הקובץ מציגה את הפלט:

.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

מה שאומר שהבדיקה עברה בהצלחה. שינוי קטן בבדיקה כדי להכשיל אותה יראה כך:

import unittest

class MyTestCase(unittest.TestCase):
    def test_something(self):
        self.assertEqual(True, False)


if __name__ == '__main__':
    unittest.main()

שימו לב ששיניתי את המילה True ל False בשורת ה assertEqual וכעת הפלט נראה כך:

F
======================================================================
FAIL: test_something (__main__.MyTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "simpletest.py", line 5, in test_something
    self.assertEqual(True, False)
AssertionError: True != False

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=1)

עד לכאן זה היה קל: בדיקה אוטומטית היא סקריפט פייתון שמריץ איזשהו קוד השוואה. כשהבדיקה מצליחה נקבל על המסך הדפסה קצרה עם המילה OK וכשהבדיקה נכשלת נקבל פירוט של הכישלון בצירוף ההודעה FAILED.

בואו נוסיף את סלניום לקערה. סלניום היא ספרית פייתון לשליטה אוטומטית בדפדפן. היא תפתח את דפדפן האינטרנט שתבקשו במצב שליטה מיוחד ותגרום לדפדפן לבצע איזה פעולות שתחליטו דרך הקוד.

הקוד הבא למשל ייכנס לדף רשימת הפוסטים בבלוג וידפיס על המסך את כותרת הדף:

from selenium import webdriver

driver = webdriver.Firefox()
driver.get('https://www.tocode.co.il/blog')
print(driver.title)

driver.close()

שילוב בין השניים הוא שיאפשר לנו לכתוב בדיקות אוטומטיות. כך לדוגמא נוכל לבדוק שבעמוד הבית באתר זה מופיעים בדיוק 4 קישורים לקורסים:

import unittest
from selenium import webdriver

class MyTestCase(unittest.TestCase):
    def setUp(self):
        self.driver = webdriver.Firefox()

    def tearDown(self):
        self.driver.close()

    def test_something(self):
        self.driver.get('https://www.tocode.co.il/')
        course_links = self.driver.find_elements_by_css_selector('.nm-block a[href^="/bundles/"]')

        self.assertEqual(4, len(course_links))


if __name__ == '__main__':
    unittest.main()

רוצים לכתוב איתי עוד דוגמאות לבדיקות, וללמוד יותר לעומק על סלניום? מעולה כי יש וובינר בחמישי הבא. הרשמה בקישור:

https://zoom.us/meeting/register/3abdb0d0b6f403b7c5b9141539e44ee6

מה כל כך הפריע לי בניוזלטר של רפליט השבוע

20/06/2018

הדרך הכי טובה לכתוב קוד מטעה היא להתעלם מ Style Guides של השפה. במיוחד היום כשיש המון שפות תכנות וכל הזמן רואים דוגמאות קוד בשפות חדשות ונגיד את האמת יש גם הרבה שפות שדומות אחת לשניה. כשאתם כותבים רובי כמו שכולם כותבים רובי אני מיד מבין שזה רובי. אבל כשאתם כותבים רובי כמו שכותבים פייתון זה מבלבל.

ועכשיו לעניין שלנו - הניוזלטר מתחיל בשורות המאוד ברורות:

require 'newsletter.rb'
puts greeting(user)

עד לפה הכל טוב ואז הם עוברים לספר על אירועים וכל הדברים שקורים שם ברפליט (באמת נשמע כמו אחלה חברה וגם האתר מוצלח). ואז בסוף הפרידה הכואבת:

newsletter.close()

הסוגריים כמובן מסיפור אחר.

כשאתם כותבים קוד במיוחד אם זה קוד ש(הרבה) אנשים אחרים הולכים לראות- תעשו טובה ולכו לקרוא קודם את ה Style Guide. לא כדאי להיתפס מחוץ לאופנה. קישורים? קבלו:

רפליט, שהוא אחד האתרים הכי מדליקים שפגשתי לאחרונה:

repl.it

וכללי ההתנהגות של רובי, שלא יתפסו אתכם עם סוגריים במקום הלא נכון:

https://github.com/rubocop-hq/ruby-style-guide

נשבר? שיישבר. האלטרנטיבה יותר גרועה

19/06/2018

הפוסטים של הימים האחרונים בבלוג נעלמו לי הבוקר. חיפוש מהיר במקור הדף חשף שהבעיה היא ב HTML לא תקני שאני כתבתי, משהו בסגנון הזה:

<a href="https://www.google.com'>go to google</a>

רואים את הבעיה? גם אני לא ראיתי. אבל אם מסתכלים טוב רואים שיש מרכאות כפולות בצד אחד של הכתובת וגרש בודד בצד השני.

אבל המרכאות הן בכלל לא הסיפור כאן. הסיפור הגדול יותר הוא על שגיאת הייבוא (שקרתה) כשהאתר ניסה לאכול את הפוסט עם השגיאה, והטיפול הלא נכון במצב הזה: הפוסט נכנס בצורה חלקית ושבר את כל הפוסטים האחרים של אותו שבוע.

המבחן של קוד הוא לא כשהכל עובד אלא כשדברים נשברים. קוד טוב יודע להישבר טוב ולתחום את ההפסד. נשבר? שיישבר, אבל שלא ייקח איתו את כל המערכת.

על שמות משתנים ופונקציות

18/06/2018

אנשים אוהבים לדבר על שפות תכנות "לכתיבה בלבד" כלומר כאלה שאי אפשר לקרוא קוד שכתוב בהן. אנחנו שוכחים שקוד קריא הוא מאפיין של המתכנתים הרבה יותר מאשר של השפה. הנה דוגמא קצרה עם אותה התוכנית באותה שפה ובשתי גירסאות שונות. תגידו אתם את מה הייתם מעדיפים לקרוא:

גירסא 1:

from random import randint

x = randint(1, 100)
while True:
    y = int(input('Please guess the value: '))
    if randint(1, 100) > 80:
        messages = ['Too large', 'Too small']
        print(messages[randint(0, 1)])

    else:
        if x > y:
            print('Too small')
        elif x < y:
            print('Too large')
        else:
            print('Bingo! You win')
            break

גירסא 2:

from random import randint

def should_lie(*_, lying_frequency=20):
    return randint(1, 100) < lying_frequency

def random_message():
    messages = ['Too large', 'Too small']
    return messages[randint(0, 1)]

secret_value = randint(1, 100)

while True:
    user_guess = int(input('Please guess the value: '))

    if should_lie(lying_frequency=20):
        print(random_message())
    else:
        if secret_value > user_guess:
            print('Too small')
        elif secret_value < user_guess:
            print('Too large')
        else:
            print('Bingo! You win')
            break

המעבר מגירסא 1 ל-2 דורש מעט מאוד מחשבה. כל מה שצריך הוא להבין את מטרת התוכנית ולתת שמות שמתאימים למטרה. אבל כשמגיעים לגירסא 2 חודש אחרי ברור לגמרי מה המשמעות של כל דבר. אפילו שהתוצאה ארוכה יותר בכמה שורות.

למה (ואיך) להוסיף Rate Limit ליישומים שלכם

17/06/2018

מגבלת קצב גישה (Rate Limit) היא המגבלה הכי חשובה שאתם שוכחים להוסיף ליישומים שלכם. בגרסאות שונות שלה המגבלה תגן עליכם ממתקפות מניעת שירות ובכך תעזור לבנות יישומים חסינים יותר.

בהגדרת המגבלות כדאי לקחת בחשבון את המצבים הבאים:

  1. נרצה לזהות ולחסום בקשות "חשודות", למשל ילדים שמריצים כל מיני סקריפטים אוטומטיים שמחפשים חורים ביישום שלנו.

  2. נרצה לחסום בקשות לגיטימיות שמגיעות בקצב מהיר מידי אם השרת שלנו לא מסוגל לעמוד בקצב (וכך להגן על שאר המשתמשים בשרת). לדוגמא נניח שפיצ'ר החיפוש באתר הוא מאוד מורכב וצורך הרבה משאבים, אז נרצה לוודא שאף פעם השרת לא מחפש יותר מ-X מילים באותו הזמן.

  3. נרצה לחסום פעולות מסוימות לפי משתמשים כדי לאכוף התנהגות לפי כללים שהגדרנו. למשל באתר מידע נרצה לאפשר למשתמשים לקרוא את דפי המידע, אבל אם משתמש קורא יותר מדי דפים בדקה אולי הוא בכלל הריץ סקריפט כדי לרוץ ולסרוק אוטומטית את כל האתר - ואת זה לא נרצה לאפשר.

הכלי שאני עובד איתו עבור הסעיף הראשון נקרא fail2ban. הוא רץ ברמת השרת בלי קשר ליישום ספציפי, קורא את קבצי הלוג ומזהה תבניות חריגות. כשמוצא הוא פשוט חוסם את כתובת ה IP הפוגענית. זה הקישור לדף הבית שלהם:

https://www.fail2ban.org/wiki/index.php/Main_Page

שני הסעיפים האחרים כבר קשורים ליישום ולכן הפיתרון אליהם תלוי בשפת הפיתוח והספריה שבחרתם. נראה שתי דוגמאות.

סביבת הפיתוח Flask המאפשרת פיתוח APIs בפייתון מציעה הרחבה בשם FlaskLimiter. הדוגמא שלהם מספרת את רוב הסיפור:

from flask import Flask
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

app = Flask(__name__)
limiter = Limiter(
    app,
    key_func=get_remote_address,
    default_limits=["200 per day", "50 per hour"]
)
@app.route("/slow")
@limiter.limit("1 per day")
def slow():
    return "24"

@route("/test")
@login_required
@limiter.limit("1 per day", key_func = lambda : current_user.username)
def test_route():
    return "42"

@app.route("/fast")
def fast():
    return "42"

הגדרת מגבלות ברירת המחדל אומרת שהנתיב fast מוגבל ל 200 פניות ביום ובכל מקרה לא יותר מ-50 פניות בשעה. הגדרה ספציפית שמופיעה לגבי הנתיב slow אומרת שניתן לפנות אליו לא יותר מפעם ביום והגדרה נוספת לגבי הנתיב test כבר לוקחת בחשבון את שם המשתמש ומגבילה כל משתמש להפעיל את הנתיב רק פעם אחת ביום.

לסביבת Rails יש פיתרון בשם rack-attack שלוקח גישה קצת שונה. הנה הדוגמא:

# config/initializers/rack_attack.rb (for rails apps)

Rack::Attack.throttle("requests by ip", limit: 5, period: 2) do |request|
  request.ip
end

# Throttle login attempts for a given email parameter to 6 reqs/minute
# Return the email as a discriminator on POST /login requests
Rack::Attack.throttle('limit logins per email', limit: 6, period: 60) do |req|
  if req.path == '/login' && req.post?
    req.params['email']
  end
end

הגדרות rack-attack נכתבות בקובץ איתחול יחיד עבור כל היישום. הבלוק הראשון חוסם גישות לפי כתובת IP כך שלא יתקבלו יותר מ-5 פניות בכל שתי שניות מאותה כתובת. הבלוק השני מגביל ניסיונות התחברות לפי מייל נתון ל 6 בדקה.

היתרון בגישה של Flask Limiter הוא שאנחנו רואים את ההגבלות כל הזמן כחלק מהגדרת הנתיב. יש בזה משהו מאוד נכון כי הגבלת קצב הגישה הרבה פעמים מושפעת ממה הפונקציה עושה. בחירת ערכי defaults טובים (בשתי הגישות) אומרת שאף פעם לא נשכח לשים מגבלת קצב כשיוצרים נתיב חדש.

עובדים בסביבות אחרות ורוצים לשתף איך אתם שמים מגבלת קצב ליישומים שלכם? אשמח לשמוע בתגובות.

שתי גירסאות של קורס מתקדם

16/06/2018

אפשר להתקדם לרוחב: לשמוע על כל הדברים המדליקים בעולם התוכן שלכם, על מה שחם היום ועל מה שעדיין בפיתוח.

ואפשר לחפור: לשאול שאלות על הדברים שאנחנו חושבים שיודעים ולפתור את אותן בעיות שוב ושוב כדי להכיר את כל הניואנסינים.

העדפה שלי- את הנושאים המדליקים וההדגמות שיפילו אותי מהכסא אני מעדיף לראות ביוטיוב. כך אני יכול לדלג על החלקים המשעממים. לכיתה נשמור את השאלות והדיונים. קורס מתקדם הוא כזה שעזר לי לראות טוב יותר דברים שחשבתי שאני מבין (וכן זה דורש נכונות ופתיחות של המדריך ושל כל המשתתפים. וזה לגמרי שווה את ההשקעה).

על קו הזינוק

15/06/2018

על קו הזינוק, לפני המירוץ, כדאי להזכיר למיכה (ולנו), שכמו שזוגיות לא בונים בתחרות גם לימוד תוכן חדש הוא לא מירוץ. אין סיבה לחפש קיצורי דרך "רק" כדי לפתור את התרגיל או הבעיה שעל הפרק. לימוד הוא כמו ג'וגינג: אנחנו לא רצים בשביל להגיע לסוף אלא בשביל הכושר.

תקראו את התיעוד. במלואו. ואז תמשיכו לחפש מקורות לימוד נוספים. ואז תדעו הכל על הנושא שעל הפרק. ואז לכו לפתור את הבעיה. ואז שוב בדרך אחרת.

וכן, קחו את הזמן. תהנו מהתהליך. אין מבחן בסוף.

מה הדבר הבא שכדאי ללמוד?

14/06/2018

את התשובה הקצרה אפשר לקחת מחתול אחד שכמו שאמר לאליס: זה תלוי לאן את רוצה להגיע.

בניגוד לאליס אנחנו יודעים לאן אנחנו רוצים להגיע ולכן גם התשובה הארוכה לא תהיה יותר מדי מסובכת:

הכי טוב ללמוד את הדבר שאתה מתעסק בו ביום-יום. יש ערך עצום בלהיות מומחה בטכנולוגיה מסוימת, מומחיות זה דבר שלוקח המון זמן לפתח ודורש המון פוקוס. לכן היום ההמלצה החד משמעית שלי היא לבחור טכנולוגיה ולדעת עליה הכל.

האתגר כאן הוא לא לכתוב עוד מאותו דבר ובאותו סגנון שאתה כבר כותב ביום-יום, אלא לצלול לעומק הטכנולוגיה וללמוד עליה דברים שבעבודה שוטפת לא היית מגיע ללמוד.

ומה לגבי הרחבת אופקים כללית? לנסות לבנות דברים חדשים בטכנולוגיות שאף פעם לא ראיתם? זה תלוי ברמת העניין שלכם בטכנולוגיה. כדאי לזכור שבשביל שיהיה למיומנות טכנית ערך כלכלי חייבים לעבור את שלב ה Tutorials ולהגיע לרמה שאתם שולטים טוב בשפה או בטכנולוגיה. לטובים ביותר שפגשתי זה לוקח מינימום שלושה חודשים של עבודה אינטנסיבית בטכנולוגיה זו. סוג כזה של השקעה קשה לבצע כמשהו נוסף על העבודה השוטפת שלכם.

איך לכתוב sleep ב JavaScript

שאלה שחוזרת אצל מתכנתים שמתחילים לעבוד עם JavaScript היא השאלה לגבי sleep. היינו שמחים לפעמים לכתוב טקסט על המסך, לחכות קצת ואז לכתוב עוד קצת טקסט. בתור שפה אסינכרונית ב JavaScript אי אפשר באמת לעצור את ריצת התוכנית בשביל לחכות. אבל גירסת ES8 של השפה הוסיפה תמיכה ב async/await איתם אפשר לבנות את sleep ואפילו די בקלות.

המשך קריאה

פיתוח שרת API עם Python ו Flask

12/06/2018

פלאסק היא סביבה לפיתוח Web בשפת Python. זוהי Micro Framework המבוססת על פלאגינים ולכן ליבת הספריה מאוד רזה, מה שאומר שקל מאוד ללמוד איך להתחיל להשתמש בה. באותה נשימה אגיד גם שקל לעשות טעויות. החברה הגדולה ביותר שמצאתי שמשתמשת בפלאסק היא Pinterest שם פלאסק היא הבסיס לשרת ה API של החברה, שרת המטפל במיליארדי בקשות ביום.

בפוסט זה אתאר בקצרה את פלאסק דרך כתיבת מספר תוכניות ונראה מה צריך כדי לבנות Backend API איתו.

המשך קריאה