After publication of an article in Forbes about the shift in global manufacturing from multi-year product cycles to rapid and continuous innovation with a focus on the practices used by WIKISPEED and the coaching provided by SolutionsIQ at industry-leading companies such as Tait Communications, Emre Aras posted the following questions on the WIKISPEED mailing list:
What kind of hardware engineering team is it?
What do you do?
Is it like Research and Development Center?
Below are my responses.
Engagement with the team at Tait has been particularly interesting because the team comprises mechanical and systems engineers in addition to software engineers and testers, all co-located in a single area with workbenches that contain only the tools and test fixtures necessary to execute on the singular vision for their pilot project. As far as the specific details of what the team was working towards, I am hesitant to give more details than just to say that they were building out a base station for use by the types of hand-held radios you might see firefighters, police officers and other emergency responders using (which is not giving away much since that is what Tait does). Since all team members were sitting together and working towards the same concise and focused vision, since everyone was aware of what everyone else was doing because of the information radiators used throughout the room and morning just-in-time planning sessions, and since the product owner was always available to answer questions and refocus priorities, the team was able to identify several knowledge gaps that related not just to this individual project but to other projects ongoing at Tait, the team was able to cut scope responsibly because of new information exposed by members that emerged from the serendipitous conversations that happen when teams work in close and open proximity, the team was able to inspect and adapt daily from the actions that surfaced in afternoon retrospectives, and the team was able to demonstrate a working product to a customer proxy within the span of just 5 days!
My role is just to be a catalyst for change. Team members often know what they need to do to get work done. Someone in my role just helps to get team members together in the same space, talking to one another, and using project management frameworks and tools that encourage teams to produce small customer-visible features on a regular cadence.
One goal of this project was to deliver a small, fully-realized feature into the hands of a customer and to get immediate feedback on a short timescale, so the project was more than just a theoretical exercise. Another goal of the project was to identify knowledge-gaps in order to minimize risk for ongoing development.
Suppose our product owner would like to publish a website for teachers to schedule their classes and for students to register for those courses. At the first backlog grooming, the product owner has prioritized the stories that would constitute a minimum viable product.
During the team's first sprint planning meeting, we sized the stories, accepted four into the sprint, and have added tasks for each. Our story board for the first sprint contains sticky notes for our user stories, the acceptance criteria and the individual tasks for each story.
As a teacher
I want to sign up
So I can add courses
Acceptance Criteria:
User is greeted with intro screen
User is able to register as a teacher or to login
After registration or login, a teacher sees account info
As a student
I want to sign up
So I can take courses
Acceptance Criteria:
User is able to register or to login as a student
After registration or login, a student sees account info
As a teacher
I want to add courses
So students can register for them
Acceptance Criteria:
A teacher can add a course
Courses occur in a semester
Courses occur in timeslots
Courses can have prerequisites
As a student
I want to register for courses
So I can get credits toward my degree
Acceptance Criteria:
A student can register for a course
A student must take any pre-reqs before registering for a course
A student cannot take 2 courses that occur in the same timeslot
Project Structure
We will create a top-level project course_registry and the two sub-projects web and specifications. We will also create a build.gradle file in the root along with settings.gradle and a build.gradle file in the specifications subproject for running automated acceptance tests (While this is not strictly necessary, it will help to keep our project boundaries clear).
course_registry
.
|____build.gradle
|____settings.gradle
| specifications
| |____build.gradle
| web
We will create a src/test/groovy folder in the specifications project. At this point we can generate Eclipse .project and .classpath files for all the projects from the project root in order to work within an IDE.
def"a user is greeted with an intro screen"(){ when: driver.get"http://localhost:8080/course_registry"
then: driver.title=="Course Registry Home" } }
We can run this spec and watch it fail.
course_registry $> gradle test --info
....
Test a user is greeted with an intro screen(timezra.course_registry.TeacherRegistrationSpec) FAILED: org.gradle.messaging.remote.internal.PlaceholderException: org.spockframework.runtime.SpockComparisonFailure: Condition not satisfied:
Test timezra.course_registry.TeacherRegistrationSpec FAILED
Gradle Worker 1 finished executing tests.
1 test completed, 1 failure
FAILURE: Build failed with an exception.
....
Satisfying the Spec
Suppose the team decides to use Grails 2.0 to implement the specification. This is not a restriction based on our other technology choices, since gradle is a general purpose build tool and since our specification defines how a user will interact with our web project entirely through a browser. The decision is based on the convenience of the framework, community support, the plug-in ecosystem and the skills of the developers.
First, we need to create an empty web/grails-app directory to indicate to the grails bootstrap that the project will be a grails application.
Then, we will configure our web build for Grails 2.0 in a new web/build.gradle file.
From the project root we will initialize the grails project.
course_registry $> gradle grails-init
:web:grails-init
The ResolvedArtifact.getResolvedDependency() method is deprecated and will be removed in the next version of Gradle. | Configuring classpath | Error log4j:WARN No appenders could be found for logger (org.springframework.core.io.support.PathMatchingResourcePatternResolver). | Error log4j:WARN Please initialize the log4j system properly. | Error log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. | Environment set to development.....
BUILD SUCCESSFUL
Total time: 1 mins 36.328 secs
We can satisfy the spec simply by modifying application.properties with the expected web application deployment path and project version, the homepage to include the expected title, and messages.properties to contain the expected messages.
These hooks depend on a specific interface in the web project, i.e., the existence of the tasks webStart and webStop. We can deploy the Grails 2.0 artifact to an embedded Jetty server through gradle to satisfy this interface.
course_registry $> gradle test --info
....
Started Jetty Server
Gradle Worker 1 executing tests.
Test a user is greeted with an intro screen(timezra.course_registry.TeacherRegistrationSpec) PASSED
Gradle Worker 1 finished executing tests.
....
NB: The Jetty plugin must be applied before the grails plugin, or else you will see an error similar to the following:
We can satisfy this specification by generating a Grails Teacher domain object with fields for the name, email and password, along with a Grails controller that uses dynamic scaffolding to generate the view and actions available, and by adding a link to the scaffolded Create Teacher page on our web/grails-app/views/index.gsp. Since the point of this tutorial is not to cover basic Grails development, we do not need to go into those specifics here, but sample code can be found on the project site or in any Grails tutorial for further reference.
Page Objects
Before we get too far with automating our acceptance criteria, we should begin to look towards a more abstract representation of the testable components of our application. From the second acceptance test above, we can already see patterns emerging. For example, when we navigate to a particular page, it would be convenient to identify that we are on the correct page, perhaps by its title. It would also be convenient to represent each view in the application as its own object, and each view could encapsulate its specific WebElements. Finally, there is a small set of actions available for any WebElement on a page, and it would be helpful to build these actions into a testing DSL. Fortunately, by combining the Page Object pattern and Groovy's dynamic language features, we can achieve all these goals with a minimal amount of code.
CourseRegistryPage(WebDriverdriver){ this.driver=driver Stringtitle=splitByCharacterTypeCamelCase(getClass().simpleName).join' ' if(!title.equals(driver.title)){ thrownewIllegalStateException("Should be on page '${title}' but was on page '${driver.title}' instead") } }
NB: The constructor verifies that the page is correct by comparing the class name to the page title; in addition, the static goTo method demonstrates how to use the PageFactory for initializing a Page Object; the methodMissing declaration also showcases Groovy's ability to call dynamic methods based on the combination of the click, type and choose actions with individual WebElement names. Such phrases become first-class elements of the testing DSL.
We can see the use of WebElement injection and dynamic methods in action with a CreateUser page that serves as the base for the CreateTeacher and CreateStudent pages.
So far, we have done all our testing with a single WebDriver, i.e., the FirefoxDriver. For simple testing, a single driver works fine, but if we wish to ensure that our website works in multiple browsers, we will need to configure our tests to use multiple WebDrivers. Fortunately, since we are using Gradle to manage our dependencies, the InternetExplorerDriver and ChromeDriver should be available to us automatically, along with the AndroidDriver and IPhoneDriver for testing our application on mobile devices. In addition, an OperaDriver is also available.
NB: Some of these drivers require that additional platform-specific software be installed, so please read the project pages for documentation on additional requirements.
NB: Unlike JUnit tests which are parameterized by fixture, Spock specifications are parameterized per feature.
We will modify our TeacherRegistrationSpec to use the Spock where: block for parameterization, we will configure multiple WebDrivers, and we will share the instance driver field among the parameterized features.
Now that our specifications are running through multiple browsers on a single machine, we can move a step further towards testing multiple browser versions on multiple OSes with cloud-based services such as Sauce Labs. For such a powerful service, the configuration changes to our existing application are surprisingly simple. We will modify our TeacherRegistrationSpec to use a RemoteWebDriver for the specific browser and OS combination we would like to test along with our Sauce Labs username and API key as described in the Sauce OnDemand documentation. We will also need to add a Gradle plugin to start SauceConnect from the build, just as we start our web application before running acceptance tests. Finally, we should ensure that all references to localhost in our application are changed to the IP address of the machine where we will be running our tests; otherwise, we might see the SauceConnect proxy freeze (you might have a different experience, and this freezing might just be attributable to the Gremlins in my machine).
classTeacherRegistrationSpecextendsSpecification{ .... def"a user is greeted with an intro screen"(){ when: driver.get"http://<your.ip.address>:8080/course_registry" .... }
def"a user can register as a teacher"(){ when: driver.get"http://<your.ip.address>:8080/course_registry/" .... } .... protecteddefbrowsers(){ defcapabilities=DesiredCapabilities.internetExplorer() capabilities.setCapability("version","7") capabilities.setCapability("platform",Platform.XP) capabilities.setCapability("name","Testing Teacher Registration in Sauce")
This post took us through the definition of user stories for a small website, the setup of a Gradle project for automating the website build and the run of its corresponding acceptance tests, the creation of our first WebDriver-based Spock specification for our acceptance criteria and the fulfillment of that spec with a Grails 2.0 application, the use of Selenium PageObjects to create abstract representations of our web pages before those web pages are even written, all the way to the creation of a Gradle plugin for running our acceptance tests on various browser/OS combinations in Sauce Labs. Each of those items in itself is worthy of a tutorial series. The combination of all these elements demonstrates with a remarkably small amount of configuration and code the type of robust and scalable ATDD infrastructure that is possible and should be at the core of any enterprise web application.