One Request, Sixteen Assertions

Anyone who has been reading my blog for a while knows that I’m passionate about API testing.  I’m also passionate about using Postman for API testing, because in my opinion it’s the easiest way to test API requests.  So it makes me sad when I see people testing an API and only asserting that they got a 200 response!  In this week’s post, I’m going to take a simple GET request and show examples of 16 assertions that could be run on the request.

For the GET request, I’ll be using the wonderful Restful-Booker application created by Mark Winteringham to teach people how to do API testing.  The request URL I’ll be using is https://restful-booker.herokuapp.com/booking/1 which will retrieve the booking with the ID of 1. If you run this GET request in Postman, you’ll get a response like this:


The response has the first and last name of a hotel guest, the total price of their room, whether or not their deposit has been paid, and what their checkin and checkout dates are.

You may see different details in the response from those in the above image, because this is a site that is under frequent use as people practice making API requests to change booking information.

Let’s look at all the different assertions we can do on this response. To add these assertions yourself, click on the “Tests” tab just underneath the request URL.

1. First, we can add the most common assertion: asserting that the response code is correct.

pm.test(“Status code is 200”, function () { pm.response.to.have.status(200); });

The pm.test refers to “Postman test”. “Status code is 200” is the name of our assertion. And pm.response.to.have.status(200) is what we are expecting from the response.

2. We can also assert that the response comes back within a reasonable time frame, like this:

pm.test(“Response time is less than 1000ms”, function () { pm.expect(pm.response.responseTime).to.be.below(1000); });

This asserts that the response time is less than one second.

3-8. Next we can assert that expected headers are present:

pm.test(“Server header is present”, function () { pm.response.to.have.header(“Server”); }); pm.test(“Connection header is present”, function () { pm.response.to.have.header(“Connection”); });

pm.test(“Content-Length header is present”, function () { pm.response.to.have.header(“Content-Length”); }); pm.test(“Etag header is present”, function () { pm.response.to.have.header(“Etag”); }); pm.test(“Date header is present”, function () { pm.response.to.have.header(“Date”); }); pm.test(“Via header is present”, function () { pm.response.to.have.header(“Via”); });

These assertions are looking for the presence of a particular header rather than checking that the header has a specific value. The specific assertions above might not be particularly necessary, but sometimes it’s a good idea to assert that certain headers are in place, such as X-XSS-Protection (not in this API), which indicates that there are measures in place to protect from cross-site scripting.  

9-10.  We can also have assertions that validate that a specific header value is being returned:

pm.test(“X-Powered-By header value is Express”, function () {
    pm.response.to.be.header(“X-Powered-By”, “Express”);
});

pm.test(“Content-Type header value is application/json”, function () {
    pm.response.to.be.header(“Content-Type”, “application/json; charset=utf-8”);
});

The first assertion is validating that the server used is Express, and the second assertion is validating that the Content-Type of the response is application/json.

11.  We can assert that certain text is included in the body of the response:

pm.test(“Response contains last name”, function () {
    pm.expect(pm.response.text()).to.include(“lastname”);
});

In this assertion, I am validating that the text in the body of the response contains “lastname”.

12-13.  I can also assert that specific json fields exist in the response.  To do this, I first need to parse out the json in the response and save it to a variable:

var jsonData = pm.response.json();

Then I can assert on specific fields in the jsonData variable.  Here I am checking to make sure that the response has both a checkin field and a checkout field:

pm.test(“Response contains checkin date”, function () {
    pm.expect(jsonData.bookingdates.checkin).to.exist;
})

pm.test(“Response contains checkout date”, function () {
    pm.expect(jsonData.bookingdates.checkout).to.exist;

})

14-15. I can also assert that specific values are returned in the response:

var jsonData = pm.response.json();

pm.test(“Correct first name is returned in response”, function () { pm.expect(jsonData.firstname).to.eql(“Mark”); });

pm.test(“Deposit has been paid”, function () { pm.expect(jsonData.depositpaid).to.be.true; });

In this first assertion, I am validating that the value of the first name returned in the response is “Mark”. In the second assertion, I’m validating that “depositpaid” is true. Keep in mind that these values may actually change because others are using this API. When you add specific value assertions, you’ll want to be sure that your test data does not change; otherwise your tests will be flaky!

16. Finally, I can assert that a value is greater or less than a certain number:

var jsonData = pm.response.json();

pm.test(“Total price is greater than 100”, function () { pm.expect(jsonData.totalprice).to.be.above(100); });

In this case, I am asserting that the total price paid is greater than 100.

The types of assertions that you run on your API responses will vary depending on what is important for you to test. While you may not decide that all types are appropriate for your API, I hope that these examples show that there are many more things to assert on than simply “200 OK”!



Stop Writing So Many UI Tests!

If you were to guess the importance of various types of automated tests by looking at the number of tutorials and articles about them on the Web, you’d think that UI tests were the most important.  But this is not the case- so much of an application can be tested through other means, especially API tests.  API tests are faster and much less flaky than UI tests, and they’re easier to write as well!  Below are four types of tests that are better suited for API testing.

