Google Analytics

Search

To search for specific articles you can use advanced Google features. Go to www.google.com and enter "site:darrellgrainger.blogspot.com" before your search terms, e.g.

site:darrellgrainger.blogspot.com CSS selectors

will search for "CSS selectors" but only on my site.


Showing posts with label xUnit. Show all posts
Showing posts with label xUnit. Show all posts

Friday, May 16, 2014

Test Driven Development

I've read a lot for and a lot against Test Driven Development (TDD) but I don't every remember something TDD has really helped me with. If you write a test then write the code which makes the test pass the end result is a piece of code which demonstrates how the application code works. I have used a number of open source applications and like every library/framework/application I have used the documentation ends up being way out of date. But if you are writing tests for all the features you add (before or after you write the code) you are essentially creating examples of how to use the library/framework/application. So even with the documentation falling out of date, so long as the code has a good base of unit tests, you can figure out how it works. I find this helpful in multiple ways. When I'm automating I tend to use tools like Selenium. Whenever I need to use a new feature of Selenium I can look at the documentation but ultimate, the unit tests for the framework tell me how it actually behaves. Additionally, the applications I am testing are written by people who understand TDD and have a good set of unit tests for each application. So I find it easy to under how new features are implemented by looking at the unit test for the application I am testing. For example, Selenium has a TakesScreenshot class. The source for it would be found at:
https://code.google.com/p/selenium/source/browse/java/client/src/org/openqa/selenium/TakesScreenshot.java
So if I want to see the tests for it, change 'src' to 'test' and change 'TakesScreenshot.java' to 'TakesScreenshotTest.java' for a full URL of:
https://code.google.com/p/selenium/source/browse/java/client/test/org/openqa/selenium/TakesScreenshotTest.java
When I look at this latter file I see many examples of how to use the TakesScreenshot class.

Thursday, June 14, 2012

So you want to do unit testing


What is a unit test? Wikipedia describes unit testing as testing individual units of code in isolation. If the code has external dependencies, you simulate the dependencies using mock objects.

For example, if I am testing code which gets data from a database, hopefully access to the database is via something like ODBC or JDBC. In which case, it is possible to use a fake database (file system or memory based) rather than say an Oracle or SQL Server driver.

If my database connection is hard coded to a particular machine or assumes the machine is localhost then my first step is to refactor the code to remove this dependency.

Part of the purpose of having unit test cases is so that we can safely change the code and know we didn't break any existing functionality. So if we need to modify the code to be able to add unit tests we have a bit of a Catch-22 situation. The truth of the matter is, if we have been changing the code without unit tests, changing it one more time in order to add unit tests is actually a step in the right direction and no worse than previous development.

Another important feature of unit tests are speed. If I am adding a new feature and I want to be sure it hasn't broken anything, I want to know as soon as possible. I don't want to write the feature, run the tests and check the results tomorrow. Ideally, I want to know in seconds. Realistically, I might have to live with minutes at first.

Test runs should be automated. If I have to make a change and figure out what tests to run, run them and check the results there is a strong chance I will stop running them. Especially if I'm on a tight timeline.

Ideally, I would check in my code. This will fire a trigger which builds my code (not the entire product, just my code) and run the unit tests against it. Putting such a build system in place is a great deal of work but worth the effort. Every minute it takes to create this build system should be weighed against how much time developers spend testing their code before they check in, how many minutes testers spend finding bugs, how much time developers take understanding the bug and fixing it. Numerous studies have shown fixing bugs is much more expensive than never introducing them in the first place.

So what do we need so far?

First, we need a unit test framework. You wouldn't create your own replacement for JDBC/ODBC. So why create your own unit test framework. There are plenty of them out there.

Second, we need mocking frameworks for the technologies we are utilizing. Which mock object frameworks you require depends on what you are using in your application. If it is a web application, you might need to mock out the web server. If it accesses a database, you will need to mock out the database.

Third, we need a build system to automate the running and reporting of the unit tests. Reporting the results is important too. Most systems will either report back to the source control client or send you an email. If the tests run in, literally, seconds, you can afford to reject the checkin if a unit test fails. If it takes more than say 5 seconds, you might want to send an email when a checkin fails.

Fourth, we need commitment from management and the team. If you don't believe there is benefit to unit testing there will be no benefit to unit testing. Training people on how to create good unit tests and maintain them is critical. If I'm starting a new project and writing tests from the beginning it is easy but the majority of you will be adding unit tests to existing code.

