• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Testing the untestable
 

Testing the untestable

on

  • 863 views

Introductions to and considerations for adopting test driven development

Introductions to and considerations for adopting test driven development

Statistics

Views

Total Views
863
Views on SlideShare
863
Embed Views
0

Actions

Likes
0
Downloads
0
Comments
0

0 Embeds 0

No embeds

Accessibility

Categories

Upload Details

Uploaded via as Microsoft PowerPoint

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment
  • אני אעבור מאוד בקצרה על יוניט טסטינג, יתרונות, חסרונות, אח"כ אני אספר קצת על הרקע להחלטה של שינוי מתודולוגיית הפיתוח, וההשלכות המקצועיות של ההחלטה הזו, ולבסוף אני אעבור על טכניקות פיתוח של הכנסת קוד שעל פניו נראה בלתי אפשרי לבדיקה, לתוך סביבת בדיקה בצורה בטוחה, בלי לשנות את ההתנהגות שלו בתהליך, עם דוגמה אמיתית ממצולות הקוד בייס של וויקס.
  • אז קודם כל קצת על יוניט טסטינג, האבן יסוד של TDD. על רגל אחת, מדובר על קוד שמריץ קוד ובודק את התוצאות בהנתן כל מני אינפוטים.בתחילת כל טסט, אינסנטס חדש של הקלאס נוצר, ופעולה אחת בלבד מתבצעת – בד"כ קיראה לפונקציה עם ערך כלשהו. לבסוף, בודקים שהפעולה גרמה לתוצאה הצפויה. אם כן, הטסט עובר. אם לא, הטסט נכשל.הסייקל פיתוח ב TDDהוא פחות או יותר ככה: מפתח כותב פעולה אטומית, שיכולה להיות פיצ'ר, חלק מפיצ'ר, תיקון באג, בד"כ לא יותר מכמה שורות קוד, ובמבקביל כותב טסטים או מתקן טסטים כדי לבדוק שהקוד שהוא הרגע כתב עושה מה שהוא אמור לעשות. בגישה הקלאסית אומרים תמיד לכתוב הטסט קודם ואז את הקוד הפשוט ביותר שגורם לטסט הזה לעבור, אבל אני לא דוגמטי לגבי הסדר, וכך גם רוב מפתחי ה TDD שדיברתי איתם.אחרי שהפיצ'ר כתוב וכל הטסטים כתובים ועוברים, אפשר לשכתב את הקוד כך שיהיה יותר יפה או יותר יעיל. כיוון שיש לנו טסטים שמוודאים את תקינות הקוד, אנחנו חופשיים לשכתב בסייקלים מאוד מהירים ובלי המון מחשבה על שינוי פוקציונליות, אם עשינו טעות, לפחות טסט אחד אמור להכשל, ומייד יש לנו פידבק שעשינו טעות.ולבסוף, כמובן חוזרים וממשיכים בכתיבה של פעולה אטומית חדשה.
  • אני לא אעבור על כל הנקודות, רק אציין שבגדול אפשר לומר שזמן הפיתוח מתארך כשעובדים עם טסטים. מן הסתם, מדובר בעוד קוד שצריך לכתוב.יוניט טסטים גם לא נועדו לבדוק ממשק או תוצאות ויזואליות. לדוגמא, המנגנון צביעה של וויקס נבדק ע"י טסטים בכל שלב בתהליך, חוץ מהאם באמת בסוף אובייקטים נצבעו כמו שהם אמורים להצבע.
  • כל הנקודות האלה מסתכמות פחות או יותר בכך שלאורך זמן, כמות הבאגים שהמוצר צובר פחותה בהרבה ביחס לפיתוח ללא טסטינג. זוהי נקודה שנבדקה ע"י מספר מחקרים ונמצאה כנכונה.
  • אז החסרון העיקרי של יוניט טסטינג זה זמן פיתוח ראשוני ארוך יותר, והיתרון העיקרי של יוניט טסטינג זה ירידה משמעותית בהצטברות באגים. אז לפרוייקטים ארוכי טווח, עבודה עם יוניט טסטים צריכה להיות החלטה יד מובנת מאליה.י
  • עם קצת נסיון ביוניט טסטינג, בדיקה של קוד חדש היא עניין של מה בכך. האתגר הטכני הוא, כיצד בודקים קוד ישן שצריך לבצע בו שינויים? קלאסים שלא נכתבו במתודולוגיה של טסטים נוטים להיות מאוד תלותיים בקלאסים אחרים, פשוט כי יותר טבעי לכתוב עם תלות מאשר בלי. הנה דוגמה מהקוד בייס שלנו. לאחרונה הכנסנו את האדיטור שלנו לתוך מעטפת פלקס, מתוך כוונה להמיר לאט לאט את ה UI שלנו לפלקס. יש לנו דיאלוג להוספת אייטמים, שמשתמשים בה בהרבה מקומות במוצר, ובין השאר מתוך דיאלוג שהומר לפלקס. כדי לפתור כל מני בעיות, היינו צריכים שהדיאלוג הוספה הזה, כשהוא נפתח מהדיאלוג הפלקסי, ייפתח בתוך מעטפת פלקסית גם הוא. הקוד שפותח את הדיאלוג הוספה נמצא בתוך קלאס מאוד ישן ומאוד גדול, שנקרא EdWizardManager
  • אנחנו בוויקס עובדים על פרוייקט ארוך טווח, ולא אימצנו טסטינג מתחילת הדרך. בתחילת קיומה, כמו כל סטארט אפ, וויקס היתה במירוץ כנגד השעון, להגיע למוצר מוכר לפני שכסף ההשקעה זולג או שמתחרים משתלטים על השוק עם מוצר מתחרה. הפיתוח המאוד מהיר הוכיח את עצמו בסופו של דבר, כשוויקס הצליחה לצאת מנצחת עם מוצר מוביל בשוק, אבל הקוד בייס של המוצר הגיע למצב שהיה ריסקי לבצע בו שינויים. מצד אחד המון לקוחות תלויים ביציבות שלו, ומצד שני הוא לא כתוב בצורה שנותנת פידבק על תוצאה של שינויים. גרסאות התחילו להתארך ולהסתבך, הQA נהיה תהליך יקר, והבנו שכדי להתקדם בצורה משמעותית מעתה והלאה, צריך לשנות את תפיסת הפיתוח.בהתחלה הוחלט על שכתוב. נשכרו אנשים והוקם צוות שהתחיל לעבוד על וויקס 2, אבל באותה תקופה גם התחילה הפניקה בתעשייה מהמהלך המוצהר של אפל כנגד פלאש. וויקס לא רצתה להשאר מחוץ לשוק ההולך וגדל של האייפונים, והוחלט לנטוש את עבודת השכתוב, לטובת כתיבת מוצר חדש שנותן שירות בניית אתרים דומה לוויקס בפלאש, אבל ב HTML. למרות שבאותה תקופה האמנתי בשכתוב, וגם רציתי להיות מעורב בשכתוב מתוך עניין מקצועי, בדיעבד אני מאוד שמח שנמנענו מזה. הסיכון והעלויות של שכתוב מוצר הם עצומים, ולאחר ששינינו את תפיסת הפיתוח על המוצר הקיים, הסתבר גם שהרבה מהבעיות של הקוד בייס המקורי הן פתירות. אז שינינו את תפיסת הפיתוח. הוחלט לאמץ בכל מחלקות הR&D חזון של CI, קוניטינוס אינטגריישן. בשביל הפלאש קליינט, שנכתב במתודולוגיה הפוכה לחלוטין לזו של CI, היינו די פסיימים בהתחלה. איך נגיע אי פעם לCI, אם אפילו כיסוי חלקי של המערכת בטסטים נראה בלתי אפשרי? אבל גילינו שכיסוי של שינויים מביא את עיקר התועלת, כך שמה שלא משתנה ונשאר לא בדוק, הוא לא פקטור, כל עוד השינויים החדשים כן בדוקים.
  • לפני שכתבתי את השינוי, רציתי לבדוק אם אני יכול בכלל לשים את הקוד הזה בסביבת בדיקה. כמו שאתם רואים, כבר בקונסטרקטור הקלאס הזה צריך כל מני תלויות שלא קשורות בכלל לשינוי שאני הולך לעשות.
  • החלטתי לאמץ גישה אופטימית. הקוד שאני רוצה לשנות לא תלוי בדברים האלה, אז אם הקלאס יווצר כשהדברים האלה הם נאל, מה טוב. הוספתי טסט שלא עושה כלום, רק כדי לראות שהיצירה של הקלאס עוברת בלי תקלות.
  • הגישה האופטימית נחלה כשלון במקרה הזה. אפילו טסט ריק לא מצליח לרוץ בלי תקלות , בגלל שעצם היצירה של הקלאס צריכה אובייקטים אחרים, שאולי גם הם צריכים אובייקטים כדי להווצר כמו שצריך.היתה לי בנקודה הזו כמה אפשרויות: או לשנות את הקונסטרקטור של הקלאס כדי שאני אוכל ליצור אותו בלי תלויות, או לשנות את הטיפוסים כדי שאני אוכל ליצור את הקלאס עם זיופים של התלויות, או להוציא את הקוד שאני עובד עליו מהקלאס.מכל האפשרויות האלה, האחרונה היתה הכי פשוטה וכללה שינויים במקומות שאני רוצה לשנות בכל מקרה. האחרות כללו שינויים שלא קשורים בכלל לשינוי שאני רוצה, וגם נשמעו לי כמו שינויים הרבה יותר מסוכנים

