|
| 1 | +# Performance Testing |
| 2 | + |
| 3 | +## Introduction |
| 4 | +Performance tests measure how your application behaves under load, focusing on execution time, resource consumption, and database efficiency. |
| 5 | +Unlike *unit tests*, which verify isolated logic, or *integration* or *API tests*, which validate component interactions and full request lifecycles, performance tests quantify *how fast* operations complete and *how many* database queries they trigger. |
| 6 | + |
| 7 | +## Running Performance Tests |
| 8 | +Performance tests are excluded from the default test run to save CI/CD time and local resources. |
| 9 | +To execute them, use the Maven `verify` lifecycle phase and override the `it.groups` property: |
| 10 | + |
| 11 | +```shell |
| 12 | +mvn verify -Dit.groups=performance |
| 13 | +``` |
| 14 | + |
| 15 | +```{note} |
| 16 | +The `it.groups` property accepts a comma-separated list. |
| 17 | +You can combine groups (e.g., `-Dit.groups=integration,performance`) as necessary. |
| 18 | +However, it is highly recommended to run them in isolation due to their computational intensity and sensitivity to system load. |
| 19 | +``` |
| 20 | + |
| 21 | +## Testing database-bound code |
| 22 | +Performance tests for code relying on retrieving entities from a database are essential for catching regressions in ORM efficiency. |
| 23 | +They can identify N+1 query problems or ensure that heavy data processing pipelines (e.g., exporting large datasets) remain responsive as the codebase evolves. |
| 24 | + |
| 25 | +### Prerequisites |
| 26 | +Any tests around database-bound code rely on [Testcontainers](https://www.testcontainers.org/) to spin up ephemeral database instances. |
| 27 | +Avoiding in-memory databases for such tests allow for more realistic testing as seen in actual deployments. |
| 28 | +Consequently, you must have **Docker** installed and running, allowing Testcontainer to start a PostgreSQL server. |
| 29 | + |
| 30 | +- If you use a local Docker daemon, ensure it has sufficient memory allocated (typically 1GB+ is recommended for running Postgres containers alongside your tests). |
| 31 | +- If your Docker daemon runs remotely, ensure the `DOCKER_HOST` environment variable is correctly configured in your shell so Testcontainers can locate it. |
| 32 | + |
| 33 | +The automated testing setup will look up a system property `postgresql.server.version` to determine which container image tag to use. |
| 34 | +The property is injected from `pom.xml` by Maven Failsafe and use a reasonable fallback value if missing. |
| 35 | +To test with a different version of PostgreSQL, you may set the Maven property `postgresql.server.version` for a run. |
| 36 | + |
| 37 | +### Example |
| 38 | +Performance test classes must follow specific conventions to be discovered and executed correctly: |
| 39 | + |
| 40 | +1. **Package Location:** |
| 41 | + Place your test class in `src/test/java`, mirroring the package structure of the code you want to test (e.g., `edu.harvard.iq.dataverse.export`). |
| 42 | + This placement grants the test class access to package private members in `src/main/java`, which is often necessary when testing internal services directly without going through the full API layer. |
| 43 | +2. **Naming Convention:** |
| 44 | + Name the class `*IT.java` so that the Maven Failsafe plugin automatically picks it up during the `integration-test` phase. |
| 45 | +3. **Setup Annotation:** |
| 46 | + Annotate the class with `@JpaPerformanceTest` to have everything set up automatically for you. |
| 47 | + A `JpaEntityManagerService` will be injected into a static class field for you, allowing interaction with a JPA Entity Manager. |
| 48 | + |
| 49 | +Below is a minimal, generic example [`SamplePerformanceIT`](/_static/developers/testing/SamplePerformanceIT.java) demonstrating the structure and how to run a transaction with or without a return value. |
| 50 | + |
| 51 | +```{literalinclude} /_static/developers/testing/SamplePerformanceIT.java |
| 52 | +:name: sample-performance-test |
| 53 | +:language: java |
| 54 | +:start-at: // |
| 55 | +``` |
| 56 | + |
| 57 | +### Understanding JpaEntityManagerService |
| 58 | +The `JpaEntityManagerService` class abstracts away the boilerplate required to set up a JPA environment for testing. |
| 59 | +Here is what it does under the hood: |
| 60 | + |
| 61 | +1. **Automatic PostgreSQL Server Setup:** |
| 62 | + The involved JUnit Test Extension makes sure to create a single server instance to speed up test setups. |
| 63 | + Nonetheless, any test class will run within its own database on the server, guaranteeing test database isolation. |
| 64 | + |
| 65 | +2. **Automatic Schema Generation:** |
| 66 | + When you call `.start()` on a `JpaEntityManagerService` instance, it initializes an EclipseLink `EntityManagerFactory` configured to automatically generate the database schema (`schema-generation.database.action=create`). |
| 67 | + This guarantees that every test run begins with a pristine database structure derived directly from your current JPA entity mappings. |
| 68 | + You do not need to run Flyway migrations or seed the database beforehand. |
| 69 | + |
| 70 | +3. **Transaction Management:** |
| 71 | + The service handles the lifecycle of JPA transactions automatically. |
| 72 | + You simply pass a lambda to `inTransaction()` or `inTransactionVoid()`. |
| 73 | + The service will: |
| 74 | + 1. Create an `EntityManager` and begin a transaction. |
| 75 | + 2. Execute your lambda. |
| 76 | + 3. Commit the transaction on success, or roll it back if a `RuntimeException` is thrown. |
| 77 | + 4. Close the `EntityManager` in a `finally` block to prevent resource leaks. |
| 78 | + |
| 79 | +4. **Query Statistics via Wrapped DataSource:** |
| 80 | + To make it easy to profile ORM behavior, `JpaEntityManagerService` wraps the underlying PostgreSQL `DataSource` using a proxy that intercepts all SQL statements. |
| 81 | + |
| 82 | + By default, the proxy tracks query counts, which you can retrieve via `QueryCountHolder.getGrandTotal()`. |
| 83 | + This provides immediate, programmatic insight into database efficiency without needing to parse verbose SQL logs. |
| 84 | + It is particularly useful for: |
| 85 | + - Verifying that a batch operation executes in a single query rather than a loop. |
| 86 | + - Catching N+1 query problems by asserting on the number of `SELECT` statements. |
| 87 | + |
| 88 | + *Advanced Usage:* The default service only tracks query counts. |
| 89 | + If you need detailed SQL logging (including bound parameters) or custom execution metrics, you can extend `JpaEntityManagerService` and register additional `StatementListener` implementations on the `ProxyDataSourceBuilder` during initialization. |
0 commit comments