The first three things are relatively easy to obtain. There are plenty of technologies and examples of people using them. The fourth requirement is the biggest reason adopting unit testing fails. If you don’t get buy in from everyone involved it just won’t work. The developers need to understand this will benefit them in the long run. The testers need to understand that less testing will be required and they need to focus on things unit testing will not catch. There will always be plenty of things to test. So there should be no fear unit testing will replace  integration or system testing. Management has to understand if they cut timelines for a project, they will not give developers time to write the unit tests. If you reward the Project Manager for getting the project out on time, he will get the project out on time even if it means giving developers no time for unit test creation. As a Project Manager, if reducing the number of issues AFTER the project has shipped is not a metric I’m evaluated on, I’m happy to ship a product which will make the next project difficult to get out on time.

So, you have the tools and you have buy in from everyone. Now what? If you have 100,000+ lines of code, where do you start writing unit tests? The answer is actually really simple. For example piece of code a developer touches, they should add unit tests. Bug fixing is the best place to start. I would FIRST write a unit test which would have caught the bug. Then I’d fix the bug and see the unit test pass.

By focusing on unit tests for bug fixes it reduces the need for regression testing, it focuses on the features customers are using and the developers are in that code anyways. If we need to refactor the code to support unit testing, might as well happen as we are changing the code. The code was broken when  we started the bug fix. So we’ll have to manually test the fix without unit tests. Hopefully, with a unit test in place, it will be the last time we manually test changes to this code.

If we are modifying the code for feature creation, not bug fixing, we want to write unit tests to confirm the current behaviour. Once we have a test which passes with the current code, we can add the feature and the tests should continue to pass.

At this point we know what we need and where to start. So let’s cover some of the how to write a unit test.

First, a unit test is going to be a function/method which calls our code. We want the name of the unit test to reflect what it is testing. When results are published they will go out to the developer but they will also be seen by the backup developer, project management and various other people as well. If I got an email telling me test17() failed I’m going to have to open the code and read what test17() is testing. You added comments and kept them up to date, right? Or course you didn’t. The comments shouldn’t be necessary. The test name should tell me what it is doing. If the test method was called, callingForgotPasswordWhenNoEmailInUserPreferences() then we all know what is being tested.

Second, what failed? Most unit test frameworks has assert statements. There is the basic fail() call but there are also things like AssertTrue, AssertEquals, AssertNotNull, etc. They can be called with just what you are checking or with a message and what you are checking. You don’t want to code any more than you have to but enough that someone receiving the results will know what failed. If the requirement for my software is “When a user clicks the Forgot Password button but they have not set an email address in their preferences, they should be presented with a message telling them to contact the system administrator.” Then the result message from my example here might be something like, “callingForgotPasswordWhenNoEmailInUserPreferences() failed. Was expecting: ‘No email address was set for your account. Please contact the System Administrator.’ but received: ‘No email address.’”. From this is it pretty clear what was expected and what we received instead. Failing to tell the user how to proceed should be considered a show stopper for the customer. On the other hand, if the result was: “callingForgotPasswordWhenNoEmailInUserPreferences() failed. Was expecting: ‘No email address was set for your account. Please contact the System Administrator.’ but received: ‘No email address was set for your account. Please contact the system administrator.’” the customer might consider this acceptable. We might even update the unit test case to ignore case so the test becomes a pass.

Unit test frameworks are pretty well established now. The general structure of a unit test is:

  • set up for the test
  • run the test
  • assert the test passed
  • clean up so the next test starts at the same point


The set up would be things like creating mock objects, initializing the inputs for the test, etc. The running of the test would be a call to the method being testing. Next would be an assert statement confirming that we received the expected results or side effect. Finally, clean up (often called tear down) the environment so it is at the exact same condition it was before the set up occurred.

Often you will group similar tests in one test suite. If I have 12 tests and they all require the same set up I will put them all in one suite. The code will then have one setUp() method that creates the environment for each test, one method for each test (12 methods in total for this example) and one tearDown(). The setUp() method will create any mock objects, initial global variables, etc. The test method will create anything particular to that test, call the method being tested then make an assert call. The tearDown() method will then clean up the environment so it is just like it was before the setUp() method was called. This is important because most unit test frameworks do no guarantee the order the tests will be run. Assuming one test starts where a previous test left off is just bad practice. I have worked on a project with 45,000 unit tests. All test are run as part of the nightly build. Rather than running all the tests on one machine, they are distributed to 238 different machines. If they all ran on one machine they would take 378 hours (over 2 weeks) to run. By distributing them over 238 computers they run in approximately 3 hours. However, if test1932 depends on test1931 and the two tests get sent to different machines, test1932 will not run correctly. Each test must be independent of all other tests. This will not seem important at first but 1 year later you might find yourself needing weeks (possibly months) to refactor all your unit tests. Moments like these often cause management to abandon unit testing.