Login Tests:  It’s easy to cycle through all kinds of username and password combinations with API tests.  The response time from a POST to a login endpoint is lightning fast, as opposed to a UI test which has to wait for the username and password fields to be populated and wait for the login response to reach the browser.  To prove this, I created a Postman collection that had sixteen login tests with various user and password combinations.  The sixteen tests ran in less than three seconds!  Imagine how long the equivalent UI tests would take.  
However, you should have two automated UI tests: one that validates that the login page looks correct, and that the user is able to log in, and one that validates that an appropriate error message is displayed when the user attempts to log in with bad credentials.
CRUD Tests:  When you’re testing CRUD functionality, you’re testing how your application interacts with the underlying data store.  This is best done at the API level.  It’s easy to create GET, POST, PUT, and DELETE tests using a tool like Postman.  You can assert on both the response codes and the body of the response (if any), and you can also do GETs to assert that your POSTs, PUTs, and DELETEs have saved correctly to the database.
The only UI tests you need in this area are one that demonstrates that form fields can be filled out and submitted, and one that shows that data is displayed correctly on the page.
Negative Tests:  API testing is great for negative tests, because not only can you run through all kinds of negative scenarios very quickly, but you can also run tests that aren’t possible in the UI.  For example, let’s say that your form has a required field.  In the UI, you can’t do a test where you submit a new record without that required field, because the UI simply won’t let you.  But in the API, you can do a POST without the required field and verify that you are getting a 400-level response.  API testing is also great for checking application security, because malicious users are likely to try to attack the application at this level.  
Here is just a sampling of the types of negative tests you can run with API testing:
  • sending in an inaccurate URL
  • trying a request without appropriate authentication
  • testing for IDOR
  • sending in incorrect headers
  • sending in a request without a required field
  • trying a request with a field value that violates type or length constraints
  • verifying that the correct 400-level error is displayed when a request is invalid
For UI testing, you’ll simply want to verify that appropriate errors are displayed on the page when you leave a required field blank or violate a field constraint.  Everything else can be covered by API tests.
Tests of Complicated Business Logic:  If you have an area of your application that requires an extensive setup of data and complicated business logic, it’s much easier to test with an API than with the UI.  Consider my hypothetical Superball Sorter, which sorts balls of various colors and sizes among four children.  Setting up the rules through the UI in an automated test would be tedious; assuming each child had a dropdown picker for size and color, you’d need to do a lot of element selection.  But if the Superball Sorter had an API that could set all the rules for the children in a single POST, it would take milliseconds to prepare the test.  
Similarly, after the sorting has been run, a UI test would need to grab all the responses on the page to validate that the balls have been sorted correctly, where an API could do a GET request for each child and validate that the appropriate balls are returned.  Four GET requests will most likely be returned and validated before a UI test could validate a single child’s values.  
Now that you have seen the many ways that API tests can be used, I hope that you will take the time to look at your current UI test suite to see which tests could be shifted to API testing.  Your automation suite will be faster, more reliable, and easier to maintain as a result!

The Easiest MongoDB Tutorial on the Web

MongoDB is one of the most popular non-relational databases in use today.  Its versatility, speed, and scalability make it popular with applications that need to store data in a JSON-like format.  It’s very easy to install MongoDB and create a database, but the query language it uses is quite different from the SQL query language.  When I first started using MongoDB, I was frustrated by the documentation I found on queries; either they tried to explain too much or the examples were too complicated.  More than once I said things in frustration like “How can I simply ask for ‘select lastName from contacts where id = 3?!!'”.

It is because of this frustration that I have created this tutorial.  In the tutorial, I’ll be including several really simple examples of queries that you are probably used to running in SQL. 

Installing Mongo:

Because this post is really about writing queries, I’m going to skip the installation instructions, and instead send you here for MacOSX and here for Windows.  Once you have finished the installation instructions, you should have a command window open that is running mongo.

Creating a Database:
Creating a new database is unbelievably easy.  We’re going to name our new database tutorial.  To create it, simply type use tutorial.  You’ll get the response switched to db tutorial.  Tada!  Your database is created.

Adding a Document:
Of course, right now your database is completely empty.  Let’s change that by typing
db.tutorial.insertOne( { _id: 1, firstName: “Prunella”, lastName: “Prunewhip” } ).  
You will get a response of 
{ “acknowledged” : true, “insertedId” : 1 }
You have just added your first document!  Note that a “document” is the equivalent of a “record” in a SQL database.  Your document has an id (which is preceded by an underscore, by convention), a first name, and a last name.

Retrieving All Documents:
Let’s make sure that your document was really added by asking for it.  Type 
db.tutorial.find() 
and you should get this as a result: 
{ “_id” : 1, “firstName” : “Prunella”, “lastName” : “Prunewhip” }
The empty find() command will find all of the documents in the database.  At the moment, we only have one document, so that’s all that was returned.

Adding Multiple Documents:
To add several documents at the same time, use the InsertMany command, like this:

db.tutorial.insertMany([ { _id: 2, firstName: “Joe”, lastName: “Schmoe” }, { _id: 3, firstName: “Amy”, lastName: “Smith” }, { _id: 4, firstName: “John”, lastName: “Smith” }, { _id: 5, firstName: “Joe”, lastName: “Bagadonuts” }, { _id: 6, firstName: “Carol”, lastName: “Jones” }, { _id: 7, firstName: “Robert”, lastName: “Johnson” } ])

Note that each document is wrapped in curly braces, separated by commas.  You’ll get a response like this: 
{ “acknowledged” : true, “insertedIds” : [ 2, 3, 4, 5, 6, 7 ] }
Now you have seven records in your database.

