The General Anatomy of a Test Script
Sustainable and optimised return on investment can be achieved from test automation when you have a well-structured foundation. While technologies, tools, and frameworks used for automation help us in developing and maintaining robust automation projects, the true power of automation remains in the tiniest component of the automation ecosystem, the test script.
The test script is the primary piece that contains instructions on how to perform the test which interprets the test case via lines of code and when the test script is executed; the test script mirrors the manual tester. Hence, it is extremely important to verify that the test script is performing the same steps and producing the same results without any discrepancy against manual testing.
In reality, test scripts may not work as expected and could show discrepancies due to incorrect assumptions, issues due to test dependencies, test data mismatches, and so on. Here, in this article, let’s focus on how to avoid these discrepancies by following a general script anatomy to structure the test in such a way as to avoid unexpected behaviours, which will help to improve the efficiency and reliability of the overall test suite reducing the execution time.
The best way to explain this would be to take a real-world test scenario.
Assume you have a feature in your application that registers unique users, searches users, and deletes users.
Now, time to derive the test cases to cover the application functionality.
- Verify registered user.
- Verify search user.
- Verify delete user.
Let’s convert the Verify register user test case to a test script.
If you look closely, @Test is an annotation used to convey that this method is a test. Generally, almost all the test frameworks such as Junit, and TestNG have these in-built annotations which help us in formulating the anatomy of the test script. However, the given names for annotation may differ from library to library.
Now, back to the test if you run this test once, you will have a successful execution. Yet, if you repeat, the test will fail as the user has been already registered previously. How do you resolve this? Let some neurons fire!
The reason for the failure of the test is the user is not unique or in general terms, the state of the application is not known by the test script. Hence, every time the tests are executed, we need to make sure to execute in a known state.
The easiest way to reset the state is by cleaning up data at the end of the test. It’s quite straightforward, you can add the user.delete(); line at the end of the test script.
Well, what happens if the user test fails before deleting the user? Then the user.delete(); will not be executed, which means adding this line at the end of the script offers no guarantee of resetting to the original state of the application.
That’s where @PostCondition annotation comes to rescue us, where the conditions are executed after the @Test. In most of the libraries, there are mainly two annotations used @AfterEach and @AfterAll. @AfterEach is executed after each test and @AfterAll is executed after all the tests in the class. These annotated methods are executed irrespective of the test being passed or failed, offering the trust of resetting the application state.
Let’s update our test now.
Now, let’s take the remaining test cases.
To search or delete a user, a user should be available in the database. Hence before the test, a user should be created and bring the test to a known state. Similar to @PostConditions above, here we need to use @Precondition, as the name suggested, the condition is executed before the test. Libraries define them as @BeforeEach and @BeforeAll. @BeforeEach is executed before each test and @BeforeAll is executed before all the tests in the class.
One of the advantages of this approach is you get the capability to add related tests in the same class improving the visibility and giving a broader idea of the execution flow of the test script. The methods with the @Precondition annotation will be executed first, then the methods with @Test annotations will be executed and finally, @PostCondition methods will be executed.
What should be kept in mind is, this is a general overview of the structure of the test script. When you are using a library like Junit or TestNG, it has its annotations defined that are executed at different levels of the execution ranging from suite level, class level to method level. When we are articulating our test scripts, we can leverage the power of these annotations to enhance the test execution of each test script and even beyond the level of the test suite, where you can handle data common across the suite.
Being aware of the general anatomy of the test script will help to improve the agility of the test script, hence improving the smooth execution of the overall test bed.
Due to the anatomy, common functionalities can be tested within the same class, reducing the overall maintenance, and providing a broader view of the test coverage for the feature.
Depending on the test framework or library used, you will come across different annotations, which you can use to improve the anatomy of the test script further enhancing smoother execution.