Upgrade to Pro — share decks privately, control downloads, hide ads and more …

CAST 2013: Software Craftsmanship for Testers

CAST 2013: Software Craftsmanship for Testers

Testers can learn a great deal from the software craftsmanship movement. This talk, given with Matt Barcomb at CAST 2013, shows some great examples of things testers can learn from developers--and what devs can learn from testers.

Fd80f9c58b06270d42356dd77a32defa?s=128

Jim Holmes

August 27, 2013
Tweet

Transcript

  1. WHAT’S SOFTWARE CRAFTSMANSHIP More Importantly, Why Should I Care? Jim

    Holmes (@aJimHolmes) jim.holmes@telerik.com Matt Barcomb (@mattbarcomb) matt@odbox.co
  2. RESOURCES Source Code: https://github.com/jimholmes/PresentationDemos/tree/ master/CAST2013 Deck:https://speakerdeck.com/jimholmes/cast-2013- software-craftsmanship-for-testers

  3. WHAT’S SOFTWARE CRAFTSMANSHIP? Grew out of the programming community Evolved

    after YEARS of PAINFULLY learned lessons Movement is still evolving
  4. WHY CARE? Maintainability Continued delivery of value to stakeholders Sanity

    of team
  5. WHO SHOULD CARE? Testers Coders A whole team... ...of generalizing

    specialists! (wtf does that even mean?!)
  6. THE GENERALIZATION MODEL Stuff to learn Progression through specialization

  7. HOW DO WE CARE? A Tester who works alone, but

    wants to improve During code reviews During (tester-coder) pairing Tester as a Navigator
  8. WHAT’S A CODE SMELL? Code Smell is when your Spidey

    Sense lights up Points to code or tests which will likely cause you pain later http://en.wikipedia.org/wiki/Code_smell First used in Fowler’s classic Refactoring book Automated tests are code!
  9. SMELLS AREN’T JUST FOR CODE Some smells can also apply

    to non-automated testing: Multiple test scenarios conflated in one test case Overly complex charters with too many concerns Test desires that are too ambiguous
  10. SMELL: WRONG PRIORITIES What’s your preferred order of these? Optimization

    / speed Correctness Readability
  11. JIM’S ORDER Readability Correctness Optimization / speed (Ron Jeffries disagrees

    with Jim…but he’s old)
  12. SMELL: READABILITY If you can’t read it, how will you

    know if it’s a good test? How will you remember what it does next month? More time is spent reading code than writing it!
  13. [Test] public void one_method_to_rule_them_all() { var profile = new FirefoxProfile();

    var exe = new FirefoxBinary(@"D:\\Program Files (x86)\\Mozilla Firefox\\firefox.exe"); IWebDriver browser = new FirefoxDriver(exe, profile); WebDriverWait w = new WebDriverWait(browser,TimeSpan.FromSeconds(10)); browser.Navigate().GoToUrl("http://localhost/AJAXDemos/CascadingDropDown/CascadingDropDown.aspx"); //browser.Navigate().GoToUrl("http://www.asp.net/ajaxLibrary/AjaxControlToolkitSampleSite/CascadingDropDown/ CascadingDropDown.aspx"); w.Until(ExpectedConditions.ElementExists(By.XPath("id('ctl00_SampleContent_DropDownList1')/ option[text()='Acura']"))); var sl =browser.FindElement(By.Id("ctl00_SampleContent_DropDownList1")); var l = new SelectElement(sl); l.SelectByText("Acura"); w.Until(ExpectedConditions.ElementExists(By.XPath("id('ctl00_SampleContent_DropDownList2')/ option[text()='Integra']"))); sl = browser.FindElement(By.Id("ctl00_SampleContent_DropDownList2")); l = new SelectElement(sl); l.SelectByText("Integra"); w.Until(ExpectedConditions.ElementExists(By.XPath("id('ctl00_SampleContent_DropDownList3')/option[text()='Sea Green']"))); sl = browser.FindElement(By.Id("ctl00_SampleContent_DropDownList3")); l = new SelectElement(sl); l.SelectByText("Sea Green"); w.Until(ExpectedConditions.ElementExists(By.XPath("//span[@id='ctl00_SampleContent_Label1' and text()='You have chosen a Sea Green Acura Integra. Nice car!']"))); browser.Quit(); } BEFORE
  14. [Test] public void a_bit_easier_to_read() { var profile = new FirefoxProfile();

    var exe = new FirefoxBinary(@"D:\\Program Files (x86)\\Mozilla Firefox\\firefox.exe"); IWebDriver browser = new FirefoxDriver(exe, profile); WebDriverWait wait = new WebDriverWait(browser,TimeSpan.FromSeconds(10)); browser.Navigate().GoToUrl("http://localhost/AJAXDemos/CascadingDropDown/CascadingDropDown.aspx"); wait.Until( ExpectedConditions.ElementExists( By.XPath("id('ctl00_SampleContent_DropDownList1')/option[text()='Acura']"))); var selectionList = browser.FindElement( By.Id("ctl00_SampleContent_DropDownList1")); var optionsList = new SelectElement(selectionList); optionsList.SelectByText("Acura"); wait.Until( ExpectedConditions.ElementExists( By.XPath("id('ctl00_SampleContent_DropDownList2')/option[text()='Integra']"))); selectionList = browser.FindElement( By.Id("ctl00_SampleContent_DropDownList2")); optionsList = new SelectElement(selectionList); optionsList.SelectByText("Integra"); wait.Until( ExpectedConditions.ElementExists( By.XPath("id('ctl00_SampleContent_DropDownList3')/option[text()='Sea Green']"))); … // CLOSING ITEMS ELIDED AFTER Line breaks, FTW Whitespace! Group common things together Lose Comments
  15. SMELL: DUPLICATION Don’t Repeat Yourself (DRY) Duplication kills maintainability! Avoid

    CPDD Cut and Paste Driven Development The Fix: Centralize common actions or declarations
  16. SMELL: DUPLICATION Version 1

  17. [Test] public void a_bit_easier_to_read() { … // OPENING ITEMS ELIDED

    wait.Until( ExpectedConditions.ElementExists( By.XPath("id('ctl00_SampleContent_DropDownList1')/option[text()='Acura']"))); var selectionList = browser.FindElement( By.Id("ctl00_SampleContent_DropDownList1")); var optionsList = new SelectElement(selectionList); optionsList.SelectByText("Acura"); wait.Until( ExpectedConditions.ElementExists( By.XPath("id('ctl00_SampleContent_DropDownList2')/option[text()='Integra']"))); selectionList = browser.FindElement( By.Id("ctl00_SampleContent_DropDownList2")); optionsList = new SelectElement(selectionList); optionsList.SelectByText("Integra"); wait.Until( ExpectedConditions.ElementExists( By.XPath("id('ctl00_SampleContent_DropDownList3')/option[text()='Sea Green']"))); selectionList = browser.FindElement( By.Id("ctl00_SampleContent_DropDownList3")); optionsList = new SelectElement(selectionList); optionsList.SelectByText("Sea Green"); wait.Until( ExpectedConditions.ElementExists( By.XPath("//span[@id='ctl00_SampleContent_Label1' and text()='You have chosen a Sea Green Acura Integra. Nice car!']"))); } BEFORE
  18. [Test] public void a_bit_easier_to_read() { … // OPENING ITEMS ELIDED

    wait_on_menu_item(1, "Acura"); var selectionList = browser.FindElement( By.Id("ctl00_SampleContent_DropDownList1")); var optionsList = new SelectElement(selectionList); optionsList.SelectByText("Acura"); wait_on_menu_item(2, "Integra"); selectionList = browser.FindElement( By.Id("ctl00_SampleContent_DropDownList2")); optionsList = new SelectElement(selectionList); optionsList.SelectByText("Integra"); wait_on_menu_item(3, "Sea Green"); selectionList = browser.FindElement( By.Id("ctl00_SampleContent_DropDownList3")); optionsList = new SelectElement(selectionList); optionsList.SelectByText("Sea Green"); wait.Until( ExpectedConditions.ElementExists( By.XPath("//span[@id='ctl00_SampleContent_Label1' and text()='You have chosen a Sea Green Acura Integra. Nice car!']"))); browser.Quit(); } AFTER
  19. SMELL: DUPLICATION Version 2

  20. BEFORE [Test] public void a_bit_easier_to_read() { … // OPENING ITEMS

    ELIDED wait_on_menu_item(1, "Acura"); var selectionList = browser.FindElement( By.Id("ctl00_SampleContent_DropDownList1")); var optionsList = new SelectElement(selectionList); optionsList.SelectByText("Acura"); wait_on_menu_item(2, "Integra"); selectionList = browser.FindElement( By.Id("ctl00_SampleContent_DropDownList2")); optionsList = new SelectElement(selectionList); optionsList.SelectByText("Integra"); wait_on_menu_item(3, "Sea Green"); selectionList = browser.FindElement( By.Id("ctl00_SampleContent_DropDownList3")); optionsList = new SelectElement(selectionList); optionsList.SelectByText("Sea Green"); wait.Until( ExpectedConditions.ElementExists( By.XPath("//span[@id='ctl00_SampleContent_Label1' and text()='You have chosen a Sea Green Acura Integra. Nice car!']"))); browser.Quit(); }
  21. [Test] public void selects_moved_to_single_method() { var profile = new FirefoxProfile();

    var exe = new FirefoxBinary(@"D:\\Program Files (x86)\\Mozilla Firefox\\firefox.exe"); browser = new FirefoxDriver(exe, profile); wait = new WebDriverWait(browser,TimeSpan.FromSeconds(10)); browser.Navigate().GoToUrl("http://localhost/AJAXDemos/CascadingDropDown/CascadingDropDown.aspx"); wait_on_menu_item(1, "Acura"); select_menu_item(1, "Acura"); wait_on_menu_item(2, "Integra"); select_menu_item(2, "Integra"); wait_on_menu_item(3, "Sea Green"); select_menu_item(3, "Sea Green"); wait.Until( ExpectedConditions.ElementExists( By.XPath("//span[@id='ctl00_SampleContent_Label1' and text()='You have chosen a Sea Green Acura Integra. Nice car!']"))); browser.Quit(); } AFTER
  22. SMELL: READABILITY Code is read 5,956 1 times more than

    it’s edited Readability is an ongoing, non-stop process (And what is clear today likely won’t be in six months. Or tomorrow.) [1] This stat is made up
  23. [Test] public void selects_moved_to_single_method() { var profile = new FirefoxProfile();

    var exe = new FirefoxBinary(@"D:\\Program Files (x86)\\Mozilla Firefox\\firefox.exe"); browser = new FirefoxDriver(exe, profile); wait = new WebDriverWait(browser,TimeSpan.FromSeconds(10)); browser.Navigate().GoToUrl("http://localhost/AJAXDemos/CascadingDropDown/CascadingDropDown.aspx"); wait_on_menu_item(1, "Acura"); select_menu_item(1, "Acura"); wait_on_menu_item(2, "Integra"); select_menu_item(2, "Integra"); wait_on_menu_item(3, "Sea Green"); select_menu_item(3, "Sea Green"); wait.Until( ExpectedConditions.ElementExists( By.XPath("//span[@id='ctl00_SampleContent_Label1' and text()='You have chosen a Sea Green Acura Integra. Nice car!']"))); browser.Quit(); } BEFORE
  24. [Test] public void selects_moved_to_single_method() { var profile = new FirefoxProfile();

    var exe = new FirefoxBinary(@"D:\\Program Files (x86)\\Mozilla Firefox\\firefox.exe"); browser = new FirefoxDriver(exe, profile); wait = new WebDriverWait(browser,TimeSpan.FromSeconds(10)); browser.Navigate().GoToUrl("http://localhost/AJAXDemos/CascadingDropDown/CascadingDropDown.aspx"); select_menu_item(1, "Acura"); select_menu_item(2, "Integra"); select_menu_item(3, "Sea Green"); wait.Until( ExpectedConditions.ElementExists( By.XPath("//span[@id='ctl00_SampleContent_Label1' and text()='You have chosen a Sea Green Acura Integra. Nice car!']"))); browser.Quit(); } AFTER
  25. SMELL: TOO MUCH RESPONSIBILITY Single Responsibility Principle A class should

    do only one thing, and do it gosh darned well
  26. [Test] public void selects_moved_to_single_method() { var profile = new FirefoxProfile();

    var exe = new FirefoxBinary(@"D:\\Program Files (x86)\\Mozilla Firefox\\firefox.exe"); browser = new FirefoxDriver(exe, profile); wait = new WebDriverWait(browser,TimeSpan.FromSeconds(10)); browser.Navigate().GoToUrl("http://localhost/AJAXDemos/CascadingDropDown/CascadingDropDown.aspx"); select_menu_item(1, "Acura"); select_menu_item(2, "Integra"); select_menu_item(3, "Sea Green"); wait.Until( ExpectedConditions.ElementExists( By.XPath("//span[@id='ctl00_SampleContent_Label1' and text()='You have chosen a Sea Green Acura Integra. Nice car!']"))); browser.Quit(); } private void select_menu_item(int listNum, string item) BEFORE
  27. [TestFixtureSetUp] public void run_once_before_anything() { var profile = new FirefoxProfile();

    var exe = new FirefoxBinary(@"D:\\Program Files (x86)\\Mozilla Firefox\\firefox.exe"); browser = new FirefoxDriver(exe, profile); wait = new WebDriverWait(browser, TimeSpan.FromSeconds(10)); browser.Navigate().GoToUrl("http://localhost/AJAXDemos/CascadingDropDown/CascadingDropDown.aspx"); } [TestFixtureTearDown] public void run_once_after_everything_else() { browser.Quit(); } [Test] public void move_setup_out() { select_menu_item(1, "Acura"); select_menu_item(2, "Integra"); select_menu_item(3, "Sea Green"); wait.Until( ExpectedConditions.ElementExists( By.XPath("//span[@id='ctl00_SampleContent_Label1' and text()='You have chosen a Sea Green Acura Integra. Nice car!']"))); } AFTER
  28. SMELL: MORE READABILITY ISSUES Symmetry: Don’t mix implementation and intent

    Kent Beck’s Implementation Patterns, 2007.
  29. [Test] public void move_setup_out() { select_menu_item(1, "Acura"); select_menu_item(2, "Integra"); select_menu_item(3,

    "Sea Green"); wait.Until( ExpectedConditions.ElementExists( By.XPath("//span[@id='ctl00_SampleContent_Label1' and text()='You have chosen a Sea Green Acura Integra. Nice car!']"))); } BEFORE
  30. [Test] public void move_setup_out() { select_menu_item(1, "Acura"); select_menu_item(2, "Integra"); select_menu_item(3,

    "Sea Green"); assert_correct_message_appears(); } AFTER
  31. SMELL: NOT ENOUGH ABSTRACTION Abstraction moves implementation to another area

    A test shouldn’t know how a page works Also helps cut duplication
  32. ABSTRACTION PHILOSOPHY “All problems in computer science can be solved

    by another layer of indirection (abstraction) -- except for the problem of too many layers of indirection (abstraction). “ -- David Wheeler
  33. BEFORE [Test] public void move_setup_out() { select_menu_item(1, "Acura"); select_menu_item(2, "Integra");

    select_menu_item(3, "Sea Green"); assert_correct_message_appears(); } private  void  assert_correct_message_appears() {        wait.Until(                ExpectedConditions.ElementExists(                        By.XPath("//span[@id='ctl00_SampleContent_Label1'  and  "+                                "  text()='You  have  chosen  a  Sea  Green  Acura  Integra.  Nice  car!']"))); } private  void  select_menu_item(int  listNum,  string  item) {        wait_on_menu_item(listNum,  item);        selectionList  =                                        browser.FindElement(                                                By.Id("ctl00_SampleContent_DropDownList"  +  listNum));        var  optionsList  =  new  SelectElement(selectionList);        optionsList.SelectByText(item); } private  void  wait_on_menu_item(int  listNum,  string  text) {        wait.Until(                ExpectedConditions.ElementExists(                        By.XPath("id('ctl00_SampleContent_DropDownList"  +                                  listNum+  "')/option[text()='"+                                text+"']"))); }
  34.  class  CarMenu  {          IWebDriver  browser;  

           [FindsBy(How  =  How.Id,  Using  =  "ctl00_SampleContent_DropDownList1")]          IWebElement  Make  {  get;  set;  }          [FindsBy(How  =  How.Id,  Using  =  "ctl00_SampleContent_DropDownList2")]          IWebElement  Model  {  get;  set;  }          [FindsBy(How  =  How.Id,  Using  =  "ctl00_SampleContent_DropDownList3")]          IWebElement  Color  {  get;  set;  }          [FindsBy(How  =  How.Id,  Using  =  "ctl00_SampleContent_Label1")]          IWebElement  Message  {  get;  set;  }            public  CarMenu(IWebDriver  browser)                                              {  this.browser  =  browser;  }          public  string  select_a_car(Car  car)                                              {  /* ELIDED */      }          private  void  select_menu_item(int  listNum,  string  item)      {  /* ELIDED */      }          private  void  wait_on_menu_item(int  listNum,  string  text)    {  /* ELIDED */      }          private  void  extract_message(string  message)                            {  /* ELIDED */      } } [TestFixture] public  class  v8_page_objects {        // SETUP ELIDED        [Test]        public  void  page_objects()        {                Car  acura  =  new  Car("Acura",  "Integra",  "Sea  Green",  "You  have  chosen  a  Sea  Green  Acura  Integra.  Nice  car!");                CarMenu  page  =  new  CarMenu(browser);                var  actual_message  =  page.select_a_car(acura);                Assert.AreEqual(acura.Message,  actual_message);        } } AFTER
  35. SMELL: COMPLEXITY Too long Too many IF/THEN/ELSE or SWITCH blocks

    Multiple return paths
  36. Login in a Test? DANGER!! (Duplication) Testing for tires ...movies

    ...computers
  37. REFACTORING Small changes to implementation, NOT behavior Goldilocks approach Not

    too much, not too little, just right
  38. TESTERS HELPING DEVS Devs often focus on happy path and

    forget stuff like “App fails when user enters 18,446,744,073,709,551,614 Ctrl-C characters in 68 concurrent browser windows simultaneously in 38 instances of Chrome, 42 FF 21.5, and IE 4.2 on a leap day at 11:59:58PM after pouring a cup of Guatemala Huehuetenango Finca Regalita roasted at full city+ over the application server. I unplugged the database server in the middle of the commit transaction too. Also there’s a french fry in the biz tier server’s disk array.”
  39. EXERCISE ! Payroll algorithm Hourly workers Straight time for <=

    40 hours Time * 1.5 for > 40 hours Salaried workers All straight time
  40. [Test] public  void  Computing_with_40_hours_at_5_rate_returns_200() {        var  wages

     =  _computer.ComputeWages(40,  5,  true);        Assert.AreEqual(200,  wages,  "40  *  5  ==  200"); } [Test] public  void  Computing_with_41_hours_at_5_rate_returns_207_50() {        var  wages  =  _computer.ComputeWages(41,  5,  true);        Assert.AreEqual(207.50,  wages); }
  41. TESTERS HELPING DEVS It’s not just about testers learning from

    devs Devs can learn better approaches to many things Sad path coverage Exception handling End user patterns
  42. WHY SMELLS MATTER Points to areas that will likely cause

    long-term pain Maintainability Flexibility for enhancements Sanity of team
  43. WHY SMELLS MATTER Identifying and fixing smells is a great

    collaboration point Testers learn cleaner testing Devs learn better coverage
  44. IT TAKES A VILLAGE TO WRITE SOME SOFTWARE

  45. ? GOT QUESTIONS Jim Holmes (@aJimHolmes) jim.holmes@telerik.com Matt Barcomb (@mattbarcomb)

    matt@odbox.co