If you retrieve all documents at this point using db.tutorial.find(), you’ll get a result like this:
{ “_id” : 1, “firstName” : “Prunella”, “lastName” : “Prunewhip” }
{ “_id” : 2, “firstName” : “Joe”, “lastName” : “Schmoe” }
{ “_id” : 3, “firstName” : “Amy”, “lastName” : “Smith” }
{ “_id” : 4, “firstName” : “John”, “lastName” : “Smith” }
{ “_id” : 5, “firstName” : “Joe”, “lastName” : “Bagadonuts” }
{ “_id” : 6, “firstName” : “Carol”, “lastName” : “Jones” }
{ “_id” : 7, “firstName” : “Robert”, “lastName” : “Johnson” }

Retrieving a Single Document:
To retrieve a single document, use this syntax:
db.tutorial.find( { _id: 1 } ).  
This will return the document with the id of 1: 
{ “_id” : 1, “firstName” : “Prunella”, “lastName” : “Prunewhip” }

Search for All Documents With a Single Value:
The previous search on id will always return just one document, because the id is unique.  If you want to search for all documents that have a particular value, you can use 
db.tutorial.find({ lastName: “Smith”}).  
This will return all documents that have the last name Smith:
{ “_id” : 3, “firstName” : “Amy”, “lastName” : “Smith” }
{ “_id” : 4, “firstName” : “John”, “lastName” : “Smith” }

Search for One Value in One Document:
Let’s say you want to find the last name of the document with the id of 3.  To do this, type:
db.tutorial.find({ _id: 3}, {lastName:1, _id:0}).  
You will get this result:  
{ “lastName” : “Smith” }
The _id:0 is there to specify that you don’t want the id returned in the response; returning the id in the response is a default behavior in MongoDB.

Return All the Values for a Specific Field:
If you wanted to get a list of all the last names in your database, you would use
db.tutorial.find({}, {lastName:1, _id:0}).  
This would return
{ “lastName” : “Prunewhip” }
{ “lastName” : “Schmoe” }
{ “lastName” : “Smith” }
{ “lastName” : “Smith” }
{ “lastName” : “Bagadonuts” }
{ “lastName” : “Jones” }
{ “lastName” : “Johnson” }

Search with “Starts With”:
MongoDB uses regex to search on field values.  To search for all the documents that have last names that begin with S, you’d do this search:
db.tutorial.find({ lastName: /^S/}).  
This will return 
{ “_id” : 2, “firstName” : “Joe”, “lastName” : “Schmoe” }
{ “_id” : 3, “firstName” : “Amy”, “lastName” : “Smith” }
{ “_id” : 4, “firstName” : “John”, “lastName” : “Smith” }

Search with “And”:
If you wanted to search for a document that had a specific first name AND a specific last name, you’d search like this:
db.tutorial.find( {$and: [{ lastName: “Smith” },{ firstName: “Amy”}]} )
which would return 
{ “_id” : 3, “firstName” : “Amy”, “lastName” : “Smith” }.

Search with “In”:
To search for all the documents that have either the last name Smith or the last name Jones, you’d use:
db.tutorial.find({lastName :{$in :[“Smith”,”Jones”]}}).  
This will return
{ “_id” : 3, “firstName” : “Amy”, “lastName” : “Smith” }
{ “_id” : 4, “firstName” : “John”, “lastName” : “Smith” }
{ “_id” : 6, “firstName” : “Carol”, “lastName” : “Jones” }

Update a Document:
If you’d like to change an existing document, you can use the Update command.  For example, to change the last name of the third document from Smith to Jones, you would type:
db.tutorial.updateOne({_id: 3 }, {$set: {lastName: “Jones”}}).  
You’ll get this response: 
{ “acknowledged” : true, “matchedCount” : 1, “modifiedCount” : 1 }.

To verify that the record was updated correctly, you can use db.tutorial.find( { _id: 3 } ), which will return { “_id” : 3, “firstName” : “Amy”, “lastName” : “Jones” }.

Delete a Document:
Finally, there may be times where you want to delete a document.  This can be done with
db.tutorial.deleteOne({_id: 4 })
which will return a response of 
{ “acknowledged” : true, “deletedCount” : 1 }.

To verify that the document has been deleted, you can run db.tutorial.find() and get this response:
{ “_id” : 1, “firstName” : “Prunella”, “lastName” : “Prunewhip” }
{ “_id” : 2, “firstName” : “Joe”, “lastName” : “Schmoe” }
{ “_id” : 3, “firstName” : “Amy”, “lastName” : “Jones” }
{ “_id” : 5, “firstName” : “Joe”, “lastName” : “Bagadonuts” }
{ “_id” : 6, “firstName” : “Carol”, “lastName” : “Jones” }
{ “_id” : 7, “firstName” : “Robert”, “lastName” : “Johnson” }
and you can see that the document with the id of 4 is no longer in the database.

This is by no means a complete record of everything that you can do with MongoDB, but it should be enough to get you started.  You can also refer to last week’s post to get a few examples of interacting with nested values in MongoDB.  I hope that you will find today’s post helpful in understanding how Mongo works, and that you will use it as a reference whenever you need it.  Happy querying!

Testing With Non-Relational Databases

Last week, I took a look at ways to query relational databases for testing.  This week I’m going to look at non-relational databases, describe how they are different from relational databases, and discuss how to query them in your testing.  Non-relational databases, such as MongoDB and DynamoDB, are sometimes called “NoSQL” databases, and are becoming increasingly popular in software applications.

The main difference between relational and non-relational databases is that relational databases use tables to store their data, where non-relational tables use documents.  The documents are often in JSON format.  Let’s take a look at what the records in the Contacts table from last week’s post would look like if they were in a non-relational database:

