For this month’s book review, I read Continuous Testing for DevOps Professionals: A Practical Guide from Industry Experts, by various authors and edited by Eran Kinsbruner. The book is divided into four sections: Fundamentals of Continuous Testing, Continuous Testing for Web Apps, Continuous Testing for Mobile Apps, and The Future of Continuous Testing.
Debugging for Testers
Wikipedia defines debugging as “the process of finding and resolving defects or problems within a computer program that prevent correct operation of computer software or a system”. Often we think of debugging as something that only developers need to do, but this isn’t the case. Here are two reasons why: first, investigating the cause of a bug when we find it can help the developer fix it faster. Second, since we write automation code ourselves, and since we want to write code that is of high quality just as developers do, we ought to know how to debug our code.
Let’s take a look at three different strategies we can employ when debugging code.
Console output:
Code that is executing in a browser or on a device generally outputs some information to the console. You can easily see this by opening up Developer Tools in Chrome or the Web Console in Firefox. When something goes wrong in your application, you can look for error messages in the console. Helpful error messages like “The file ‘address.js’ was not found” can tell you exactly what’s going wrong.
Often an error in an application will produce a stack trace. A stack trace is simply a series of error statements that go in order from the most recent file that was called all the way back to the first file that was called. Here’s a very simple example: let’s say that you have a Node application that displays cat photos. Your main app.js page calls a function called getCats which will load the user’s cat photos. But something goes wrong with getCats, and the application crashes. Your stack trace might look something like this:
Error: cannot find photos
at getCats.js 10:57
at app.js 15:16
at internal/main/run_main_module.js:17:47
- The first line of the stack trace is the error- the main cause of what went wrong.
- The next line shows the last thing that happened before the app crashed: the code was executing in getCats.js, and when it got to line 10, column 57, it couldn’t find the photos.
- The third line shows which file called getCats.js: it was app.js, and it called getCats at line 15, column 16.
- The final line shows what file was called to run app.js in the first place: an internal Node file that called app.js at line 17, column 47.
Stack traces are often longer, harder to read, and more complicated than this example, but the more you practice looking at them, the better you will get at finding the most important information.
Logging:
Much of what you see in the console output can be called logging, but there are often specific log entries set up in an application’s code that record everything that happens in the application. I’m fortunate to work with great developers who are adept at creating clear log statements that make it easy to figure out what happened when things go wrong.
Log statements often come with different levels of importance, such as Error, Warning, Info, and Debug. An application can sometimes be set to only log certain levels of statement. For example, a Production version of an application might be set to only log Errors and Warnings. When you’re investigating a bug, it may be possible to increase the verbosity of the logs so you can see the Info and Debug statements as well.
You can also make your own log statements, simply by writing code that will output information to the console. I do this when I’m checking to make sure that my automation code is working like I’m expecting it to. For example, if I had a do-while statement like this:
do {
counter++
}
while (counter < 10)
I might add a logging statement that tells me the value of counter as my program progresses:
do {
console.log (“The value of counter right now is: ” + counter)
counter++
}
while (counter < 10)
The great thing about creating your own log statements is that you can set them up in a way that makes the most sense to you.
Breakpoints:
A breakpoint is a place that you set in the code that will cause the program to pause. Software often executes very quickly and it can be hard to figure out what’s happening as you’re flying through the lines of code. When you set a breakpoint, you can take a look at exactly what all your variable values are at that point in the program. You can also step through the code slowly to see what happens at each line.
Debuggers are generally available in any language you can write code in. Here are some examples:
- Python uses the pdb library
- Javascript uses the debugger statement
- Powershell uses breakpoints in the Powershell ISE
- C# has all kinds of debugging tools in Visual Studio
- Java has debugging tools in Eclipse and IntelliJ
I hope this post helps you get started with both debugging your code, and investigating someone else’s bugs!
The Joy of JWTs
Have you ever used a JWT before? If you have tested anything with authentication or authorization, chances are that you have! The term JWT is pronounced “jot” and it stands for JSON Web Token. JWTs are created by a company called Auth0, and their purpose is to provide a method for an application to determine whether a user has the credentials necessary to request an asset. Why are JWTs so great? Because they allow an application to check for authorization without passing in a username and password or a cookie. Requests of all kinds can be intercepted, but a JWT contains non-sensitive data and is encrypted, so intercepting it doesn’t provide much useful information. (For more information about the difference between tokens and cookies, see this post.) Let’s learn about how JWTs are made!
A JWT has three parts, which are made up of a series of letters and numbers and are separated by periods. One of the best ways to learn about JWTs is to practice using the official JWT Debugger, so go to jwt.io and scroll down until you see the Debugger section.
Part One: Header
The header lists the algorithm that is used for encrypting the JWT, and also lists the token type (which is JWT, of course):
{
“alg”: “HS256”,
“typ”: “JWT”
}
Part Two: Payload
The payload lists the claims that the user has. There are three types of claims:
Registered claims: These are standard claims that are predefined by the JWT code, and they include:
iss (issuer)- who is issuing the claim
iat (issued at)- what time, in Epoch time, the claim was issued
exp (expiration time)- what time, in Epoch time, the claim will expire
aud (audience)- the recipient of the token
sub (subject)- what kinds of things the recipient can ask for
Public claims: These are other frequently-used claims, and they are added to the JWT registry. Some examples are name, email, and timezone.
Private claims: These are claims that are defined by the creators of an application, and they are specific to that company. For example, a company might assign a specific userId to each of their users, and that could be included as a claim.
Here’s an example used in the jwt.io Debugger:
{
“sub”: “1234567890”,
“name”: “John Doe”,
“iat”: 1516239022
}
Here the subject is 1234567890 (which isn’t a very descriptive asset), the name of the user who has access to the subject is John Doe, and the token was issued at 1516239022 Epoch time. Wondering what that time means? You can use this Epoch time converter to find out!
Part Three: Signature
The signature takes the first two sections and encodes them in Base64. Then it takes those encoded sections and adds a secret key, which is a long string of letters and numbers. Finally it encrypts the entire thing with the HMAC SHA256 algorithm. See my post from last week to understand more about encoding and encryption.
Putting It All Together
The JWT is comprised of the encoded Header, then a period, the encoded Payload, then another period, and finally the encrypted signature. The JWT Debugger helpfully color-codes these three sections so you can distinguish them.
If you use JWTs regularly in the software you test, try taking one and putting it in the JWT Debugger. The decoded payload will give you insight into how your application works.
If you don’t have a JWT to decode, try making your own! You can paste values like this into the Payload section of the Debugger and see how the encrypted JWT changes:
{
“sub”: “userData”,
“userName”: “kjackvony”,
“iss”: 1516239022,
“exp”: 1586606340
}
When you decode a real JWT, the signature doesn’t decrypt. That’s because the secret used is a secret! But because the first and second parts of the JWT are encoded rather than encrypted, they can be decoded.
Using JWTs
How JWTs are used will vary, but a common usage is to pass them with an API request using a Bearer token. In Postman, that will look something like this:
Testing JWTs
Now that you know all about JWTs, how can you test them?
- Try whatever request you are making without a JWT, to validate that data is not returned.
- Change or remove one letter in the JWT and make sure that data is not returned when the JWT is used in a request.
- Decode a valid JWT in the Debugger, change it to have different values, and then see if the JWT will work in your request.
- Use a JWT without a valid signature and make sure that you don’t get data in the response.
- Make note of when the JWT expires, and try a request after it expires to make sure that you don’t get data back.
- Create a JWT that has an issue time of somewhere in the future and make sure that you don’t get data back when you use it in your request.
- Decode a JWT and make sure that there is no sensitive information, such as a bank account number, in the Payload.
Have fun, and happy testing!
New Course! Postman Essential Training
BIG NEWS! My LinkedIn Learning course on Postman is now live! This course is an introduction to creating API requests and assertions with Postman. You’ll learn how to create a test collection, run it from the command line, and set it to run as an automated job in Jenkins.
You can access the course here: https://www.linkedin.com/learning/postman-essential-training
Encryption and Encoding
We’ve all encountered mysterious hashed passwords and encrypted texts. We’ve heard mysterious terms like “salted” and “SHA256” and wondered what they meant. This week I decided it was finally time for me to learn about encryption!
The first distinction we need to learn is the difference between encryption and encoding. Encoding simply means transforming data into a form that’s easier to transfer. URL encoding is a simple type of encoding. Here’s an example: the Coderbyte website has a challenge called “Binary Reversal”. The URL for the page is https://coderbyte.com/information/Binary%20Reversal; the space between “Binary” and “Reversal” is replaced with “%20”. There are other symbols, such as !, that are replaced in URL encoding as well. If you’d like to learn more about URL encoding, you can play around with an encoding/decoding tool such as this one.
Another common type of encoding is Base64 encoding. Base64 encoding is often used to send data; the encoding keeps the bytes from getting corrupted. This type of encoding is also used in Basic authentication. You may have seen a username and password encoded in this way when you’ve logged into a website. It’s important to know that Basic authentication is not secure! Let’s say a malicious actor has intercepted my login with Basic auth, and they’ve grabbed the authentication string: a2phY2t2b255OnBhc3N3b3JkMTIz. That looks pretty secure, right? Nope! All the hacker needs to do is go to a site like this and decode my username and password. Try it for yourself!
Book Review: Enterprise Continuous Testing
As I’ve mentioned in previous posts, this year I’m reading one testing-related book a month and reviewing it in my blog. This month I read Enterprise Continuous Testing, by Wolfgang Platz with Cynthia Dunlop.
This book aims to answer solve the problems often found in continuous testing. Software continuous testing is defined by the author as “the process of executing automated tests as part of the software delivery pipeline in order to obtain feedback on the business risks associated with a software release as rapidly as possible”. Platz writes that there are two main problems that companies encounter when they try to implement continuous testing:
1. The speed problem
Testing is a bottleneck because most of it is still done manually
Automated tests are redundant and don’t provide value
Automated tests are flaky and require significant maintenance
2. The business problem
The business hasn’t performed a risk analysis on their software
The business can’t distinguish between a test failure that is due to a trivial issue and a failure that reveals a critical issue
I have often encountered the first set of problems, but I never really thought about the second set. While I have knowledge of the applications I test and I know which failures indicate serious problems, it never occurred to me that it would be a good idea to make sure that product managers and other stakeholders can look at our automated tests and be able to tell whether our software is ready to be released.
Fortunately, Platz suggests a four-step solution to help ensure that the right things are tested, and that those tests are stable and provide value to the business.
Step One: Use risk prioritization
Risk prioritization involves calculating the risk of each business requirement of the software. First, the software team, including the product managers, should make a list of each component of their software. Then, they should rank the components twice: first by how frequently the component is used, and second by how bad the damage would be if the component didn’t work. The two rankings should be multiplied together to determine the risk prioritization. The higher the number is, the higher the risk; higher risk items should be automated first, and those tests should have priority.
An example of a lower-risk component in an e-commerce platform might be the product rating system: not all of the customers who use the online store will rate the products, and if the rating system is broken, it won’t keep customers from purchasing what’s in their cart. But a higher-risk component would be the ability to pay for items with a credit card: most customers pay by credit card, and if customers can’t purchase their items, they’ll be frustrated and the store will lose revenue.
Step Two: Design tests for efficient test coverage
Once you’ve determined which components should be tested with automation, it’s time to figure out the most efficient way to test those components. You’ll want to use the fewest tests possible to ensure good risk coverage. This is because the fewer tests you have, the faster your team will get feedback on the quality of a new build. It’s also important to make sure that each test makes it very clear why it failed when it fails. For example, if you have a test that checks that a password has been updated, and also checks that the user can log in, when the test fails you won’t know immediately whether it has failed on the password reset or on the login. It would be better to have two separate tests in this case.
Platz advocates the use of equivalence classes: this is a term that refers to a range of inputs that will produce the same result in the application. He uses the example of a car insurance application: if an insurance company won’t give a quote to a driver who is under eighteen, it’s not necessary to write a test with a driver who is sixteen and a driver who is seventeen, because both tests will test the same code path.
Step Three: Create automated tests that provide fast feedback
Platz believes that the best type of automated tests are API tests, for two reasons: one, while unit tests are very important, developers often neglect to update them as a feature changes, and two, UI tests are slow and flaky. API tests are more likely to be kept current because they are usually written by the software testers, and they are fast and reliable. I definitely agree with this assessment!
The author advises that UI tests should be used only in cases where you want to check the presence of or location of elements on a webpage, or when you want to check functionality that will vary by browser or device.
Step Four: Make sure that your tests are robust
This step involves making sure that your tests won’t be flaky due to changing test data or unreliable environments. Platz suggests that synthetic test data is best for most automated tests, because you have control over the creation of the data. In the few cases where it’s not possible to craft synthetic data that matches an important test scenario, masked production data can be used.
In situations where environments might be unreliable, such as a component that your team has no control over that is often unavailable, he suggests using service virtualization, where responses from the other environment are simulated. This way you have more control over the stability of your tests.
Enterprise Continuous Testing is a short book, but it is packed with valuable information! There are many features of the book that I didn’t touch on here, such as metrics and calculations that can help your team determine the business value of your automation. I highly recommend this book for anyone who wants to create an effective test automation strategy for their team.
Adventures in Node: Arrow Functions
This year I’ve been feeling an urge to really learn a programming language. There are lots of languages I know well enough to write automation code in- C#, Java, Javascript, and so on- but I decided I wanted to really dive into one language and learn to really understand it.
I decided to go deep with Node.js. Node is essentially Javascript with a server-side runtime environment. It’s possible to write complete applications in Node, because you can code both the front-end and the back-end of the application. And I was fortunate enough to find this awesome course by Andrew Mead. Andrew does a great job of making complicated concepts really simple, so as I am taking the course, I’m finding that things that used to confuse me about Node finally make sense! And because I like sharing things I’ve learned, I’ll be periodically sharing my new-found understanding in my blog posts.
I’ll start with arrow functions. Arrow functions have been around for a few years now, but I’ve always been confused by them, because they weren’t around when I was first learning to write code. You may have seen these functions, which use the symbol =>. They seem so mysterious, but they are actually quite simple! Arrow functions are simply a way to notate a function to save space and make code easier to read. I’ll walk you through an example. We’ll start with a simple traditional function:
const double = function(x) {
return x + x
}
double is the name of the function. When x is passed into the function, x + x is returned. So if I called the double function with the number 3, I’d get 6 in response.
Now we’re going to replace the function with an arrow:
const double = (x) => {
return x + x
}
Note that the arrow comes after the (x), rather than before. Even though the order is different, function(x) and (x) => mean the same thing.
Now we’re going to replace the body of the function { return x + x } with something simpler:
const double = (x) => x + x
When arrow functions are used, it’s assumed that what comes after the arrow is what will be returned. So in this case, x + x means the same thing as { return x + x }. This is only used if the body of the response is relatively simple.
See? It’s simple! You can try running these three functions for yourself if you have node installed. Simply create an app.js file with the first version of the function, and add a logging command:
console.log(double(3))
Run the file with node app.js, and the number 6 will be returned in the console.
Then replace version 1 of the function with version 2, run the file, and you should get a 6 again. Finally, replace version 2 with version 3, and run the file; you should get a 6 once again.
It’s even possible to nest arrow functions! Here’s an example:
const doublePlusTen = (x) => {
const double = (x) => x + x
return double(x) + 10
}
The const double = (x) => x + x is our original function. It’s nested inside a doublePlusTen function. The doublePlusTen is using curly braces and a return command, because there’s more than one line inside the function (including the double function). If we were going to translate this nested function into plain English, it would look something like this:
“We have a function called doublePlusTen. When we pass a number into that function, first we pass it into a nested function called double, which takes the number and doubles it. Then we take the result of that function, add 10 to it, and return that number.”
You can try out this function by calling it with console.log(doublePlusTen(3)), and you should get 16 as the response.
Hopefully this information will help you understand what an arrow function is doing the next time you encounter it in code. You may want to start including arrow functions in your own automation code as well. Stay tuned in the coming weeks for more Adventures in Node posts!
How I Would Have Tested the Iowa Caucus App
About six weeks ago, the Iowa Democratic Party held its caucus. For those who don’t live in the United States, this event is one of the first steps in the presidential primaries, which determine who will be running for president in the next presidential election.
- Usability testing: the application should be extremely easy to install and use.
- Functional testing: does the application actually do what it’s supposed to do? Testers should test both the happy path- where the user does exactly what is expected of them- and every possible sad path- where the user does something odd, like cancel the transaction or back out of the page.
- Device and carrier testing: testers should test on a wide variety of carriers, with a wide variety of providers, and with a wide variety of connection scenarios, including scenarios such as a wifi connection dropping in the middle of a transmission. Testers should also ensure that the application will work correctly overseas for the remote precincts. They can do this by crowd-sourcing a test application that has the same setup as the real application.
- Load and performance testing: testers should make sure that the application can handle 2500 simultaneous requests, which is much higher than the actual use case. They should also make sure that page response times are fast enough that the user won’t be confused and think that there’s something wrong with the application.
- Security testing: testers should run through penetration tests of the application, ensuring that they can’t bypass the login or hijack an http request.
- Backup phone system testing: testers should validate that they can make 2500 simultaneous calls to the backup phone system and be able to connect. Since there probably won’t be 2500 phone lines available, testers should make sure that wait times are appropriate and that callers are told how many people are in the queue in front of them.
- it will teach the leaders how to use the app
- it will validate that accurate counts are reported through the app
- it will help surface any issues with specific devices, operating systems, or carriers
- it will validate that the backup phone system works correctly
API Contract Testing Made Easy
As software becomes increasingly complex, more and more companies are turning to APIs as a way to organize and manage their application’s functionality. Instead of being one monolithic application where all changes are released at once, now software can be made up of multiple APIs that are dependent upon each other, but which can be released separately at any time. Because of this, it’s possible to have a scenario where one API releases new functionality which breaks a second API’s functionality, because the second API was relying on the first and now something has changed.
The way to mitigate the risk of this happening is through using API contract tests. These can seem confusing: which API sets up the tests, and which API runs them? Fortunately after watching this presentation, I understand the concept a bit better. In this post I’ll be creating a very simple example to show how contract testing works.
Let’s imagine that we have an online store that sells superballs. The store sells superballs of different colors and sizes, and it has uses three different APIs to accomplish its sales tasks:
Inventory API: This API keeps track of the superball inventory, to make sure that orders can be fulfilled. It has the following endpoints:
- /checkInventory, which passes in a color and size and verifies that that ball is available
- /remove, which passes in a color and size and removes that ball from the inventory
- /add, which passes in a color and size and adds that ball to the inventory
Orders API: This API is responsible for taking and processing orders from customers. It has the following endpoints:
- /addToCart, which puts a ball in the customer’s shopping cart
- /placeOrder, which completes the sale
Returns API: This API is responsible for processing customer returns. It has the following endpoint:
- /processReturn, which confirms the customer’s return and starts the refund process
- When the Orders API processes the /addToCart command, it calls the /checkInventory endpoint to verify that the type of ball that’s been added to the cart is available
- When the Orders API processes the /placeOrder command, it calls the /remove command to remove that ball from the inventory so it can’t be ordered by someone else
- When the Returns API runs the /processReturn command, it calls the /add command to return that ball to the inventory
- /checkInventory, where the body contained { “color”: “purple”, “size”: “small” }
- /remove, where the body contained { “color”: “red”, “size”: “large” }
- /add, where the body contained { “color”: “yellow”, “size”: “small” }
More Fun With Cypress
Two weeks ago, I wrote about my first experiences using Cypress.io. I was intrigued by the fact that it was possible to do http requests using Cypress commands, so this week I decided to see if I could combine API commands with UI commands in the same test. To be honest, it wasn’t as easy as I thought it would be, but I did manage to come up with a small proof-of-concept.
Part of the difficulty here may lie in the fact that there aren’t many websites around on which to practice both UI and API automation. For my experimentation, I decided to use the OWASP Juice Shop, which is a great site for practicing security testing. I wanted to log into the site using an HTTP command, and then use the token I retrieved from my login to navigate to the site as an authenticated user.
Setting up the HTTP command was pretty easy. Here’s what it looks like:
var token;
describe(‘I can log in as a user’, () => {
it(‘Logs in’, () => {
cy.request({
method: ‘POST’,
url: ‘https://juice-shop.herokuapp.com/rest/user/login’,
headers: {‘content-type’:’application/json’},
body: {
email: ‘foo5@bar.com’,
password: ‘123456’
}
})
.then((resp) => {
const result = JSON.parse(JSON.stringify(resp.body));
token = result.authentication.token;
expect(resp.status).to.eq(200);
})
});
});
Let’s take a look at what’s happening here. First I declare the token variable. The ‘I can log in as a user’ and ‘Logs in’ parts are just the names of the test section and the test. Then we have the cy.request section. This is where the http request happens. You can see the method, the url, the headers, and the body of the request. Next, there’s the then((resp), which shows what the test is doing with the response. With const result = JSON.parse(JSON.stringify(resp.body)), I’m parsing the body of the response into JSON format and saving it to a result variable. Then I’m setting the token variable to result.authentication.token. Finally, with expect(resp.status).to.eq(200) I’m doing a quick assertion that the status code of the response is 200 just to alert me if something didn’t come back correctly.
Next, I loaded the web page, and included the token in the browser’s local storage so the web page would know I was authenticated:
describe(‘Is logged in’, function() {
it(‘Is logged in’, function() {
cy.visit(‘https://juice-shop.herokuapp.com/#/’, {
onBeforeLoad (win) {
win.localStorage.setItem(‘token’, token)
},
})
cy.contains(‘Dismiss’).click();
cy.contains(‘Your Basket’).should(‘be.visible’);
})
});
With this line: cy.visit(‘https://juice-shop.herokuapp.com/#/’ I’m navigating to the web page. With the next section:
onBeforeLoad (win) {
win.localStorage.setItem(‘token’, token)
},
})
I’m telling the browser to put the token I saved into local storage. There was a popup window with a “Dismiss” button that appeared in the browser, so I closed it with cy.contains(‘Dismiss’).click(). And finally with cy.contains(‘Your Basket’).should(‘be.visible’) I asserted that the link called “Your Basket” was visible, because that link doesn’t appear unless the user is authenticated.
My code definitely wasn’t perfect, because I noticed that when I manually logged in, I saw my email address in the Account dropdown, but when I logged in through Cypress, the email address was blank. I also tried doing some other UI tasks, like adding an item to my cart, but I had trouble simply because the application didn’t have good element identifiers. (I so appreciate developers who put identifying tags on their elements! If your developers do this, please thank them often.) And there may be irregularities with this application because it was specifically designed to have security holes.
It would be very interesting to see how easy it would be to set up API and UI testing in Cypress when testing an application with normal authentication processes and good element identifiers! However, I think my experiment showed that it’s fairly easy to integrate API and UI tests together in Cypress.










