Stop Waiting for Developers! Utilize Code Scaffolding And Get Ahead
How to keep pace with development and stay sane by scaffolding your test code
The work-life of an automated tester often seems to be an endless game of catch-up. A sprint starts and developers begin their work, submitting their code somewhere in the middle to the end of the sprint. The tester then has to scramble to write their automation ASAP.
This situation is less than ideal. So what is one to do? How can test automation keep up with development while maintaining a high standard of quality? The answer is simple:
Code scaffolding
In this article, I will describe what code scaffolding is and how to best implement it. I will walk through a hypothetical example of it in practice using a user story and code examples.
What Happens Without Code Scaffolding?
So why is the situation described above “less than ideal”? Assuming test automation is part of your team’s “Definition of Done”, the crunch at the end of the sprint presents a few options to consider:
Option 1 - Quantity over quality
In this option, the person writing the test automation will crank out all the tests needed by the end of the sprint. Hooray! Unfortunately, because of their time constraints, they didn’t consider their solution from a high-level or larger architecture implications. They wrote the code required to meet the goal without regard to how it fits into the code base in general, how upcoming stories might update it, etc. This can lead to brittle code that is difficult to maintain, understand, and extend. Booooo! Sure, it works today. Let me know how it works in two years.
But let’s say we don’t want the quality of the automation code to suffer like it would with this option. Given that and the time crunch, let’s assume we will not complete the test automation by the end of the sprint. This brings us to the next option…
Option 2 - Roll the story into the next sprint
With this option, the team rolls the user story over to the next sprint. Though not terrible, this ultimately leaves a sour taste in the mouth of much of the team for a few reasons:
- The points for completed work are not collected until the next sprint. Your solution owner/project managers will not be happy if this happens too often.
- This can make it difficult to make an accurate sprint point estimation.
- Carrying a story this long makes context shifting more difficult for developers. Developers move to the next story after their previous code has been submitted and accepted. As time passes it becomes more difficult to shift their context back to the code you’ve now found an issue with. They will have to do some serious context shifting to get back in the frame of mind to make a fix on older code. This is far less efficient than if you were to find the issue a day or so after they submitted the code.
If you continue to roll stories into the next sprint, the following option is often considered…
Option 3 - Automation is decoupled from the “Definition of Done”
Along with many of the negatives presented in the option above (especially context shifting), this option devalues test automation itself. If your team is comfortable saying that automation is not important to the context of a story being considered done, it’s a slippery slope to saying that not all tests need to be automated. Eventually, your team may decide that test automation isn’t that important and isn’t done at all.
If any of this seems familiar, it’s time to consider doing things differently. It’s time to consider implementing code scaffolding into your test automation.
Defining Code Scaffolding
What is “code scaffolding”? I define it as such:
Code Scaffolding (n): A way to represent a feature in code without the requirements of functionality or completeness.
The point of code scaffolding is to quickly represent a new feature in your test code without needing it to work at that moment. The goal is to complete the scaffolding before development has delivered their code. Once they have completed their code, you will connect the loose ends and get it working.
One of the strengths of code scaffolding is that you can start writing it when a user story is ready. You don’t have to wait for developers to finish their code to start. This means you can represent new features in step with development rather than always trailing behind as in typical methods.
Breaking down the Acceptance Criteria
Let’s start with an example and walk through step-by-step. Let’s say it’s the start of a sprint. Below is a user story (ID: US 12345) describing a new feature on our website…
Using the acceptance criteria (AC), here’s what we know we will need to represent with our scaffolding:
- Mousing over the product will show the information overlay
- The information overlay will contain five unique objects
- The elements above will match the attached designs
- Mousing off the product will cause the information overlay to disappear
The objects described in #2 should get their own class. We can create tests for #1, 2, and 4. It is not easy to test #3 through automation so we will have to validate that manually. (Note: it is possible to create visual automated tests but this often requires 3rd party software and will be considered outside the scope of this article.)
Now that we’ve broken down the AC, let’s create that class for #2 and tests for 1, 2, and 4…
Basic Scaffolding
Now that we’ve broken down the AC, let’s start creating some code scaffolding!
Above is a basic class scaffold to capture the properties described in the AC (bullet #2). Below is a basic test code to support the AC (bullets #1, 2, & 4).
Congratulations — your absolute minimum code scaffolding is complete! We’re done!
… Or are we?
Oh no — there’s text below. We are not done …
Looking at the code, it doesn’t feel very helpful. We have a representation of our functionality and tests but there’s a lot of coding that is still needed. Before we move to the next thing, let’s make some improvements to these issues:
- The objects in the
ProductInformationOverlay
class don’t return anything or have types. - The tests have no setup code, don’t import the
ProductInformationOverlay
class, and aren’t ready to make any validations. Worst of all — if we ran these tests right now they would all return true when nothing is actually validated!
Let’s improve on what we’ve done and create some robust code scaffolding!
Robust Code Scaffolding — Classes
The first step for scaffolding your classes — as you can see from the code above — is to define what objects the class will need to have. We’ve done the rudimentary work but haven’t saved much time for future efforts. We still have to:
- Set the properties or use a
get
method to return something - Define what it is that you will be returning and return something
- Add any extra methods if appropriate
Step 1: Create ‘get’ methods
Let’s start by creating a get
method for each property. A get
method is often the best way to implement elements for UI automation. They ensure the elements returned have not been overwritten by dynamic events occurring on the page.
Now that we’ve updated the code (see left), we can continue adding to it.
Step 2: Defining and populating your ‘get’ methods
The next step is to type the get
methods and make them do something. Looking at the code below, you’ll notice a few changes:
- Each
get
statement is typed (:ElementFinder
) so that we know what type of object the method will return. - A
return
line has been added to eachget
. Each returns anElementFinder
object (Note: for more information on the particulars of typing or object types, see “Coding Notes” at the bottom of this article). - The selector strings for each element are different to demonstrate that these are placeholders. I used
'ADD_CSS_IDENTIFIER_HERE'
,'TBD'
, and''
as examples of what you could use. You will overwrite them with each element’s CSS selector values once development submits their code.
Now that we’ve fleshed out the properties of the class, it’s time to think more about any methods that could be useful.
Step 3: Adding extra methods
It is useful to also add scaffolding code for supporting methods if you know they will be necessary. Since we have yet to write out the test scaffolding it’s not clear if any support methods will be necessary. Let’s come back to this later.
Now, let’s finally start writing out some tests!
Robust Code Scaffolding — Tests
As we have seen from the code far above, the first step for scaffolding your tests is to write out the basic test descriptions to validate your acceptance criteria. The next steps are:
- importing the supporting classes that are necessary
- writing out as much functionality as possible
- leaving comments to help support the final effort for when development submits their code
Step 1: Importing the supporting classes
To validate the class we’ve created we need to ensure that we import it. Line 1 imports ProductInformationOverlay
, it’s declared on Line 4, and instantiated on Line 7. Because we instantiate it in a beforeEach
loop, it will be reset anew for each test (i.e., it
block). Now any test within the scope of the describe
block can use it.
This screenshot also shows a beginning of step 2 — writing out functionality. What’s here is only sudo-code, though; placeholder comments are used where functionality should go.
Looking at the sudo-code, each test starts with “mouse over the product”. We can move that step into the beforeEach
to DRY up the code. We also need to import another class that describes the product that we will be mousing over. (Don’t worry about this — you can assume the product
class has already been implemented in other code. After all, why would your team be working on a product overlay before they had a product itself?)
In our second screenshot, we import, declare, and instantiate the product. We then use the product’s native mouseOver()
method and then instantiate the overlay
object. Still, we haven’t completed the second step — to write out code that can be used to validate the code.
SIDEBAR — why write out validations that cannot actually be tested at this time? Good question! Writing out as many of the validations now will mean less work in the future. It can also help with understanding how everything will fit together and help you, as a tester, think through the feature even more. Creating these validations will also help you know whether you need to write out support methods for your class.
Step 2: writing out as much functionality as possible
Here is a screenshot of the testing code written out to validate the ProductInformationOverlay
and it’s objects.
The first and third tests validate that the overlay appears and disappears as described in the AC. There are a few things to notice in the second it
block:
- The AC doesn’t specify formatting for the pricing or title labels so all you can validate is that an empty string is not returned. You might want to ask your team to specify the formatting of these and then test against that.
- Along those lines, you’ll notice we’re not checking for the specific price or product title here. Because those were not specified in the AC there’s no test. Create a separate story to validate that the data shown is correct.
- It might be a good idea to ask whether the capitalization (or lack thereof) is correct on the button labels as written in the AC.
Something else to consider — if you were to run your testing suite with the code above as-is, you would expect to see three failing tests. You’ll need to exclude the tests so that they aren’t run with the suite until you reintroduce them.
SIDEBAR — why exclude the tests rather than comment them out? Good question! Both are ways to ensure your tests won’t run until they’re ready. I prefer excluding them (see the next screenshot) for the reason that your tests will still count in the final reporting as ignored/excluded. This is an indicator that you have tests that need attention before they can run. No such indicators are seen when using comments to exclude the tests. This also makes it easier to forget you need to go back and uncomment the tests when appropriate.
Step 3: Commenting
The last step is to leave comments to help support reintroducing the tests once development submits their code. There are only a couple of things to do here since we were able to finish coding all our tests:
- Exclude the tests. Placing an
x
beforedescribe
excludes the tests within its scope (in Jasmine, the testing framework used for these examples). You can achieve the same effect by placing anx
before eachit
. You’ll notice I also leave a comment above the excluded test block to explain why I excluded the tests. This will help future testers/developers who come across the code. - Leave references to the user story/test case. You’ll notice “Ref: US 12345” above each test. I use this to reference the user story that inspired these tests. Being able to reference the original user story may help those reading the code in the future when they’re trying to better understand the code. I also recommend referencing test cases here (though I didn’t have one to reference here).
But wait a minute… we never got back to Step 3 from way above!
RIGHT! STEP 3! Thanks for reminding me.
If you look at the testing code above, you’ll see the first and third tests have a line of code like this:
expect(await overlay.isPresent()).toBeTruthy();
await overlay.isPresent()
will not return true or false. It will throw an exception. Why? It’s because our framework (Protractor) defines isPresent()
as a method for ElementFinder
objects. If you look at the scaffolding code we wrote above, we didn’t define that support method for the ProductInformationOverlay
class, so let’s do that now…
In this screenshot, we define the isPresent()
method at the end of the class. The method assesses whether all the elements of the class are present or not and returns the result.
With this in place, our code scaffolding is now complete!
What’s left?
Now that you’ve completed the scaffolding for the classes and the tests, what’s the next step? The remaining tasks are as follows:
- wait for development to submit their code
- find the CSS selectors for the five elements in the class
- re-include the tests to the testing suite and remove any comments related to that
- run the tests to make sure they work as expected
So now that you’re done, sit back, relax, and wait for development to submit their code, confident that you won’t have much automation to finish at that point.
… Or start working on some other code scaffolding! Maybe do that instead ;-)
Code Notes
The code examples in this article use the following technologies:
- Code is written in TypeScript (very similar to JavaScript)
- The testing framework is Jasmine
- The general code framework is Protractor.
For more information on Promises and the ‘async’ and ‘await’ keywords, please read my previous article - “Redefining ‘Using Promises with the Page Object Model’”.
For more information on what to do when a sprint starts, please read my previous article, “The Sprint has started and I have a set of user stories to test. Now what?”.