{
              contactId: “10000”,
              firstName: “Prunella”,
              lastName: “Prunewhip”,
              email: “[email protected]”,
              phone: “8005551000”,
              city: “Phoenix”,
              state: “AZ”
}
{
              contactId: “10001”,
              firstName: “Joe”,
              lastName: “Schmoe”,
              email: “[email protected]”,
              state: “RI”,
}
Note that Joe does not have a value for phone or city entered, so they are not included in his document.  This is different from relational databases, which are required to include a value for every field. Instead of having a NULL value for phone and city as Joe’s record did in the SQL table, those fields are simply not listed.
Another key difference between relational and non-relational databases is that it’s possible to add a new field into a table without adding it in for every document.  Let’s imagine that we are adding a new record to the table, and we want that record to include a spouse’s name.  When that record is added, it will look like this:

{
              contactId: “10002”,
              firstName: “Amy”,
              lastName: “Smith”,
              email: “[email protected]”,
              phone: “8885551001”,
              city: “Boise”,
              state: “ID”,
              spouse: “John”
}
The original documents, 10000 and 10001, don’t need to have this spouse value.  In a relational database if a new field is added, the entire schema of the table needs to be altered, and Prunella and Joe will need to have spouse values or NULL entered in for those fields.

With a non-relational database, it’s not possible to do joins on table data as you saw in last week’s post.  Each record should be treated as its own separate document, and you can do queries to retrieve the documents you want.  What that query language looks like depends on the type of the database used.  The examples below are using MongoDB’s query language, which is JavaScript-based, and are querying on the documents listed above:

db.contacts.find() – this will return all the contacts in the table
db.contacts.find( { contactId: “10001” } ) – this will return the document for Joe Schmoe

To make the responses easier to read, you can append the command .pretty(), which will organize the data returned in JSON format rather than a single line of values. 

You can also run a query to return a single field for each document:

db.contacts.find({}, {firstName:1, _id:0}) – this will return just the first name for each contact

Because the documents in a non-relational database have a JSON-like structure, it’s possible to have documents with arrays.  For example, our Contacts table could have a document that lists the contact’s favorite foods:

{
              contactId: “10000”,
              firstName: “Prunella”,
              lastName: “Prunewhip”,
              email: “[email protected]”,
              phone: “8005551000”,
              city: “Phoenix”,
              state: “AZ”,
              foods: [ “pizza”, “ice cream” ]
}

It’s even possible to have objects within arrays, as follows:

{
              contactId: “10001”,
              firstName: “Joe”,
              lastName: “Schmoe”,
              email: “[email protected]”,
              state: “RI”,
              pets: [ { type: “dog”, name: “fido” }, { type: “cat”, name: “fluffy” } ]
}

You can see how this type of data storage might be advantageous for your application’s data.  Nesting data in this fashion makes it easier to read at a glance than it would be in a relational database, where the pets might be in their own separate table.

To run a query that will return all the contacts that have cats, you would simply request:

db.contacts.find( {“pets.type”:”cat”} )

To run a query that will return all the contacts that have cats named Fluffy, you would request:

db.contacts.find( {$and: [{“pets.type”:”cat”},{“pets.name”:”fluffy”}]} )

These are just a few simple examples of how to query data with a non-relational database, and they should be enough to get you started in your testing.  To learn more, be sure to read the documentation for the type of database you are using.  As non-relational databases become increasingly popular, this knowledge will be extremely useful.  

Testing With Relational Databases

In last week’s post, I discussed various ways to test your application’s database.  In order to verify that your data has been saved correctly, you’ll need to query the database, and the way to query the database will depend on what type of database you have.  In the past, most databases were relational, but in recent years there has been a trend towards using non-relational databases.  In this week’s post, I’ll address relational databases, and in next week’s post, I’ll talk about non-relational databases.

Relational databases, such as MySQL and Microsoft SQL Server, are based on tables.  Each table relies on a schema, which defines what columns will be in the table, what data types they will have, and which columns will accept null values.  Here’s an example of a typical SQL table:

contactId
firstName
lastName
email
phone
city
state
10000
Prunella
Prunewhip
8005551000
Phoenix
AZ
10001
Joe
Schmoe
NULL
NULL
RI

Note that there are seven different columns in the table.  The first column in the table, contactId, is the primary key for the table. This will be a unique value; there will never be two contactIds with the same value. 

With a relational database, the schema remains unchangeable, so when Joe Schmoe is added to the database without a phone or city, those places in the table need to be filled with NULL.

Tables in a relational database can connect to each other.  Here is a table in the same database that shows the contact’s favorite foods:

foodId
contactId
food
1
10000
Pizza
2
10000
Ice cream
3
10001
Sushi

In this table the primary key is the foodId.  But notice that the contactId is present in this table.  The contactId here is the same as the contactId in the first table.  So we can see in this table that Prunella has two different favorite foods, pizza and ice cream, and Joe’s favorite food is sushi.

When testing a relational database, you can use SQL query language to verify that the values you are looking for are present in the database.  For example, if you had just added a new contact with the name of Amy Smith to the Contacts table, you could query the database to see if it had been added, like this:

select * from Contacts where lastName = ‘Smith’ and firstName = ‘Amy’

and the query would return a table row in response:

contactId
firstName
lastName
email
phone
city
state
10003
Amy
Smith
8885551001
Boise
ID

