That’s a good question. I’m going to focus on the "sub-questions" (in different order) to come up with an answer...
And if the class does things like network connection, reading and writing files (which may also depend on the state of the file system), database connection, etc?
Not everything is a unit test
The first thing is that nay we’re talking about real unit testing.
Unit tests shall be isolated and shall in general ensure that a method or, at most, a class presents the expected behaviour based on the simple concept of input, processing and output, that is, date of entry X
, I must receive Y
and so on.
Secondly, it is important to understand that not everything necessarily needs to be tested in unit tests. For other situations they serve integration tests, system tests in other types of tests.
Integration tests
Integration tests serve to determine whether the parts continue to function when put together.
Nothing prevents an integration test from accessing files, network or database. The important issue here is how reliable this is so that external factors do not influence the test result, leading to false positives or false negatives.
Common cases are:
- Depending on whether a file exists on disk: can lead to problems if someone forgets to put the file in the correct location.
- Depend on an existing database: tests must reset the database at each run, otherwise existing data may interfere and problems will never be detected, such as in case there are no records in a table.
- Relying on an external service over the network: if the test needs a web service to work, you can provide a endpoint fictitious, for example.
Testing in and out
In the case of files on disk, it is nothing complicated and it is not wrong that the test class records some files on disk and performs a routine that will read the files and generate others, and at the end you read the generated files and see if they are correct.
Another possibility is to create your classes using abstractions like InputStream
and OutputStream
instead of passing a File
or Path
. So you test your generation and reading routines without actually using the disk. Much more efficient.
Testing databases
There are several ways. The most efficient is to use a database in memory.
Other ways is to create a routine that cleans and/or restores a database. Ideally, the bank should be local, to prevent multiple people from running the test at the same time and conflict.
Another alternative is to use a container like Vagrant to run the tests in an isolated and efficient way. Vagrant uses Virtualbox and you can configure a virtual machine that simulates the production environment.
Testing the network
To test how your system reacts to external services, you can create a local service, initialized during testing, that returns fixed values according to the test.
If you have access to WSDL or sterno service specification this is even quite simple.
If your system is distributed you can create isolated stress situations to verify that the code is resilient. For example, if your system has two components that play the role of producer and consumer and you want to test whether communication works well, can create a producers and consumers who generate and consume messages a large amount of messages without performing a really heavy processing. At the end you simply check some counter to know if no message has been lost.
What you don’t need to test
Anyway, there are things you just don’t test because it’s not part of the scope of the project.
In the case of file, I used as an example the use of InputStream
, after all you want to test if your system is able to decode the input, don’t you? Or do you now want to test if the Java API works?
In the case of a web service or network service, you also don’t want to test the third party system, do you? You do not need and should not invoke third-party systems during the test. You simply assume that they work according to some documentation, that is, you send X
he must return Y
. If that’s the contract, assume he’ll fulfill it.
The case of the class that handles files is even practical, but for a class that depends on a service as unfriendly and unpredictable as a network?
I know that in practice various runtime errors can occur that are not found in isolated tests.
Here come concepts such as MVP, non-continuous delivery or prototype of architecture.
Basically, for integrations with third-party systems you must create isolated prototypes to test whether integration occurs successfully.
Don’t wait for the system to be working to do this!
One way to do this is to create a library type in a separate project that can be called via the command line or something like that. This is not part of the main project, so it does not disrupt the development cycle. However, whenever there is a problem or change you can run the tests manually.
In addition, with releases frequent system, prioritizing the areas of greatest risk, you put those parts that present risk of failure in "production" right away and anticipate problems.
How to prove in development time that these classes work?
Here’s a summary of everything, but before we try to prove that something works, let’s think first about how to divide all this.
First, the functionalities of your system need to be tested unitarily. If you cannot do this, you have a modeling problem.
For example, if you have something like:
rotina(arquivoEntrada, arquivoSaida) {
String s = lerArquivo(arquivoEntrada)
String r = processarArquivo(s)
gravarResultado(r, arquivoSaida)
}
You should deeply consider switching to something like:
rotina(inputStream, outputStream) {
Data data = ler(inputStream)
Result result = processar(data)
gravarResultado(result, outputStream)
}
Now you can test each routine independently.
Second, you don’t need to test everything. Don’t load the burden of testing the Java API, for example.
Third, if there are integrations with external systems, you should do three things:
- Test these integrations manually as early as possible to ensure they work as expected.
- Place the test code in a separate location so that it does not interfere with the development of the main system.
- Prioritize putting integrations into production as early as possible so that hidden problems appear as soon as possible.
Finally, I can say that you should not worry about prove that everything works. Of course, the better your tests, the better the quality of the system.
However, there will always be cases that cannot be anticipated and that often only experience and analytical thinking will be able to catch up. A common example is that disk reading, database reading or integration with other systems becomes a bottleneck in performance.
This is because your tests may be 100% correct, but often we test with a mass of data that hardly exceeds 10 elements, when not 2 or 3.
Therefore, the "architect" or more experienced people participating in the development of the application should clinically analyze what are the critical points and determine whether additional tests of load, stress, resilience, etc.
You are confusing principles, the purpose of the tests is not to leave the system infertile but to ensure that there are treatments for eventual failures.
– Felipe Assunção
In java is used
try-catch
for this type of test, whenever you lose the connection to a server, it releases an error and goes to the exception, and that’s where it is decided what will be done next (normally).– Brumazzi DB
If you only have one data structure, you don’t have to test it. Put an example of what you consider "unstoppable". I can’t imagine something unstable. There are things you can’t easily test in an automated way, but none of the ones mentioned in the question. Evidence can be obtained by formal method, but in general does not compensate for the cost. It is rare for someone to want proof.
– Maniero
@Felipeasunción but I did not state that the purpose of the tests is to show that a system is infallible, I only asked if there is any way to test classes that depend on resources where one cannot be sure of their condition.
– Sid
@bigown think of any class that needs networking: How do you test if it reacts the way you want if the connection fails? How to test if rela reacts the way you want if the connection succeeds? What’s more: How to test if it behaves the way you want it to if the network suddenly drops? These are nondeterministic conditions, where you depend on luck for something. It’s different than testing a data structure.
– Sid
Unit testing is something new to me, but from what I understand the main goal is to verify that the code will work properly when there are changes in the system that can affect its behavior in some way. If there is a network failure and it is mandatory for your system to work, it should be dealt with within a block
try-catch
, is not something that should be tested in a unit test.– Filipe Moraes
@Sid considering his example: If you need to copy a network file (unstable) if the network is currently unavailable how would you like your class to behave? returns exception or puts the file in a queue to wait for the network? any behavior you want basically you just need to ensure that during the failure the system will know how to respond to the problem ie
testável
.– Felipe Assunção
I will repeat: there is no data structure test. Test and exception are separate things. See this: http://answall.com/q/36745/101
– Maniero
@Felipeasunción but that’s where it is. One of the goals of the tests is what? Offer immediate feedback so we can fix errors as soon as possible, because sometimes the code you wrote doesn’t always do what you think it does, so you turn to make sure. " Simply ensure in your code that the program will do such a thing if the connection fails, "yes, but how to ensure this as soon as possible?
– Sid