The Kotlin title claimed to be more associated with Android development, but why not experiment? With its help, we found a way to slightly simplify the automation of testing the API of one of our services, as well as to facilitate the work of testers who are not familiar with programming and the nuances of the Java language.
What are we doing? We are developing a service for sending brokerage questionnaires for calculating and receiving a decision on them. And despite the fact that this is a banking solution, the development is carried out by a small scrum team, in which 1-2 specialists are involved in testing, depending on the load and the situation on the project.
Under the cut, we will tell you about the results of our experiments, which we gladly transferred to production.
No face
Our service was developed to integrate with the interface of partners and does not have its own UI. Therefore, in this case, testing the API directly is the only option available. Although API tests have a number of advantages even with an interface:
- allow you to start testing already at the stage of developing services;
- simplify error localization;
- generally reduce testing time.
Our service includes a set of APIs for transferring the data of the client's questionnaire for calculation and for obtaining a solution for a car loan. The following checks can be distinguished as the main high-level testing tasks:
- format of fields and parameters;
- correctness of status codes and error messages;
- required fields and service logic.
Crucial moment
The team prepared the MVP of the service in a very short time, and the automated tests were originally written by the product developer. It was faster and more convenient to include autotests in the project, so initially they were closely related to its code and were based on the Java stack, REST-assured, and the TestNG test framework.
With the development of the project, the involvement of testers in the automation process grew. At the moment, the tasks of supporting and updating autotests lie entirely with the testing department. In this regard, it became necessary to adjust the approach to automation - to simplify tests as much as possible and make them independent of the main code. And, thus, to solve the arising difficulties:
- communication with the code makes the tests more vulnerable to any design changes, requires a lot of labor to maintain performance;
- complex relationships increase the time for finding information and investigating errors;
- It is difficult to involve new employees of the testing department in working with Java code, it requires training of testers and a significant time to enter the project.
Therefore, we decided to change the approach to testing and formulated the following requirements for new tools:
- full compatibility with Java, which allows you to rewrite checks in stages, while continuing to work with old tests;
- simple, intuitive code;
- free and stable language;
- selection of autotests in a separate project.
What does Kotlin have to do with it
The Kotlin language meets our needs as much as possible.
In addition to full compatibility with Java in Kotlin, we were interested in the following features:
- the conciseness of the language makes the code clear and easy to read;
- syntactic sugar;
- intuitive code and easy entry of specialists into the project;
- the ability to transfer autotests from Java to Kotlin gradually and painlessly, partially if necessary.
Of course, there are also disadvantages, especially in comparison with Java. First, a relatively small community: finding help and answers to Java questions is much easier than with Kotlin. Secondly, in general, Java is a more universal language with more capabilities and a large number of developments and libraries.
But, frankly, for testing purposes, we do not need such extensive capabilities that only Java can provide. Therefore, we preferred the simplicity and brevity of Kotlin.
From words to deeds
As a result of a partial transition to a new language, we received a noticeable gain in labor costs for specialists, a significant simplification of the code and a reduction in its volume. This is clearly seen in specific examples from tests of our service for processing brokerage profiles.
In Kotlin, you can initialize a request object with one line of code. For example, the ApplicationDTO class (for sending a questionnaire to the solution) and ErrorDTO (errors coming from the service) look like this:
Kotlin allows you to set field values during initialization. This saves a lot of time when writing tests: when creating an object, the fields are already filled with valid data, we only change the values that refer to the current check.
The JsonIgnoreProperties annotation allows you not to specify all the fields in a class if they are not needed for tests and do not need to be checked. If a field is received that is not specified in the description, the test will not fail with an error.
The question mark at the type of a variable indicates that at some point it can be null. But when using a variable, this will need to be taken into account and processed. Finally, unlike Java tests, NullPointerException can be avoided gracefully here.
Let us illustrate with a simple example of error checking for an incorrect login:
class ApplicationTests {
val DEFAULT_LOGIN = "consLogin"
val ERROR_CODE = "3"
val BASE_URI = "https://base_url.com"
@Test
fun incorrectLogin() {
val incorrectLogin = DEFAULT_LOGIN + "INCORRECT";
val res = given()
.spec(spec)
.body(ApplicationDTO)
.`when`()
.post(endpointApplication(incorrectLogin)
.then()
.statusCode(400)
.extract().`as`(ErrorDTO::class.java)
assertThat(res.error_message).containsIgnoringCase(" ")
assertThat(res.error_code).isEqualToIgnoringCase(ERROR_CODE)
}
companion object{
private var spec: RequestSpecification? = null
@JvmStatic
@BeforeClass
fun initSpec() {
spec = RequestSpecBuilder()
.setContentType(ContentType.JSON)
.setBaseUri(BASE_URI)
.addFilter(ResponseLoggingFilter())
.addFilter(RequestLoggingFilter())
.build()
}
}
}
We create a specification for queries in BeforeClass, which we reuse in all tests of this class. Jackson serializes and deserializes json objects, which simplifies things: you do not need to work with them as with strings, because of this, the number of errors is significantly reduced.
With Kotlin, we significantly reduce the amount of code, which means we make life easier for both “writers” and “readers”. The ApplicationDTO class already contains a constructor and some other methods (hashCode (), copy (), etc.) - we do not need to "overload" the code with them and novice autotest developers are not distracted by them. They do not need to be updated in case of any changes, which reduces the time for making edits to tests.
It is also very convenient that Kotlin supports named arguments - the code is quite easy to read, and there is no need to write setters for the json object classes yourself.
Try new things for free
I would like to note the openness and freeness of Kotlin and all the libraries we use, which greatly simplifies the work and expands the possibilities for experimenting and introducing new approaches. Agree, it's always easier to try new things for free, and in case of failure, return to old practices.
In the future, we plan to develop a new approach and scale it to other projects, translate the rest of the API and user interface tests to Kotlin.
PS: Despite the fact that Kotlin is primarily associated with Android development, the scope of its use, as you understand, is not limited to this. Reducing the time spent on writing code, its simplicity and conciseness will help reduce labor costs in solving many problems. If we have new cases with Kotlin, we will definitely tell you about them.here .