In the above query, the asterisk * tells SQL that we want all of the columns for the record returned.

Because this is a relational database, you could also do a query with a join.  A SQL join combines the data from two tables, joining on a column that they have in common.  

In the example above, both columns have the contactId column.  Let’s say that you have given your new contact Amy a favorite food (chocolate), and you want to verify that it saved to the database correctly, but you don’t know what Amy’s contactId is.  You can’t just query the Food table for “Amy Smith” because her first and last names aren’t in there.  And you can’t query the Contacts table for the food, because it’s not in that table.  But you could query the Contacts table with that information, get the contactId from that, and then use the contactId to query the Food table for the favorite food.  

This is what such a query would look like:

select food from Foods 
inner join on Contacts 
where Foods.contactId = Contacts.contactId 
and Contacts.firstName  = ‘Amy’
and Contacts.lastName = ‘Smith’ 

and the query will return this response:

food
Chocolate

  
Let’s walk through what happens in the query.
select food from Foods – this tells SQL to return just the food column from the Foods table
inner join on Contacts – this tells SQL that the query will be joining information from the Foods table with information from the Contacts table
where Foods.contactId = Contacts.contactId – this is instructing SQL to find the contactIds in the Foods table and match them up with the contactIds from the Contacts table
and Contacts.firstName  = ‘Amy’ and Contacts.lastName = ‘Smith’  – these last two lines are telling SQL that we are only interested in the record with the first name Amy and the last name Smith 

There are many more complicated ways to query a relational database, but with these two query types you will be able to do most of your data validation.  

Be sure to check out next week’s post, where I’ll talk about how to test with non-relational databases!

Database Testing

As software testers we often take for granted the fact that our application has a database behind it.  We tend to focus on the visible, such as the user interface, or the application logic of the API when we are testing.  But it’s important to test the database as well!  Below are six ways to test your application’s database.

1. Verify that the data fields are the correct type

Each data field in the database will be a specific type, such as int, float, string, or datetime.  Verify that each field’s datatype is appropriate.  For example, a date field in the application should be saved as a datetime type rather than as a string.  This might not seem like a big deal, but if sorting functionality is added to the application, you’ll find that April 10 is sorted before January 11 because the dates are being sorted alphabetically.

2. Verify that the fields that are required in the database are also required in the API and the UI

Generally each record in a database will have some fields that are required.  If a field is required in the database, it should also be required in the API and the UI.  If the UI doesn’t set the last name field as required, but the database has set it as required, a user could submit a new record without a last name and the database will return an error.

3. Verify that the parameters set on the fields in the database match the parameters in the API and the UI

Data fields in a database will have specific limits set, such as a character limit for a string, or a maximum value for an int. The fields in the API and the UI should have the same limits.  If the limits do not match, confusion can ensue.  Let’s say for example that your application has a field for a street address.  In the UI the developer has set the character limit to 50 characters, but in the database, the limit is set to 40.  If a user types in a street address with 45 characters, the UI will accept that value, but the database will not.  This will result in the record not being saved, and it won’t be obvious to the user what the problem is.

4. Verify that sensitive data, such as user passwords, are encrypted in the database

I once worked for a company that did not encrypt their user passwords.  I had access to the production database, which meant that I could see the username and password for every single customer of the company.  It should be pretty obvious that this was a huge security risk!  Passwords should always be encrypted in the database so there is no way for anyone but the user to know what their password is.

5. Verify that your database supports all your API’s operations

Just because your POST request returns a 200 doesn’t mean that the data was saved to the database correctly.  When you make an API request, make sure that the database saved every field correctly.  I have seen situations where a PUT request did not result in every new value being saved to the database.  I’ve also seen a situation where a PATCH request updated the correct fields, but set every other field to null!  Be sure to test every available CRUD operation, and check every field for accuracy.  Also make sure to change field values from null to an entry, and from an entry to null.  If a field you are testing is a string, determine with your team whether empty strings will be allowed, and test going from null to an empty string, an empty string to a value, from a value to an empty string, and so on.

In addition, if your API is going to support the DELETE operation, find out from your team whether those deletes will be hard-deletes, meaning that the record will be completely removed from the database, or soft-deletes, meaning that the record stays in the database, but is moved to another table or stays in the existing table with a “deleted” flag set.  Then test the DELETE operation to verify that it is behaving as expected.

6. Verify that leading and trailing spaces are removed when saving to the database

Have you ever had trouble logging in to an application where you are sure you have the right username?  The problem could be that when the username was originally created, a trailing space was accidentally added, and it was saved to the database with the extra space.  When you tried to log in as “doglvr49”, the system was expecting “doglvr49 “.  Similarly, if you entered a contact’s last name as ” Jones” instead of “Jones” and the leading space wasn’t trimmed, and you tried to sort the contacts alphabetically, you’d find ” Jones” before “Allan”.  When you are testing text fields in your application, try testing them with leading and trailing whitespaces, and then verify in the database that those spaces have been trimmed.

Issues such as having a mismatch between character limits in the database and the UI, or having a string instead of an int, or having trailing spaces in a record, can seem like small issues, but they can result in big problems.  By following these six steps in database testing, you will help ensure that your end users will have a good experience entering and retrieving data.

Why The Manual vs. Automation Debate is Wrong