This is unit testing is a nutshell. I will warn you, ‘the devil is in the details.’ Hiring someone who has gone through the pains of setting up a unit test framework is always a good idea. Either find a good consultant or hire someone full time to work on the framework for you. Some unit test frameworks are jUnit for Java, cppUnit for C++, nUnit for .NET, etc. Gerard Meszaros has written an excellent book called “xUnit Test Patterns: Refactoring Test Code”. In it he talks about “Test Smells”. Essentially, you can sometimes look at a piece of code and say, “This code stinks.” A code or test ‘smell’ is an indicating that the code has problems, i.e. it stinks. I have found reading Gerard Meszaros book I know what to look for before I do it. Originally the book was designed for people who created unit tests, found the tests have issues, i.e. they ‘smell’ and are looking to fix them, i.e. refactor. By reading the book, I avoid creating the bad unit tests in the first place.

Good luck and have fun!


Friday, August 20, 2010

New Job

It certainly has been a while since I posted to my blog. For anyone who is curious, I started a job as QA Manager at Certicom Corporation.

Certicom is in the business of cryptography. We create libraries or toolkits for C and Java which are used by customer applications. At the lowest level is Crypto. Crypto is cryptographic routines and algorithms used as part of a security solution. Customers like XM Radio use our Security Builder® Crypto™.

The next level up is PKI or Public Key Infrastructure. Security Builder® PKI™ enables you to add robust, standards-based digital certificates and key management to applications and devices, ensuring trust and non-repudiation. Some customers will develop their own Crypto solution for use with our PKI or they will use our Crypto solution with our PKI.

The security most people in the public know about is SSL or Secure Sockets Layer. Our Security Builder® SSL™ product can be used for people wishing to implement SSL. Either in a client or server. For example, with our product you can develop a mod_ssl for use with Apache Web Server.

Another term you may be familar with is VPN or Virtual Private Network. To create a VPN requires IPSec or Internet Protocol Security. This is acheived using Certicom's Security Builder® IPSec™.

In addition to all the publicly available products we create custom solutions for various industries and companies.

The company was recently acquired by Research In Motion. Most people know this company as BlackBerry, which is just a product the company produces.

As QA Manager I have the challenging task of testing all the different implementations of our products. Some are Windows, AIX, HP-UX, Linux, Solaris or Mac OS based but others are build on embedded devices. Our Asset Management System, used in chip manufacturing plants, utilizes Web Services, AJAX, JavaEE and other web technologies as a front end to a complex cryptography solution.

At this time I am actually looking to hire testers for testing these products. It is quite challenging to find people who can do the work. Ideally, they need to know or be able to learn:
  • C, C++ and Java programming
  • Defect tracking systems
  • Test reporting
  • Knowledge of Windows, Linux or UNIX
  • Experience testing embedded devices or mobile devices, e.g. BlackBerry
  • Ability to create test plans
  • Working knowledge of test automation
  • Unit testing, system testing, integration testing
  • Experience with source control
  • Knowledge of cryptography
  • Development experience or experience testing toolkits and libraries
Basically, someone who is a junior programmer, an intermediate tester and some IT or support knowledge.

If you know anyone who fits this description or you believe you are up for the challenge, you can apply to the position at https://rim.taleo.net/careersection/professional/jobdetail.ftl?lang=en&job=188752. If this link does not work, try the following:
  1. Go to http://www.rim.com/
  2. Go to the Careers section
  3. Go to Americas, this should bring you to a Job Search page
  4. In the keywords field enter: Certicom
This should give you a list of all the jobs current available. Anything relating to Test would be my department.

Monday, February 15, 2010

Why each test case should start and end at the same state

I have seen a number of test automators struggling with creating a test suite. Their problem is that they are using xUnit style test cases but they want to change the way they were intended to be used.

For JUnit, the execution is:
  • Run Before, open web browser
  • Run Test Case #1
  • Run After, close web browser
  • Run Before, open web browser
  • Run Test Case #2
  • Run After, close web browser
  • Run Before, open web browser
  • Run Test Case #3
  • Run After, close web browser
What test automators want is:
  • Run Before, open web browser
  • Run Test Case #1
  • Run Test Case #2
  • Run Test Case #3
  • Run After, close web browser
