OVERVIEW
Structural testing is the technique that tests the system's structure or component. It determines what is happening inside the system or application and is often referred to as glass box or white box testing. In this type of testing, the testers are required to know the internal implementations of the code, like how the software is implemented and how it works.
For example, a structural technique wants to understand how loops in the software work. Different test cases can be derived to exercise the loop once, twice, or more. This may be done irrespective of the functionality of the software.
Structural testing applies at all testing levels. Developers use it in component testing and component integration testing when there is robust tool support for code coverage. It's also used in the system and acceptance testing. However, the structures are different.
Structural testing is a software testing approach that tests the code structure and intended system flows. For example, verifying the actual code to test whether the conditional statements are correctly implemented and every statement in the code is correctly executed. It is also known as structure-based, glass-box, clear-box, and white box testing.
To perform this testing, one needs to understand the code thoroughly. That is why this testing is usually performed by developers who write the code requirements, as they are well aware of the overall system functionality and user flows.
Let's consider a scenario where you wish to test a specific error message in an application. You need to test the trigger condition for it, but there can be many triggers.
It is possible to overlook one while testing scenarios mentioned in the software requirement specifications (SRS). This is where a structural testing technique will handle the trigger, aiming to cover all the nodes and paths in the code structure.
By running structural tests, the test cases written by the system requirements can be analyzed first, and more test cases can be added to expand the test coverage.
In this section, let’s look at some of the characteristics of structural testing.
Following are the advantages and disadvantages of using the structural testing approach.
Pros
Cons
Structural testing is divided into the following four main types.
Let us understand each of these testing types in more detail.
Mutation testing is where errors are purposely introduced into an application under test to verify whether the existing test cases can detect the error. In this type of testing, the mutant of the program is created by making modifications to the original program. Minor changes are made to the source code to see if the defined test cases can detect code errors.
It is a type of white box testing, which is also called a fault-based testing strategy, where you can create a fault in the program. The expectation is that none of the test cases should pass. If the test case passes, there is an error in the code, and the mutant (the modified version of our code) lives. If the test case fails, there is no error in the code, and the mutant is killed. The end goal is to kill all mutants.
In addition, mutation testing also helps to test the quality of the defined test cases or the test suites with the scope to write effective test cases. The more mutants are killed, the better the quality of the test cases.
Data flow testing is a software testing technique based on the data values and their usage in the program. This software testing verifies that data values are correctly used and that they generate the correct results. This testing also helps to trace the dependencies between data values on a particular execution path.
It uses the control flow graph to detect invalid things that can interrupt data flow. Anomalies in the data flow are detected at the time of association between values and variables for the following reasons.
Data flow anomalies: Data flow anomalies are the errors encountered in a software application. These are classified as Type 1, 2, and 3, respectively.
Let us understand the different data flow anomalies:
Type 1: A variable is defined, and the same value is assigned twice.
list_1 = [1,2,3,4]
list_1 = [5,6,7,8]
for var in list_1:
print(var)
list_1 variable is defined, and two different values are assigned to it. The first value is ignored.
Note: Type 1 anomalies do not result in application failure.
Type 2: The variable’s value is referenced before the variable is defined.
for var in list_1: print(var)The loop in the above example has no values to iterate over.
Note: Type 2 anomalies cause the application to fail.
Type 3: A data value generated but never used.
list_1 = [1,2,3,4]
list_2 = [5,6,7,8]
for var in list_1:
print(var)
list_2 variable is not referenced.
Note: Type 3 anomalies may not cause the program to fail.
The following are the types of data flow testing:
The data flow testing process defines the dependencies between data values. You are required to define the different paths that can be followed in a program. The process involves the following steps.
You need to draw a control flow graph, which is a graphical representation of the paths you will follow in your program.
cost = 20
a = int(input("How many visitor seats did you reserve? ")) b = int(input("How many member seats did you reserve? ")) if a>b: bill = cost -1 else: bill = cost print(bill)In the above example, a member should get a discount if they invite a visitor.
A variable is defined or used in a program. In the control flow graph, variables are defined at each node. Each node is named as per the variable type it contains.
Considering the variable cost in the control flow graph, these are the defining and usage nodes described in the table below.
Node | Type | Code |
---|---|---|
1 | Defining node | cost = 20 |
5 | Usage node | bill = cost -1 |
7 | Usage node | bill = cost |
The following are the two types of definition-usage paths:
An example of a dc path about the bill variable is shown below.
Node | Type | Code |
---|---|---|
5 | Defining node | bill = cost -1 |
7 | Defining node | bill = cost |
8 | Usage node | print(bill) |
This is all about adding the desired inputs that help you to create a comprehensive test suite.
Note: You need to have a different test suite for each variable. The test suite will help you identify data flow anomalies.
Control flow testing is the basic model of structural testing. This testing technique is part of white box testing. It contains the following features:
Slice-based testing can be defined as a software testing technique based on slices – executable parts of the application or group of statements that impact certain values at particular points of interest in the program.
For example, parts of a program where variables are defined or an output for a group of statements. It contains the following features.
Example: Following is the code to print even and odd numbers using Python
num_list = range(1,12)
even_nums = []
odd_nums = []
for var in num_list:
if var%2==0:
even_nums.append(var)
print(f"Even numbers: {even_nums}")
elif var%3==0:
odd_nums.append(var)
print(f"Odd numbers: {odd_nums}")
Following are the two ways to look at a slice:
In this example, if you look at the odd numbers output, you can trace the code’s part that gives you this output.
As per the slicing criteria by Mark Weiser, in the above example, to get the slice, you start from the output from line 10, which becomes the n. The variable is defined as var.
So the slicing criteria are defined as
S(v,n) = S(var,10).The output will be: 10,9,8,4,3,1
So, the slice in this code is:
num_list = range(1,12)
odd_nums = []
for var in num_list:
elif var%3==0:
odd_nums.append(var)
print(f"Odd numbers: {odd_nums}")
The following are the two types of slice-based testing:
Here it is verified that the code is correct in terms of syntax and requirements, and then you do not execute it. Static slice-based testing is also known as verification testing.
The code is executed and uses test data to ensure it works as expected. The range can be increased to range(1,50) to see whether it generates only odd numbers. Dynamic slice-based testing is also known as validation testing.
It is important to note that dynamic slice-based testing is ‘smaller’ when compared to its static counterpart.
The following are the factors to determine the slicing criteria.
To test different code aspects, you need to understand the control flows.
The visual representation of different code sections helps define the paths that can be followed during execution is called a control flow graph. It has several components like nodes, edges, paths, junctions, and decision points. The graph can be created manually or automatically, where software is used to export the graph from the source code.
Let us understand these components in more detail.
The process block comprises nodes with one entry and exit path.
Example:
The process block is not an important part of the control flow graph; you can test it only once.
The decision can be based on conditional statements such as if-else statements with two possible outcomes and case statements with multiple output values.
In the above diagram, a decision point (Age=18) is followed by either ‘yes’ or ‘no’ options based on the condition.
A few things to consider while constructing a control flow graph:
After defining the control flow graph, proceed to the next phase in the control flow testing procedure, describing the extent to which the code needs to be tested.
Trying to cover all paths in the tests is practically impossible. You need to find a middle ground to determine how much testing can be done.
Let's say that you aim at testing 50% of the code, which means that you will define all executable code statements and aim at testing at least half of them. However, the question is, ‘do you need to define all possible executable paths?’ This, again, might be practically impossible.
A better approach might be aiming to test 50% of the paths you can identify at each code section. There are different levels of coverage, statement, branch, and path coverage, explained later in this section.
The next step is to create the test cases you will execute. The test cases in structural-based testing are determined based on the following factors:
The above factors give you an idea of the types of test cases you need to create. You can also use a structural test generation tool to create test cases.
You can run the test cases and enter input or data to check how the code executes it and verify if you get the intended results. For example, enter an array in a function call to check the results you get after looping through it or whether the decision points are making the correct decisions.
Here, all you need to do is check whether you get accurate results after execution. For example, when you enter an array where all the values are above 18, you should have all the decision points resulting in eligibility.
To carry out control flow testing, the following assumptions are applicable.
Following are the different levels of coverage in control flow testing.
This technique aims at implementing all programming statements with minimal tests. In structural-based testing, executable code statements have a critical role in deciding the methods of designing the tests. The aim is to achieve 100% coverage, so every executable statement should be tested at least once. The higher the coverage, the lesser the likelihood of missing the bugs and errors.
This technique runs a series of tests to ensure that all the branches are tested at least once. This technique involves testing the points in the control flow graph branches (where decisions are made). The outcomes are boolean. Even if a switch statement has multiple outcomes, each case block compares a pair of values.
The aim is to achieve 100% branch coverage, where you must test each outcome at least once at each decision level. Since you are dealing with boolean outcomes, you should aim to run at least two tests per code section.
This technique aims to test all possible paths, which means that each statement and branch is covered. This level of coverage is more exhaustive when compared to decision and statement coverage.
The purpose is to discover all possible paths and test them at least once. This can be extremely time-consuming. However, it can help find bugs or errors in the code or even aspects that need to be defined, for example, user input.
Here are the basic formulas you can use to measure the effectiveness of your structural tests.
Following are some of the tools used in structural-based testing.
For automation testing needs, always make sure you test your software applications on real world environments for accurate testing. Continuous quality cloud platform like LambdaTest lets you perform manual and automated testing of websites and applications on an online device farm of 3000+ real browsers, devices, and platform combinations.
Also, subscribe to LambdaTest YouTube Channel and stay updated with the detailed tutorials around Selenium testing, Cypress testing, and more.
Following are some of the best practices that need to be followed while performing structural-based testing:
In this tutorial, we explored structural testing, its advantages, disadvantages, best practices, tools, and more. It is always recommended to combine different testing types and approaches. For example, structural testing and requirement testing complement each other, as there can be features that might not have developed when structural testing was carried out.
Functional testing is software testing where the software is validated based on the requirements specified in the SRS (Software Requirements Specifications). Structural testing is a type of testing where the software is verified based on the internal code implementation.
There are four types of structural testing: mutation testing, data flow testing, control flow testing, and slice-based testing.
Structural testing involves verifying the source code for aspects such as the correct implementation of conditional statements and the execution of each statement in the code
Try LambdaTest Now !!
Get 100 minutes of automation test minutes FREE!!
Did you find this page helpful?