Exercise: jq for testers
A set of testing-relevant exercises to help testers use, and see the use of, jq – a tool to filter and manipulate json.
Notes on jq
(maybe on a page)
JSON is a text-based record format as a hierarchy – it's not rows and columns. A JSON record is typically an object {} or a list [], and everything inside is a key: value – and those values can themselves be objects {} (named parts in any order) or lists [] (ordered rows), giving the hierarchy. The basic unit is key : value , one per line.
jq lets you filter based on name (.foo shows you a top level value thta has the key foo), lets you drill down in to a hierarchy (.foo.bar shows you the value for the property bar under foo), and lets you iterate or access specifics in a list with [n] (.[].foo will, if you've given jq a list, give you the value of the property foo on every item on the list, while .[0].foo will give you the first.
jq stacks commands – you'll send information from left to right with |, and you'll often send to one of jq's collection commands e.g. .[].foo | unique | sort, or to a selection command .foo[] | select(.bar == "moon")
Ultimately, it's a powerful filtering language for extracting, exploring and creating data. I'll not be rewriting the manual, and the cookbook shows the complex tasks the tool can manage.
Reading a query:
- if you see a
jqwith information in all the[], it’s like dot notation – it’s effectively an address. - If a
[]is empty, it’s iterating, and if you see a|it’s filtering or aggregating, sometimes to another filter, sometimes to a keyword likesortorselect(. - If you see a
..it’s traversing the whole tree, and a?means it’s finding stuff, or checking stuff and handling errors. - If you see
.then that’s picking out everything, and.foopicks out the thing calledfooat whatever level thejqreader is at.,separates and aggregates queries. - If there’s a
[and]around something that produces information, then that information is being wrapped up as an array. If it’s wrapped in{and}you’re getting an object.
For the exercises, we'll use online jq tools. As with regex, I reckon that we don't need to be expert with jq, if we have tools to help build and explore queries. All the following tools take in json and a query, and show you the results in real time.
- JQ / Playground is a page that lets you ask for
jqqueries in plain language, and to see their effects in real time. - JQ Play Offline is a page that shows you the results of queries more readably, and lets you work with command-line options
- The JQ Playground lets you get your json from an http endpoint – and is from the makers of
jq. It runs slightly less swiftly.
jq is primarily a command line tool: These pages are handy for building or testing a query, and for learning through playing, but you'll want the command line version to do work. The tools abstract away the command-line syntax, which goes jq -c '.fruit.color,.fruit.price' fruit.json – wrapping the command in '', acting on a file, and giving an option.
However, jq is not always available, because it's not one of the core xNIX toolset. Its official page is jqlang.org, and
Exercise 1 – exploring data with jq
10 minutes
We'll use the data below, which is a generated JSON output from an imaginary system that logs events to an events object, and logs traces to a traces object.
- use
keysto show the top-level keys - use
traces[]to show everything in the traces array - use
.traces[].traceIdto show the traceIDs.- Aside: Compare with
.traces[] .traceIdand.traces[] | traceId - Aside: Compare with
.traces | map(.traceId) | uniqueand
[ .traces[] | .traceId ] | unique
- Aside: Compare with
- use
events[]to show everything in the events array - use
events[0]to show the first event in the events array - use
.events | map(.traceId) | uniqueto show all the unique traceIDs in the events array. - use
.traces | map({(.traceId): [.spans[].name]}) | addto show all event names for each trace. - use
.events[] | select(.type == "mobile_request") | .data.deviceto see all devices for mobile requests.
This will, I hope, let you be familiar with accessing by name, with iterating and picking – and familiar with the data itself.
Exercise 2 – building queries
10 mins
Imagine something you'd like to know as a tester from this data. Use an LLM of your choice, or JQ / Playground above, to find a query. Try it out. Parse the query you got.
Share with the rest of the group – they may try it out, parse it themselves, check that the data is right, or see if they can get the same data in a different way.
Exercise 3 – Generating data with jq
Wrap a jq command in [] or {} to generate an array or object.
Sample Data
{
"traces": [
{
"traceId": "123456789",
"duration": 2000,
"spans": [
{
"id": "span1",
"name": "web_request",
"startTime": 1634491200000,
"endTime": 1634491201000,
"tags": {
"http.method": "GET",
"http.url": "/api/users"
}
},
{
"id": "span2",
"name": "database_query",
"startTime": 1634491201000,
"endTime": 1634491201500,
"tags": {
"db.query": "SELECT * FROM users WHERE id = 1"
}
},
{
"id": "span3",
"name": "cache_lookup",
"startTime": 1634491201500,
"endTime": 1634491201800,
"tags": {
"cache.key": "user_profile_1"
}
}
]
},
{
"traceId": "987654321",
"duration": 3000,
"spans": [
{
"id": "span1",
"name": "web_request",
"startTime": 1634491202000,
"endTime": 1634491203000,
"tags": {
"http.method": "POST",
"http.url": "/api/payments"
}
},
{
"id": "span2",
"name": "payment_processing",
"startTime": 1634491203000,
"endTime": 1634491204000,
"tags": {
"payment.amount": 100.00,
"payment.method": "credit_card"
}
},
{
"id": "span3",
"name": "fraud_check",
"startTime": 1634491204000,
"endTime": 1634491204500,
"tags": {
"fraud.score": 10
}
}
]
},
{
"traceId": "456789123",
"duration": 1500,
"spans": [
{
"id": "span1",
"name": "mobile_request",
"startTime": 1634491205000,
"endTime": 1634491205500,
"tags": {
"http.method": "GET",
"http.url": "/api/products"
}
},
{
"id": "span2",
"name": "product_catalog_lookup",
"startTime": 1634491205500,
"endTime": 1634491206000,
"tags": {
"product.category": "electronics"
}
}
]
},
{
"traceId": "321654987",
"duration": 2500,
"spans": [
{
"id": "span1",
"name": "web_request",
"startTime": 1634491207000,
"endTime": 1634491208000,
"tags": {
"http.method": "POST",
"http.url": "/api/orders"
}
},
{
"id": "span2",
"name": "inventory_check",
"startTime": 1634491208000,
"endTime": 1634491208500,
"tags": {
"product.id": "prod123",
"quantity": 5
}
},
{
"id": "span3",
"name": "order_processing",
"startTime": 1634491208500,
"endTime": 1634491209500,
"tags": {
"order.id": "order456",
"order.total": 50.00
}
}
]
},
{
"traceId": "159753",
"duration": 1000,
"spans": [
{
"id": "span1",
"name": "mobile_request",
"startTime": 1634491210000,
"endTime": 1634491210500,
"tags": {
"http.method": "GET",
"http.url": "/api/notifications"
}
},
{
"id": "span2",
"name": "notification_fetch",
"startTime": 1634491210500,
"endTime": 1634491211000,
"tags": {
"notification.type": "promotion",
"notification.message": "25% off sale!"
}
}
]
},
{
"traceId": "753159",
"duration": 1800,
"spans": [
{
"id": "span1",
"name": "web_request",
"startTime": 1634491212000,
"endTime": 1634491212500,
"tags": {
"http.method": "GET",
"http.url": "/api/profile"
}
},
{
"id": "span2",
"name": "user_profile_fetch",
"startTime": 1634491212500,
"endTime": 1634491213000,
"tags": {
"user.id": "user123",
"user.email": "user@example.com"
}
},
{
"id": "span3",
"name": "settings_fetch",
"startTime": 1634491213000,
"endTime": 1634491213800,
"tags": {
"settings.theme": "dark",
"settings.language": "en"
}
}
]
},
{
"traceId": "951357",
"duration": 2200,
"spans": [
{
"id": "span1",
"name": "mobile_request",
"startTime": 1634491215000,
"endTime": 1634491215500,
"tags": {
"http.method": "POST",
"http.url": "/api/feedback"
}
},
{
"id": "span2",
"name": "feedback_submit",
"startTime": 1634491215500,
"endTime": 1634491216000,
"tags": {
"feedback.rating": 4,
"feedback.comment": "Great app, but could use more features."
}
},
{
"id": "span3",
"name": "support_ticket_create",
"startTime": 1634491216000,
"endTime": 1634491217200,
"tags": {
"ticket.id": "ticket123",
"ticket.priority": "medium"
}
}
]
},
{
"traceId": "357951",
"duration": 1700,
"spans": [
{
"id": "span1",
"name": "web_request",
"startTime": 1634491218000,
"endTime": 1634491218500,
"tags": {
"http.method": "GET",
"http.url": "/api/analytics"
}
},
{
"id": "span2",
"name": "data_aggregation",
"startTime": 1634491218500,
"endTime": 1634491219000,
"tags": {
"data.source": "user_activity",
"data.timeframe": "last_7_days"
}
},
{
"id": "span3",
"name": "report_generation",
"startTime": 1634491219000,
"endTime": 1634491219700,
"tags": {
"report.id": "weekly_analytics",
"report.format": "pdf"
}
}
]
},
{
"traceId": "753951",
"duration": 2800,
"spans": [
{
"id": "span1",
"name": "mobile_request",
"startTime": 1634491220000,
"endTime": 1634491220500,
"tags": {
"http.method": "PUT",
"http.url": "/api/settings"
}
},
{
"id": "span2",
"name": "user_settings_update",
"startTime": 1634491220500,
"endTime": 1634491221000,
"tags": {
"setting.language": "es",
"setting.notification_preferences": "email"
}
},
{
"id": "span3",
"name": "email_notification",
"startTime": 1634491221000,
"endTime": 1634491222800,
"tags": {
"email.recipient": "user@example.com",
"email.subject": "Your settings have been updated"
}
}
]
}
],
"events": [
{
"timestamp": 1634491200000,
"type": "user_login",
"data": {
"userId": "user123",
"ipAddress": "192.168.1.100"
},
"traceId": "123456789"
},
{
"timestamp": 1634491201000,
"type": "database_query",
"data": {
"query": "SELECT * FROM users WHERE id = 1"
},
"traceId": "123456789"
},
{
"timestamp": 1634491201800,
"type": "cache_hit",
"data": {
"cacheKey": "user_profile_1"
},
"traceId": "123456789"
},
{
"timestamp": 1634491202000,
"type": "payment_initiated",
"data": {
"paymentId": "payment123",
"amount": 100.00
},
"traceId": "987654321"
},
{
"timestamp": 1634491204000,
"type": "fraud_check_complete",
"data": {
"fraudScore": 10
},
"traceId": "987654321"
},
{
"timestamp": 1634491205000,
"type": "mobile_request",
"data": {
"userId": "user456",
"device": "iPhone"
},
"traceId": "456789123"
},
{
"timestamp": 1634491205500,
"type": "product_catalog_lookup",
"data": {
"productCategory": "electronics"
},
"traceId": "456789123"
},
{
"timestamp": 1634491207000,
"type": "order_placed",
"data": {
"orderId": "order456",
"totalAmount": 50.00
},
"traceId": "321654987"
},
{
"timestamp": 1634491208500,
"type": "inventory_check",
"data": {
"productId": "prod123",
"quantity": 5
},
"traceId": "321654987"
},
{
"timestamp": 1634491210000,
"type": "mobile_request",
"data": {
"userId": "user789",
"device": "Android"
},
"traceId": "159753"
},
{
"timestamp": 1634491210500,
"type": "notification_fetched",
"data": {
"notificationType": "promotion",
"notificationMessage": "25% off sale!"
},
"traceId": "159753"
},
{
"timestamp": 1634491212000,
"type": "user_profile_viewed",
"data": {
"userId": "user123",
"userEmail": "user@example.com"
},
"traceId": "753159"
},
{
"timestamp": 1634491213000,
"type": "user_settings_fetched",
"data": {
"theme": "dark",
"language": "en"
},
"traceId": "753159"
},
{
"timestamp": 1634491215000,
"type": "mobile_request",
"data": {
"userId": "user456",
"device": "Android"
},
"traceId": "951357"
},
{
"timestamp": 1634491215500,
"type": "feedback_submitted",
"data": {
"rating": 4,
"comment": "Great app, but could use more features."
},
"traceId": "951357"
},
{
"timestamp": 1634491216000,
"type": "support_ticket_created",
"data": {
"ticketId": "ticket123",
"priority": "medium"
},
"traceId": "951357"
},
{
"timestamp": 1634491218000,
"type": "web_request",
"data": {
"userId": "user789",
"ipAddress": "192.168.1.101"
},
"traceId": "357951"
},
{
"timestamp": 1634491218500,
"type": "data_aggregation",
"data": {
"dataSource": "user_activity",
"timeframe": "last_7_days"
},
"traceId": "357951"
},
{
"timestamp": 1634491219000,
"type": "report_generated",
"data": {
"reportId": "weekly_analytics",
"reportFormat": "pdf"
},
"traceId": "357951"
},
{
"timestamp": 1634491220000,
"type": "mobile_request",
"data": {
"userId": "user123",
"device": "Android"
},
"traceId": "753951"
},
{
"timestamp": 1634491220500,
"type": "user_settings_updated",
"data": {
"language": "es",
"notificationPreferences": "email"
},
"traceId": "753951"
},
{
"timestamp": 1634491221000,
"type": "email_notification_sent",
"data": {
"recipient": "user@example.com",
"subject": "Your settings have been updated"
},
"traceId": "753951"
}
]
}