The problem is, this is not how things work in JUnit. So they have been using static methods and helper classes to create the web browser. It looks like:
  • Run Before, if browser == null [true], browser = open web browser
  • Run Test Case #1
  • Run After, if last test case [false], close web browser
  • Run Before, if browser == null [false], browser = open web browser
  • Run Test Case #2
  • Run After, if last test case [false], close web browser
    • Run Before, if browser == null [false], browser = open web browser
      • Run Test Case #3, flag last test case
        • Run After, if last test case [true], close web browser

        This is essentially what test automators want because the After/Before calls between test cases do nothing and leave the web browser open.

        Writing test cases which follow each other, i.e. the final state of test case n is the setup state for test case n+1, is a bad idea.

        If you start with something simple and current it is not a bad idea.

        Imagine the test suite growing and growing. A few months from now you have 5000 'test cases'. Everything is going great. Then they make a change to the application. Your test cases start failing. You investigate the first failure which happens to be test case 3476 of 5000. You need to run all the test cases from 1 to 3476 before you can get to the point it APPEARS to fail.

        A few hours later you find out that things have changed in the application and it is a false negative, i.e. the problem is in the test automation and not the application. You fix it and run the test suite again. A few hours later you find out your fix didn't work. So you tweak it and run the test suite again. A few hours later you find the fix still isn't right. You try one more time. While the test suite is running for the third time that day, 5pm hits. Do you go home and check the results in the morning? Do you work late?

        Let's say you work late and find test case 3476 is working again. But wait, test case 3788 fails now. You set a break point just before the failure point and run the test suite again. Even if you can fix the problems in one attempt, if there are multiple test cases needing maintenance you will still take days to just FIX the old test cases. Where do you find time to add new test cases for the next features? They will take just as long if you add them to the end of this chain.

        Another thing to consider is, hopefully, you will get to a point that your test suite takes MANY hours to run. I've had test suites which took over 8 hours to run. What do you do? You could ask the Project Manager for more time. As a Project Manager he is going to look at the problem just like any other problem he deals with. If Tim has a task to do and it is going to take him 12 days to complete but we need it done in 4 days, break the task into 3 sub-tasks and bring in two more people to help Tim. For safety, bring in three more people. With four people (Tim plus the three new people) working on it, each person should take 3 days to do their part. Working in parallel, the whole task will be done in 3 days.

        How does this apply to test automation? If one machine is taking 40 hours to run the test suite but we want it run daily. Out of a 24 hour day we need time for maintenance, building the application, backing up the system, etc. So let's say you have 8 hours to run the test suite. If it takes 40 hours to run the whole thing, 40 / 8 = 5 so get 5 computers and break the test suite into 5 parts. Heck, computers are a lot cheaper than people; get 10 computers and break the test suite into 10 parts. Now it takes 4 hours to run the test suite.

        But how do you break apart the test suite? The way I write a test suite is to have the Before call setup for the test, I run the test case then the After call returns the system to the exact same state as before the test was run. This means all test cases start from the same point and end at the same point. I can run the tests in any order I want. I can run one, some or all the tests without worrying about how one test will affect another test. For me, if I have 5000 tests and I want to run them on 10 machines then I run tests 1 to 499 on machine 1, tests 500 to 999 runs on machine 2, tests 1000 to 1499 runs on machine 3, ..., tests 4500 to 5000 runs on machine 10.

        If I try this and find that machine 3 is taking 7 hours and machines 5 and 9 are taking 3 hours and 2 hours, I can start moving some from machine 3 to machine 9, keep doing this until machine 9 is taking 4 hours and machine 3 is taking 5 hours. Then I can move some from machine 3 to machine 5 until they are both taking 4 hours.

        Which test cases I move to which machine does not matter. It should take me seconds to make the change.

        Other the other hand, if your test cases all depend on the previous test case, you will need to figure out where to make the first break, i.e. test case 1 to what? Once you figure that out, you'll have to figure out how to get the next test case into the correct state before it starts. You will have to do this 10 times and each time it could take you over a day to get things set up. In other words, it could take weeks just to get back to nightly build and test.

        Bottom line, it feels like you are saving time by having the tests run one after the other without closing but in the long run it will cost you so much that you might have to abandon test automation or seriously jeopardize the project.

        It is important to understand that if this happens a project manager has to deal with a lot more than testing, a LOT MORE. He will not want to hear why you need two EXTRA weeks. He will just expect you to find a solution and bring the project in on time and under budget. If fingers start pointing you can be sure that the project manager will throw the test automators 'under the bus'.

        Creating a Selenium Test Case in Java from scratch

        I you want to create a Selenium test case from scratch, i.e. not using the record method listed earlier in my blog, here is how you do it:


        1. I assume you have Eclipse installed
        2. I assume you have downloaded the Selenium RC
          1. The files selenium-server.jar and selenium-java-client-driver.jar will be needed
        3. In Eclipse create a new Java Project
          1. Pick a name for the project, set anything else you think you'll need on the first page
          2. On the next page, go to the Libraries tab and add the two selenium jar files to the project
          3. Finish
        4. Right click on the src folder and create a new JUnit Test Case
          1. Select JUnit 3, pick a package if you want (can be changed later), pick a name, tick setUp and tearDown, click Finish
          2. It should ask you if you want to add the JUnit 3 library to the project. Answer yes.
        5. You should now be looking at the JUnit test case class in the editor
        6. Change the class so it 'extends SeleneseTestCase
        7. You should get a warning on the SeleneseTestCase
        8. Hover over it and you should get the option to import com.thoughtworks.selenium.SeleneseTestCase which you should do
        9. In the setUp() method, the body should be:
          1. super.setUp(url, browser); where url is the URL of you web site being tested and browser is something like "*firefox", "*iehta" or "*safari"
          2. For example, super.setUp("http://www.google.ca", "*safari");
          3. The setUp method should now look like:



            public void setUp() throws Exception {
                    super.setUp("http://www.google.ca", "*safari");
                }
            
            
        10. In the tearDown() method the body should be:
          1. super.tearDown();
          2. The tearDown method should now look like:



            public void tearDown() throws Exception {
                    super.tearDown();
                }
            
            
        11. Now we can add a test case. It might look like:
        12. public void testAGoodDescriptionOfWhatWeAreTesting() throws Exception {
                  selenium.open("/");
                  System.out.println(selenium.getHtmlSource());
              }
          
          
        13. To run this, you'll need to start the Selenium Server.
        14. Go to a command line and enter:
        15. java -jar selenium-server.jar
          
        16. You might need some more command line switches like -firefoxProfileTemplate or -trustAllSSLCertificates. To see help on the server use:
        17. java -jar selenium-server.jar -help
          
        18. Once you have the server running, in Eclipse you want to run the test case as a JUnit Test
        NOTE: the SeleneseTestCase is a JUnit 3 TestCase. It assumes the names of the methods are fixed and does not use annotations. You have to use setUp, tearDown and all test cases need to start with 'test'. If you want to create the same thing as JUnit 4 you can use:

        import org.junit.After;
        import org.junit.Before;
        import org.junit.Test;
        import com.thoughtworks.selenium.DefaultSelenium;
        import com.thoughtworks.selenium.Selenium;
        
        public class Whatever {
        
         Selenium selenium;
        
         @Before
         public void setUp() throws Exception {
          selenium = new DefaultSelenium("localhost", 4444, "*firefox", "http://www.google.com");
         }
        
         @Test
         public void whatever() throws Exception {
          selenium.open("/");
          System.out.println(selenium.getHtmlSource());
         }
        
         @After
         public void tearDown() throws Exception {
          selenium.close();
          selenium.stop();
         }
        }
        
        
        And there is a simple test case create in Eclipse.
        
        

        Friday, February 12, 2010

        Step by Step Selenium Java

        This is a document on how to write a Selenium Test Case in Java.


        1. Go to Selenium HQ.
        2. Go to the Download section and download Selenium IDE plus Selenium RC
        3. Install the Selenium IDE into Firefox (I assume you have Firefox installed)
        4. Go to Google.
        5. From the Tools menu in Firefox select Selenium IDE.
        6. From the Add-On Preferences, change Selenium to save in Java.
        7. On the Google page, right click and select Open /ig
        8. Type Selenium in the Google search text field.
        9. Press TAB to move focus away from the input field.
        10. Right click on the search text field and select verifyValue q Selenium
          1. Note that verifyValue is the action, q is the locator and Selenium is the optional third value
        11. Click the I'm Feeling Lucky button.
        12. Go to the Selenium IDE and you should see everything we just did.
        13. Click the red dot in the upper right to stop the recording.
        14. From the File menu, save the file as LearningSelenium.java.
        15. Open Eclipse
        16. Create a Java Project
        17. Add the jar selenium-java-client-driver.jar to the project.
        18. Add the library junit 3.
        19. Right click on the project name in the Package Explorer and add new package com.example.tests.
        20. Import from File System, the LearningSelenium.java file we saved earlier.
        21. When you finish there should be an error because the class is called Untitled by default.
        22. Edit the file and change the class name to LearningSelenium
        23. In the setUp() change the http://change-this-to-the-site-you-are-testing/ to http://www.google.ca/
        24. If using Mac OS, change the browser string from *chrome to *safari.
        25. Add a public void tearDown() throws Exception which just calls super.tearDown();
        26. Run Firefox from the command line using firefox -P (or firefox-bin -P on UNIX/Linux machines)
        27. Create a new profile, call it selenium and save it in the Eclipse workspace
        28. Go to the command line and start the selenium server using:
          1. java -jar selenium-server.jar -firefoxProfileTemplate /directory/where/you/saved/firefox/profile/selenium
          2. But change the /directory/where/you/saved/firefox/profile/selenium to the place you saved the profile in step 25.
        29. Back in Eclipse, right click the class name and select Run As, JUnit Test
        You are done.

        Wednesday, January 27, 2010

        Test Automation Manifesto

        I have been recently reading xUnit Test Patterns by Gerard Meszaros. Excellent book with a lot of things I learned the hard way. I also found an article by Gerard et al titled, "The Test Automation Manifesto". The manifesto is:

        Automated tests should be:

        1. Concise: As simple as possible and no simpler.
        2. Self Checking: Test reports its own results; needs no human interpretation.
        3. Repeatable: Test can be run many times in a row without human intervention.
        4. Robust: Test produces same result now and forever. Tests are not affected by changes in the external environment.
        5. Sufficient: Tests verify all the requirements of the software being tested.
        6. Necessary: Everything in each test contributes to the specification of desired behavior.
        7. Clear: Every statement is easy to understand
        8. Efficient: Tests run in a reasonable amount of time.
        9. Specific: Each test failure points to a specific piece of broken functionality; unit test failures provide “defect triangulation”
        10. Independent: Each test can be run by itself or in a suite with an arbitrary set of other tests in any order.
        11. Maintainable: Tests should be easy to understand and modify and extend.
        12. Traceable: To and from the code it tests and to and from the requirements.

        In his article he talks about bad code 'smells'. A 'smell' is something which you notice again and again as a problem. Even before you have clearly identified it, you can 'sniff' them out.

        The first two code smells he talks about are THE reason record and playback test automation doesn't work.

        When you use record and playback to automate, it will (a) hard-code test data and (b) duplicate code. If I'm testing an application which requires me to log in before each test, the recorder will record me logging in for each test case. If I do not refactor the log in to a function that all the test cases use and the developer changes the login, I would have a maintenance nightmare. Additionally, if the username and password change and the test data is hard-coded, I'd have to find all the instances and change them.

        By the way, the idea of 'refactoring' is to change the code to be more maintainable without changing the way it runs. For example, if I had the following code:

        // test#1
        // put "darrell" in username text field
        // put "password" in password text field
        // click Submit button
        //
        // test#2
        // put "darrell" in username text field
        // put "password" in password text field
        // click Submit button
        //
        // test#3
        // put "darrell" in username text field
        // put "password" in password text field
        // click Submit button
        //
        // test#4
        // put "darrell" in username text field
        // put "password" in password text field
        // click Submit button
        //

        and it worked as expected, I could refactor it into:

        // login(username, password)
        // put $username in username text field
        // put $password in password text field
        // click Submit button

        // test#1
        // login("darrell", "password")
        //
        // test#2
        // login("darrell", "password")
        //
        // test#3
        // login("darrell", "password")
        //
        // test#4
        // login("darrell", "password")
        //

        This will run pretty much the same but now if the Submit button gets changed to a Login button, I don't have to change all 4 test cases (what if I have 40,000 test cases). I just change:

        // login(username, password)
        // put username in username text field
        // put password in password text field
        // click Login button

        and it updates all the test cases. This code still has hard-coded data. How about changing this to put the data in a property file:

        # login data
        username=darrell
        password=password

        then change my code to:

        // read property file
        // username = getProperty("username")
        // password = getProperty("password")

        // test#1
        // login(username, password)
        //
        // test#2
        // login(username, password)
        //
        // test#3
        // login(username, password)
        //
        // test#4
        // login(username, password)
        //

        Now I can edit the property file if I want to change the username or password.

        I picked property file to hold the test data but it could just as easily been a database, spreadsheet, text file, compiled resource, global variables, etc.