I don’t generally editorialize in my blog- I prefer to focus on what to test rather than theories of testing- but I feel compelled to say that I’m tired of the whole “manual vs. automated testing” discussion.  Some people describe automated testing as the cure for bad code everywhere, and others lament the poor manual tester who has no technical skills.  Meanwhile, there are advocates who say that automation is merely a panacea, and that automation code should be used only for simple tools that will aid the manual tester, who is the one who really knows the product.

In my opinion, this debate is unnecessary for two reasons:
1) “Manual” and “automated” are arbitrary designations that don’t really mean anything.  If I write a Python script that will generate some test data for me, am I now an automation engineer?  If I log into an application and click around for a while before I write a Selenium test, am I now a manual tester?  
2) The whole point of software testing- to put it bluntly- is to do as much as we can to ensure that our software doesn’t suck.  We often have limited time in which to do this.  So we should use whatever strategies we have available to test as thoroughly as we can, as quickly as possible.  
Let’s take a look at three software testers: Marcia, Cindy, and Jan.  Each of them is asked to test the Superball Sorter (a hypothetical feature I created, described in this post).  
Marcia is very proud of her role as a “Software Developer in Test”.  When she’s asked to test the Superball Sorter, she thinks it would be really great to create a tool that would randomly generate sorting rules for each child.  She spends a couple of days working on this, then writes a Selenium test that will set those generated rules, run the sorter, and verify that the balls were sorted as expected.  Then she sets her test to run nightly and with every build.  
Unfortunately, Marcia didn’t take much time to read the acceptance criteria, and she didn’t do any exploratory testing.  She completely missed the fact that it’s possible to have an invalid set of rules, so there are times when her randomly generated rules are invalid.  When this happens, the sorter returns an error, and because she didn’t account for this, her Selenium test fails.  Moreover, it takes a long time for the test to run because the rules need to be set with each test and she needed to build in many explicit waits for the browser to respond to her requests.  
Cindy is often referred to as a “Manual Tester”.  She doesn’t have any interest in learning to code, but she’s careful to read the acceptance criteria for the Superball Sorter feature, and she asks good questions of the developers.  She creates a huge test plan that accounts for many different variations of the sorting rules, and she comes up with a number of edge cases to test.  As a result, she finds a couple of bugs, which the developers then fix.
After she does her initial testing, she creates a regression test plan, which she faithfully executes at every software release.  Unfortunately, the test plan takes an hour to run, and combined with the other features that she is manually testing, it now takes her three hours to run a full regression suite.  When the team releases software, they are often held up by the time it takes for her to run these tests.  Moreover, there’s no way she can run these tests whenever the developers do a build, so they are often introducing bugs that don’t get caught until a few days later.
Jan is a software tester who doesn’t concern herself with what label she has.  She pays attention during feature meetings to understand how the Superball Sorter will work long before it’s ready for testing.  Like Cindy, she creates a huge test plan with lots of permutations of sorting rules.  But she also familiarizes herself with the API call that’s used to set the sorting rules, and she starts setting up a collection of requests that will allow her to create rules quickly.  With this collection, she’s able to run through all her manual test cases in record time, and she finds a couple of bugs along the way.
She also learns about the API call that triggers the sorting process, and the call that returns data about what balls each child has after sorting.  With these three API calls and the use of environment variables, she’s able to set up a collection of requests that sets the rules, triggers the sorting, and verifies that the children receive the correct balls.  
She now combines features from her two collections to create test suites for build testing, nightly regression testing, and deployment testing.  She sets up scripts that will trigger the tests through her company’s CI tool.  Finally, she writes a couple of UI tests with Selenium that will verify that the Sorter’s page elements appear in the browser correctly, and sets those to run nightly and with every deployment.
With Jan’s work, the developers are able to discover quickly if they’ve made any changes in logic that cause the Superball Sorter to behave differently.  With each deployment, Jan can rest assured that the feature is working correctly as long as her API and UI tests are passing.  This frees up Jan to do exploratory testing on the next feature.
Which of these testers came up with a process that more efficiently tested the quality of the software?  Which one is more likely to catch any bugs that come up in the future?  My money’s on Jan!  Jan isn’t simply a “manual tester”, but she isn’t a “software developer in test” either.  Jan spends time learning about the features her team is writing, and about the best tools for testing them.  She doesn’t code for coding’s sake, but she doesn’t shy away from code either.  The tools and skills she utilizes are a means to ensure the highest quality product for her team.  

What To Do When There’s a Bug in Production

There is nothing quite as bone-chilling to a software tester than the realization that a bug has been found in Production!  In this post, I’ll walk through a series of steps testers can take to handle Production bugs and prevent them in the future.

Step One: Remain Calm

Because we are the ones who are testing the product and signing off on the release, it’s easy to panic when a bug is found in Production.  We ask ourselves “How could this have happened?” and we can be tempted to thrash around looking for answers.  But this is not productive.  Our top priority should be to make sure that the bug is fixed, and if we don’t stay calm, we may not investigate the issue properly or test the fix properly.

Step Two: Reproduce the Issue

If the issue came from a customer, or from another person in your company, the first thing to do is to see if you can reproduce the issue.  It’s possible that the issue is simply user error or a configuration problem.  But don’t jump to those conclusions too quickly!  Make sure to follow any steps described by the user as carefully as you can, and wherever possible, make sure you are using the same software and hardware as the user: for example, use the same type of mobile device and the same build; or the same type of operating system and the same browser.  If you are unable to reproduce the issue, ask the user for more information and keep trying.  See How to Reproduce A Bug for more tips.

Step Three: Gather More Information