Testing the untestable Testing the untestable Presentation Transcript

  • Testing the Untestable
    Taming production code
    Roy Klein
    Flash Client Team Leader
    Wix.com
  • Overview
    Test Driven Development Overview
    The motivations for and challenges of testing our production code
    Techniques + a real life example
  • Unit Testing
    Exercise classes in an isolated environment
    Verify that actions on the code produce predictable, consistent results
    Write Tests
    Write Code
    Pass All Tests
    Refactor
  • The price of Unit Testing
    New logic is more time consuming to write
    Old logic may require extensive Refactoring to be testable
    Amount of code to maintain increases
    Illusion of being bug free when tests pass
    No Stage in FlexUnit
    The actual visual output can’t be fully tested
  • The benefits of Unit Testing
    Better definition and understanding of the code’s purpose
    Helps improve code design
    Empowers refactoring
    Documents the usage of the code
    A small, quick environment to debug in
    Provides immediate feedback when the code changes (Adding/modifying functionality, Refactoring, Merging)
  • TDD ROI conclusions
    The price:
    Initial additional development time
    The benefit:
    Cumulative reduction in defects
    The conclusion:
    No brainer for long term projects
  • The Challenges
    Classes and functions have multiple responsibilities
    Classes are strongly coupled to other classes
    Some of these couplings are to Singletons
    Ambiguous dependencies (Objects, callbacks)
    In short, code changes have unknown side effects that are hard to foresee
  • The Motivation
    Our production code is the result of several years of rat chase Rapid Development
    Refactoring core code is dangerous
    Augmenting core capabilities is expensive
    Reducing code volatility may save us from a dangerous rewrite, or allow us to reuse code when a rewrite occurs
    Moving to CI is key to remaining competitive
  • Legacy Testing Heuristic
    Before making the change, write passing tests for the area around your change
    These tests are intended to preserve the behavior of the code prior to your change
    Add failing tests that define the change you’re making
    Apply your change and make all tests pass
  • Replace Dialog: Dependency Breaking
  • Replace Dialog: Dependency Breaking
  • Case Study 1: Replace Gallery
  • Safe Modification: Function extraction
    Preserve the original functions’ signature.
  • 1. Preserve function signature
  • Safe Modification: Function extraction
    Preserve the original functions’ signature.
    Extract the function to its own class
  • 2. Extract the modified function
  • Safe Modification: Function extraction
    Preserve the original functions’ signature.
    Extract the function to its own class
    Call the new function with the same signature, and a reference to an interface representing the original class
  • 3. Reference original class
  • Safe Modification: Function extraction
    Preserve the original functions’ signature.
    Extract the function to its own class
    Call the new function with the same signature, and a reference to an interface representing the original class
    Use the IDE to determine what calls are to be redirected to the original class
  • 4. Use IDE to find what’s missing
  • Safe Modification: Function extraction
    Preserve the original functions’ signature.
    Extract the function to its own class
    Call the new function with the same signature, and a reference to an interface representing the original class
    Use the IDE to determine what calls are to be redirected to the original class
    Put those calls in the interface
  • 5. Put what’s missing in the interface
  • 5. Put what’s missing in the interface
  • Safe Modification: Function extraction
    Preserve the original functions’ signature.
    Extract the function to its own class
    Call the new function with the same signature, and a reference to an interface representing the original class
    Use the IDE to determine what calls are to be redirected to the original class
    Put those calls in the interface
    Create a mock class implementing the interface for testing
  • 6. Create a mock class
  • Test can now execute!
  • Breaking encapsulation on purpose
    Encapsulation is a tool that makes the code usage easier to understand
    The need to break it often points to problem in the design of the code usage
    Problems in design can’t be solved without Unit Tests in place
    The break can help in understanding what needs to be changed
    Damage can be minimized with self documenting code
  • Breaking encapsulation on purpose
  • Solving Singletons dependency
    You can’t use a testing instance
    Create a testing only function to set a testing instance
  • Solving Static dependencies
    You can’t subclass or mock a static class
    Replace reference to static variables with getters and static functions to local functions
    Subclass and override with a test class to use your own data/logic
    In case of a massive amount of static calls, use the same class name in your testing project to override the original class, and delegate all calls to another class
  • Conclusion
    Getting legacy code into a testing environment is possible
    Most of the benefits of TDD can be achieved with partial coverage
    Adapting TDD during any stage of the development is worthwhile. The bigger the project, the more beneficial it is
  • Q & A
    • Agile Principles, Patterns and Practices: Robert Martin
    • Working Effectively With Legacy Code: Michael Feathers
    Further reading: