Reduce Spring & Hibernate initialization time while executing JUnit test cases - java

we're using a Spring/Hibernate application and JUnit to execute tests (using IntelliJ). When I have to develop/execute a test, the application startup time is about 10-30 seconds, depending on the number of Hibernate entities to be initialized.
I'm wondering if there is any solution to minimize the initialization time. My idea is to basically have an application server instance where the initialized application is running and I'm able to execute JUnit tests on this application without having to build the Spring/Hibernate context after one test has been finished, but instead to reexecute it immediately after adapting the code.
So my question is if there is a feasible approach and any thoughts/feedback is greatly appreciated. For simplification, let's assume that only test code is going to be changed, not any production code as this probably would require hotswapping or any similar mechanism.
Best regards,
Niko

Related

Integration test in Spring application using Flowable

I need to write some integration tests for my Spring application using Flowable. My tests must include the application BPMN workflow logic.
My question is - should I start and deploy normal Flowable engine during my tests as I do in the application? In official documentation I see some Flowable classes prepared for unit testing but nothing for integration.
Won't starting real Flowable engine cause performance issues during running IT? I'm afraid that they will take long time if I will need to run this with every test separately. How do you deal with this in your applications?
If you ask me, then you should definitely start and deploy a normal Flowable engine during your tests. The link you pasted from the documentation is the exact way how you can do the test. Keep in mind that you can use your own configuration, you don't need a special Spring configuration for the testing.
Starting the real Flowable engines won't cause any performance issues during your testing. All tests in the Flowable repository are actually tests that create and destroy an engine within a single test and that is quite fast. In your case it would be even faster as you won't be starting the engine for each test (the Spring application context is cached between tests). I also have to note that even if you start the engine for each test the time would be negligible as booting the engine is quite fast.
Keep in mind that other components from your Spring application might slow down the start of the tests.
As a reference in the flowable-spring module there are 76 tests in 28 test classes, where each test class has it's own Spring configuration, which means there is no Spring context reuse between tests. All those tests take 55s on my local machine. For those tests you need to keep into consideration that some tests are testing some complex scenarios where the async executors are running and are taking more time than usually. You most probably won't have such tests. With those specific tests disabled (3 from 3 test classes) the test time goes down to 28s.
NB: If you are not using #Deployment or you are relying on the auto deploy feature from Flowable then make sure that you are deleting the instances that you are creating in your tests. This would make sure that data from one test does not affect data from another test.

Best practices or the effective approach to write integration test which run in a continues integration environment