Now that you have reproduced the issue, gather more information about it.  Is the issue happening in your test environment as well?  Is the issue present in the previous build?  If so, can you find a build where the issue is not present?  Does it only happen with one specific operating system or browser?  Are there certain configuration settings that need to be in place to see the issue?  The more information you can gather, the faster your developer will be able to fix the problem.

Step Four: Understand the Root Cause

At this point, your developer will be working to figure out what is causing the bug.  When he or she figures it out, make sure they tell you what the problem was, and make sure that you understand it.  This will help you figure out how to test the fix, and also to determine what regression tests should be done.

Step Five: Decide When to Fix the Issue

When there’s a bug in Production, you will want to fix it immediately, but that is not always the best course of action.  I’m sure you’ve encountered situations where fixing a bug created new ones.  You will want to take some time to test any areas that might have been impacted by the fix.

When trying to decide when to fix a bug, think about these two things:

  • how many users are affected by the issue?
  • how severe is the issue?

You may have an issue where less than one percent of your users are affected.  But if the bug is so severe that the users can’t use the application, you may want to fix the bug right away.

Or, you may have an issue that affects all of your users, but the issue is so minor that their experience won’t be impacted.  For example, a misaligned button might not look nice, but it’s not stopping your users from using the application.  In this case, you might want to wait until your next scheduled release to fix the issue.

Step Six: Test the Fix

When you test the bug fix, don’t check it just once.  Be sure to check the fix on all supported browsers and devices.  Then run regression tests in any areas affected by the code change.  If you followed Step Four, you’ll know which areas to test.  Finally, do a quick smoke test to make sure no important functionality is broken.

Step Seven: Analyze What Went Wrong

It’s tempting to breathe a big sigh of relief and then move on to other things when a Production bug is fixed.  But it’s very important to take the time to figure out exactly how the bug got into Production in the first place.  This is not about finger-pointing and blame; everybody makes mistakes, whether they are developers, testers, product owners, managers, or release engineers.  This is about finding out what happened so that you can make changes to avoid the problem next time.

Perhaps your developers made a code change and forgot to tell you about it, so it wasn’t tested.  Perhaps you tested a feature, but forgot to test it in all browsers, and one browser had an issue.  Maybe your product owner forgot to tell you about an important use case, so you left it out of your test plan.

Whatever the issue, be sure to communicate it clearly to your team.  Don’t be afraid to take responsibility for whatever your part was in the issue.  See my post on Extreme Ownership for more details.

Step Eight: Brainstorm Ways to Prevent Similar Issues in the Future

Now that you know what went wrong, how can you prevent it from happening again?  Discuss the issue with your team and see if you can come up with some strategies.

You may need to change a process: for example, having your product owner sign off on any new features in order to make sure that nothing is missing.  Or you could make sure that your developers let you know about any code refactoring so you can run a regression test, even if they are sure they haven’t changed anything.

You may need to change your strategy: you could have two testers on your team look at each feature so it’s less likely that something will be overlooked.  Or you could create test plans which automatically include a step to test in every browser.

You may need to change both your process and your strategy!  Whatever the situation, you can now view the bug that was found in Production as a blessing, because it has resulted in you being a wiser tester, and your team being stronger.

Merge Conflict Resolution for the Confused

Anyone working with version control software such as Git will eventually come across a merge conflict.  If you are new to working with Git, here is a simple example of a merge conflict:

The master branch contains a file with this text:
Kristin Jackvony was here on May 22, 2019

Prunella and Joe each check out a version of this master branch.  Prunella makes a branch called “Prunella” and Joe makes a branch called “Joe”.  

Joe updates the file in his branch to read:
Kristin Jackvony was here on May 22, 2019
Joe Schmoe was here on May 23, 2019

Joe does a pull request for his file changes, and they are approved and merged into the master branch.

Shortly thereafter, Prunella updates the file in her branch to read:
Kristin Jackvony was here on May 22, 2019
Prunella Prunewhip was here on May 23, 2019

She also does a pull request for her file changes, but because she no longer has the latest version of the master branch, there is a merge conflict.  Git sees that she wants to add her name to the second line of the file, but her version of the master branch doesn’t have anything on the second line, whereas the new version of the master branch already has Joe’s name on it.  Prunella will need to resolve the conflict before her changes can be merged.

I’ll be honest; I really wanted to master resolving Git merge conflicts for this post. However, resolving them from the command line still confounds me!  Fortunately, I’ve found a number of different ways to handle merge conflicts so that they no longer fill me with dread.  Below I’ll discuss six steps for handling a conflict.

Step Zero: Avoid Having a Merge Conflict In The First Place

By using the helpful hints in last week’s post, especially tips one, two, and six, you can avoid creating a merge conflict.  Doing a pull from master is always a good idea before you do anything code-related.

Step One: Don’t Panic

When you have a merge conflict, it’s important not to thrash around trying “git this” and “git that”.  You might make things more confusing this way.  A merge conflict will eventually be solved, even if you have to resort to asking for help (Step Seven).  And you can’t possibly have done anything irreversible; that’s the beauty of version control!

Step Two: Resolve the Conflict from Within GitHub 

GitHub has an easy interface that will allow you to resolve merge conflicts.  Simply click the Resolve Conflicts button and observe the conflict.  It will look something like this:

>>>>>HEAD
Joe Schmoe was here on May 23, 2019
=====
Prunella Prunewhip was here on May 23, 2019
<<<<< Prunella

