As a Quality Assurance Engineer, you'll be responsible for ensuring that software meets its requirements. This involves understanding both functional and non-functional requirements, and creating test cases to verify them.
At Amazon, and in general software development, a Quality Assurance Engineer (QAE) works with developers to ensure that products are high quality. "Quality" describes how well something does its job. Therefore, to determine if our software is high quality, we have to describe its job.
Usually, customers give us requirements, telling us what they want our software to do. We refine these into simple stories about how the customer will use our software. Each story is a use case.
Use cases help us understand and improve our requirements. Once we have finalized our use cases, we use them to determine whether our software does its job: whether it is high quality.
Functional requirements describe what the software should do. They specify the behavior and features of the system. Examples include:
Non-functional requirements describe how the software should perform. They specify qualities and constraints of the system. Examples include:
SDEs and QAEs work together to write and perform tests that prove our software does what the customer expects. Although their areas of responsibility are not cleanly separated, SDEs generally spend most of their effort on functional requirements and tests, and QAEs handle most non-functional requirements.
A use case is a description of how a user interacts with a system to achieve a specific goal. It includes:
Use case names often follow an "actor verb noun" pattern, like "Player Places Bid" or "Customer Adds Item to Cart". If the actor is very general or is known ahead of time, it can be left out.
List the assumptions we make about the use case. For example, "Starting warehouse ID is not null" would be a good precondition for a route planning use case.
List the promises you can make about what won't change when the use case is performed. For example, "Input will not be modified".
List the promises you can make about what will change by the time the use case is done. For example, "Result will be the order in which the drone should deliver items such that it travels the shortest distance."
List the steps that the actor and your software will take. These are most useful if they follow one of these templates:
Notice that none of the steps includes the word "if". Instead of using "if" in a step, create two use cases with preconditions for each of the "if" conditions.
Preconditions:
Steps:
Postconditions:
Alternate Cases:
Preconditions:
Invariants:
Postconditions:
Steps:
Preconditions:
Invariants:
Postconditions:
Steps:
When we write code, we turn "the software" steps in all the use cases into Java. Here's an example implementation for the shortest route use case:
public class ShortestRouteFromWarehouse { /** * Finds the shortest route from a warehouse to deliver all orders. * * @param warehouseId The ID of the starting warehouse * @param orders The orders to be delivered * @return A sorted list of orders representing the shortest delivery route * @throws IllegalArgumentException if warehouseId is null */ public List<Order> findShortestRoute(String warehouseId, List<Order> orders) { // Check precondition if (warehouseId == null) { throw new IllegalArgumentException("Warehouse ID cannot be null"); } // Make a copy to ensure we don't modify the input (invariant) List<Order> routeOrders = new ArrayList<>(orders); // Calculate the shortest route (simplified for example) Collections.sort(routeOrders, (o1, o2) -> { // Sort based on distance from warehouse and between orders // Implementation details omitted for brevity return 0; }); // Return the sorted list (postcondition) return routeOrders; } }
Complex software can have many requirements, making it easy to forget or misunderstand important functionality. You can find missed functionality by examining each step of your use cases and asking, "what could go wrong here?" Make a new use case with preconditions for every answer.
Once you're done, determine which requirement each use case helps satisfy. Some requirements will have multiple use cases. Requirements without use cases indicate that you either don't understand how the customer will use your software or need to write another use case.
On the other hand, use cases without requirements indicate that you either have found a hidden requirement or don't need to write that code.