Main API
So far, we learned how to write and run a simple Touca test using the Touca SDK for Java. But our previous isPrime example was too minimal to show how Touca can help us describe the behavior and performance of real-world software workflows. Let us use a Profile Lookup software as another example that takes the username of a student and returns basic information about them, such as their name, date of birth, and GPA.
1
public static Student findStudent(final String username);
Copied!
where Student has the following members:
1
import java.time.LocalDate;
2
3
public class Student {
4
public String username;
5
public String fullname;
6
public LocalDate dob;
7
public double gpa;
8
}
Copied!
Here's a Touca test we can write for our code under test:
1
import io.touca.Touca;
2
3
public final class StudentsTest {
4
5
@Touca.Workflow
6
public void findStudent(final String username) {
7
Touca.startTimer("find_student");
8
Student student = Students.findStudent(username);
9
Touca.stopTimer("find_student");
10
Touca.assume("username", student.username);
11
Touca.check("fullname", student.fullname);
12
Touca.check("birth_date", student.dob);
13
Touca.check("gpa", student.gpa);
14
Touca.addMetric("external_source", 1500);
15
}
16
17
public static void main(String[] args) {
18
Touca.run(StudentsTest.class, args);
19
}
20
21
}
Copied!
While we are using the same test framework as before, we are tracking more data about the behavior and performance of our software using various data capturing functions. In this tutorial, we will learn how these functions work and how they can help us detect regressions in future versions of our software.

Describing Behavior

For any given username, we can call our findStudent function and capture the properties of its output that are expected to remain the same in future versions of our software.
We can start small and capture the entire returned object as a Touca result:
1
Touca.check("student", student);
Copied!
Adding the output object as a single entity works. But what if we decided to add a field to the return value of findStudent that reported whether the profile was fetched from the cache?
Since this information may change every time we run our tests, we can choose to capture different fields as separate entities.
1
Touca.assume("username", student.username);
2
Touca.check("fullname", student.fullname);
3
Touca.check("birth_date", student.dob);
4
Touca.check("gpa", student.gpa);
Copied!
This approach allows Touca to report differences in a more helpful format, providing analytics for different fields. If we changed our findStudent implementation to always capitalize student names, we could better visualize the differences to make sure that only the value associated with key fullname changes across our test cases.
Note that we used Touca function assume to track the username. Touca does not visualize the values captured as assertion unless they are different.
We can capture the value of any number of variables, including the ones that are not exposed by the interface of our code under test. In our example, let us imagine that our software calculates GPA of students based on their courses.
If we are just relying on the output of our function, it may be difficult to trace a reported difference in GPA to its root cause. Assuming that the courses enrolled by a student are not expected to change, we can track them without redesigning our API:
1
private static double calculateGPA(final Course[] courses) {
2
Touca.check("courses", courses);
3
double sum = Arrays.asList(courses).stream().mapToDouble(item -> item.grade).sum();
4
return courses.length == 0 ? sum / courses.length : 0.0;
5
}
Copied!
Touca data capturing functions remain no-op in production environments. They are only activated when running in the context of a Touca.run function call.

Describing Performance

Just as we can capture values of variables to describe the behavior of different parts of our software, we can capture the runtime of different functions to describe their performance.
Touca can notify us when future changes to our implementation result in significantly changes in the measured runtime values.
1
Touca.startTimer("find_student");
2
Student student = find_student(username);
3
Touca.stopTimer("find_student");
Copied!
The two functions startTimer and stopTimer provide fine-grained control for runtime measurement. If they feel too verbose, we can opt to use scopedTimer as an alternatives:
1
Touca.scopedTimer("find_student", () -> {
2
student = Students.findStudent(username);
3
});
Copied!
Alternatively, we could use io.touca.ScopedTimer in a try-with-resources statement:
1
try (ScopedTimer timer = ScopedTimer("find_student")) {
2
Student student = Students.findStudent(username);
3
}
Copied!
It is also possible to add measurements obtained by other performance benchmarking tools.
1
Touca.addMetric("external_source", 1500);
Copied!
In addition to these data capturing functions, Touca test framework automatically tracks the wall-clock runtime of every test case and reports it to the Touca server.
Like other data capturing functions, we can use Touca performance logging functions in production code, to track runtime of internal functions for different test cases. The functions introduced above remain no-op in production environments.
Last modified 20h ago
Copy link
Edit on GitHub