All you need to do is decide which entry you want to go on line 2, and which entry you want to go on line three, and make those edits, deleting the extraneous symbols and branch names along the way.  When you are done, your file will look like this:

Kristin Jackvony was here on May 22, 2019
Joe Schmoe was here on May 23, 2019
Prunella Prunewhip was here on May 23, 2019

Now click the Mark as Resolved button, and your pull request should be ready for merging.

Step Three: Resolve the Conflict from the Command Line

If you are using Git as a version control system, but you are not using GitHub to host your repositories, you may not have a nice UI to work with in order to resolve your conflict.  In this case, you may need to use the command line.  

What I do in this scenario is open the file with the merge conflict in a text editor such as TextEdit or Notepad++, I edit the file so that it looks the way I want it to, removing all the extraneous symbols and branch names, then I do a git add with the filename, and then a git commit.  I do this in accordance with the instructions found here: https://help.github.com/en/articles/resolving-a-merge-conflict-using-the-command-line.  However, I have only had success with this once or twice.  Usually I have to go on to Step Four or Step Five.

Step Four: Forget About Your Existing Branch and Make a New One

In this scenario, I copy the text of the entire file with all my changes, and I paste it somewhere outside the code.  Then I go to the master branch and do a git pull origin master. Now I should have all the latest changes.  Then I create a brand new branch, switch to that branch, and paste in all of my file changes.  Then I do a fresh new pull request, which won’t have a merge conflict.

Step Five: The Nuclear Option

If Step Four failed to work, I exercise the nuclear option, which is to delete the entire repository from my machine.  Before I do this, however, I make sure to make a copy of my file with all my changes and paste it somewhere outside the code, as I did in Step Four.  Then I delete the entire repository and clone it again.  Then I continue as I did in Step Four, creating a new branch and switching to it, making my changes, and doing a brand new pull request.

Step Six: Ask For Help

If all else fails, ask someone for help.  There’s no shame in asking for help with a particularly thorny merge conflict!  If you followed Step One and didn’t panic, your helper will be able to see exactly what you’ve done so far and can help you arrive at a solution.

Git purists may argue that merge conflicts should be resolved the right way (using Step Two or Step Three), and they are probably right.  But doing Step Four or Step Five can keep you from wasting time trying to resolve the conflict, and they also will keep you from defenestrating your laptop!

Six Tips for Git Success

Last week, I wrote a Gentle Introduction to Git, which was designed to give testers a general overview of how version control software works, from cloning a repository to submitting a pull request.  Even when you understand how Git works, it can still be a bit mysterious, because there is so much happening that you don’t see.  The command line does not offer a visual interface to show you what branch you are on, or when the last time was that you pulled from the master branch.  So in this post I’ll be describing six tips that make using Git easier.

Tip One:  Run git status frequently

A common mistake that Git users make is to do a bunch of work and commit while on the wrong branch.  Because I am not a Git expert and I’m never sure how to recover from this mistake, I run git status very frequently.  I always run it as soon as I’ve opened the command line and navigated to my repository, so I won’t be surprised by what branch I’m on. 

Running git status is also a good way to find out what files have been changed in your branch.  Sometimes we make a change to a file for debugging purposes and we forget to change it back.  If you run git status, it will alert you that a file you didn’t want to change has been altered.

Tip Two: Pull from the master branch before you do anything

Before you start changing code in your branch, you’ll want to make sure that your branch has the very latest code in it.  Otherwise, you may be updating code that doesn’t exist any more, or you may be duplicating work that has already been done.  By making sure that you have the latest code, you will also avoid creating a merge conflict, where your branch and the master branch have differences that the version control system doesn’t know how to handle. 

Once you have pulled from the master branch, remember to switch to your own branch!  If you are running git status frequently, you’ll notice if you’ve forgotten to make the switch.

Tip Three: Add all of your changed files at once

If you have changed a number of files, you’ll find it tedious to type
git add <insert long file name here>  over and over again.  A quick way to add all of your changed files at once is to use git add -A.  Just be sure when using this that you want to add all of your files.  I don’t know of a command that will let you add all the files except one or two.  If you know a command for this, please comment on this post and share it with everyone!

Tip Four: Name your commits something helpful

When you commit your files, adding a commit message is optional, but most companies expect their employees to do it.  Make it easier on yourself and everyone else by naming your commits something that will make sense later.  “One line code change” is not a very helpful commit message.  “Adding test for new contact info endpoint” provides much more detail. 

Tip Five: View your git logs in a single line

It can be hard to remember what you’ve done in Git, because there’s no UI to show you.  This is where git log is helpful.  The log in its full version will show you the last several commits made, who made them, and the date and time they were made.  I find it’s easier to read the logs when they are condensed to a single line; to do this, type git log –pretty=oneline. To exit the log, type q

Tip Six: View the diff of your files before you do a pull request

If you are running git status frequently, you probably won’t commit and push any files that you didn’t mean to push.  But it’s still possible to accidentally commit code you didn’t mean to commit in a file that has the changes you want.  So before you do a pull request and ask someone to review your code, view the “diff” of your files, which simply means looking in GitHub and comparing the files in your branch with the files in the master branch to see which lines were changed.  Make sure that there are no code changes in the file that you didn’t want to commit, such as commented-out code or debugging statements.

If you find that you’ve accidentally pushed something that you didn’t mean to, simply change the file to what you want it to be and add, commit, and push it again.

Hopefully you will find these six tips helpful as you work with Git!  In next week’s post, I’ll talk about how to handle the dreaded merge conflict.