In general, I write integration test from my service/ remoting layer to the database so that I can check the server side layers are integrated and tested, I would like to keep the rollback as false if not we will miss out the database constraint level validation. It is a personal preference.
We can follow different approaches
- Create data for each test case and delete it once executed
- Run with a certain amount of existing common data such as (User)
There may be entities depends on other several entities and to be able to test such flows it requires a lot of effort to create every entity for each test case or class and maybe for a business flow if we make a decision we create a certain amount of data and execute a business flow with a certain number of test and clear the data. These things can consume a lot of time to run such test cases.
Is there an effective approach or best practice that is followed in the industry to write integration test in the continues integration environments. I normally use TestNG as it provides spring support. Is there any Java-based frameworks.
I think it really depends on a project and there is no silver bullet solution here.
There are indeed many approaches as you state, I'll mention a few:
Take advantage of Spring's #Transactional annotation on the test. In this case, spring will execute rollback after each test. so that the data changed by the test won't really be saved in the database even if the test passes.
Do not use #Transactional but organize tests so that they won't interfere (each test will use its own set of data that can co-exist with other tests data). If the test fails and doesn't "clean-up" its stuff, then other tests should still run. In addition, if the tests are being run in parallel, they still should not interfere.
Use new schema for each test (obviously expensive, but still can be a viable option to some projects).
Now, the real question is what do you test.
If you test a java code, like that your SQLs are created correctly, then probably the first way is a way to go.
Of course, it also depends on what commands are being executed during the tests, not in all databases all the commands can be in a transaction (for example in Postgres you can use DDL inside a transaction, in Oracle you can't, and so forth).
Another concern to think about during the continuous testing is the performance of tests.
Integration tests are slow and if you have a monolith application that runs hundreds of them, then the build will be really slow. Managing build that runs hours is a big pain.
I would like to mention here 2 ideas that can help here:
Moving to microservices helps a lot in this case (each microservice runs only a bunch of its tests and hence the build of each microservice on its own is much faster by nature)
Another interesting option to consider is running the tests against a docker container of the database that starts right in the test case (it also can be cached so that not every test will raise a docker container). A big benefit of such an approach is that everything runs locally (on the build server), so no interaction with the remote database (performance) + the clean-up of resources is done automatically, even if some tests fail. The Docker container dies and all the data put by the tets gets cleaned up automatically. Take a look at Testcontainers project maybe you'll find it helpful

Is it bad practice to allow my Junit tests to interact with a real DB?

I'm building a basic HTTP API and some actions like POST /users create a new user record in the database.
I understand that I could mock these calls, but at some level I'm wondering if it's easier to let my Junit tests run against a real (test) database? Is this a bad practice? Should only integration tests run against a real DB?
I'm using flyway to maintain my test schema and maven for my build, so I can have it recreate the test DB with the proper schema on each build. But I'm also worried that I'd need some additional overhead to maintain/clean the state of the database between each test, and I'm not sure if there's a good way to do that.
Unit tests are used to test single unit of code. This means that you write a unit test by writing something that tests a method only. If there are external dependencies then you mock them instead of actually calling and using those dependencies.
So, if you write code and it interacts with the real database, then it is not a unit test. Say, for some reason your call to db fails then unit test will also fail. Success or failure of your unit test should not be dependent on the external dependencies like db in your case. You have to assume that db call is successful and then hard code the data using some mocking framework(Mockito) and then test your method using that data.
As often, it depends.
On big projects with lots of JUnit tests, the overhead for the performance can be a point. Also the work time needed for the setup of the test data within the database as well as the needed concept for your tests not interfering with the test data of other tests while parallel execution of JUnit tests is a very big argument for only testing against a database if needed and otherwise mock it away.
On small projects this problems may be easier to handle so that you can always use a database but I personally wouldn't do that even on small projects.
As several other answers suggest you should create unit tests for testing small pieces of code with mocking all external dependencies.
However sometimes ( a lot of times) it should worth to test whole features. Especially when you use some kind of framework like Spring. Or you use a lot of annotations. When your classes or methods have annotations on them the effects of those annotations usually cannot be tested via unit-tests. You need the whole framework running during the test to make sure it works as expected.
In our current project we have almost as much integration tests as unit tests. We use the H2 in-memory DB for these tests, this way we can avoid failures because of connectivity problems, and Spring's test package could collect and run multiple integration tests into the same test-context, so it has to build the context only once for multiple tests and this way running these tests are not too expensive.
Also you can create separate test context for different part of the project (with different settings and DB content), so this way the tests running under different context won't interfere with each-other.
Do not afraid of using a lot of integration tests. You need some anyway, and if you already have a test-context it's not a big deal adding some more tests into the same context.
Also there are a lot of cases which would take a LOT of effort to cover with unit-tests (or cannot be covered fully at all) but can be covered simply by an integration tests.
A personal experience:
Our numerous integration tests were extremely useful when we switched from Spring Boot to Spring Boot 2.
Back to the original question:
Unit tests should not connect to real DB, but feel free to use more integration tests. (with in-memory DB)
Modern development practices recommend that every developer runs the full suite of unit tests often. Unit tests should be reliable (should not fail if the code is OK) Using an external database can interfere with those desiradata.
If the database is shared, simultaneous runs of the testsuite by different developers could interfere with each other.
Setting up and tearing down the database for each test is typically expensive, and thus can make the tests too slow for frequent execution.
However, using a real database for integration tests is OK. If you use an in-memory database instead of a fully real database, even set up and tear down of the database for each integration test can be acceptably fast.
A popular choice is the use of an in-memory database to run tests. This makes it easy to test, for example, repository methods and business logic involving database calls.
When opting for a "real" database, make sure that every developer has his/her own test database to avoid conflicts. The advantage of using a real database is that this prevents possible issues that could arise because of slight differences in behavior between in-memory and real database. However, test execution performance can be an issue when running a large test suite against a real database.
Some databases can be embedded in a way that the database doesn't even need to be installed for test execution. For example, there is an SO thread about firing up an embedded Postgres in Spring Boot tests.

How to increase productivity with slow startup time in frameworks like Spring Boot and Hibernate?

I am trying out Hibernate and noticed that the startup time is roughly 4500ms on my laptop.
Spring boot for example also takes ~7000ms to startup, if you have the most basic spring-JPA and web-MVC set up.
How are developers supposed to do prototyping or TDD in Java, if after every code change I have to wait five seconds?
How much faster do dynamic language start up?
For example, how long does a single persist statement in Python's SQLAlchemy take?
I understand that in real-world applications startup takes way longer, and updates or tests are performed remotely with a CI/CD server. But I'm still in the learning phase and for now I like to be able to see my output after minor code changes.
I think there is a misconception on your end. The main focus of TDD are unit tests. Of course, TDD is a great concept, but the point is: you want those quick feedback loops.
Thus you use it to write unit tests. Tests that work on a small, isolated unit. Tests that have zero dependencies on anything else but your compiled classes and JUnit/TestNG/... other test related frameworks.
You write a test (5, 10 lines of code); you write production code (probably not much more than that). Run, write new test, or fix production code ...
And then; when you are convinced that all your units do what they are supposed to do; then you start looking into writing "functional" or "integration" tests that require your whole stack to be up and running.

rollback nested transaction in java

We run junit tests to test our java codebase. Each test will read/write some data to a mysql database(possibly multiple tables). It seems that the tests are leaving behind data that is interfering with tests that run after it. Is it possible that we can abort/rollback all changes done by the test at the end of each unit test?
We are using cactus framework to test ejbs in glassfish application server. The ejbs can call code in the AS that can read/write to the DB.
We are using hibernate and jdbc to talk to the DB.
A possible solution would be resetting the database after each test with a simple database script you could run after each test, but this would consume a LOT of time.
If you are running integration tests, then you will be using real EJBs, there is not much you can do about this because it would be complicated to make them understand when the test begins and ends. For a simple operation you could force an Exception to cause a rollback, but if you use more than 1 transaction in a single test this won't work.

Categories

Resources