How do you test something "unstoppable"?

Asked

Viewed 404 times

4

Testing certain components of a system is simple if that component does not have very relevant dependencies, such as a class like this:

public class Data {
    private final String _field;

    public Data(String field) {
        _field = field;
    }
    ....
}

But testing this class is simple because it is simply a data structure that does not influence much except the state that exists within itself.

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?

How to prove in development time that these classes work?

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?

  • 1

    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.

  • 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).

  • 2

    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.

  • @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.

  • @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.

  • 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.

  • @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.

  • 3

    I will repeat: there is no data structure test. Test and exception are separate things. See this: http://answall.com/q/36745/101

  • @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?

Show 4 more comments

3 answers

8

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:

  1. Test these integrations manually as early as possible to ensure they work as expected.
  2. Place the test code in a separate location so that it does not interfere with the development of the main system.
  3. 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.

4

That’s what mocking is for. I will explain using an example:

Obs: I will use C# for the code examples, but the syntax is very similar to that of Java soon will be very easy to understand.

Obs²: C# has the function of automatically generating property getters/setters (public int Numero {get; set;). But under the hood it works like in java: private variables with methods to access and set such variables.

public class DataProcessing
{
    private Database _database;

    public DataProcessing(Database database)
    {
        _database = database;
    }
}

And the Database class:

public class Database
{
    public List<int> Numbers { get; private set; }

    public Database()
    {
        //conecta-se ao db, puxa os dados e salva em Numbers
    }
}

As you can see, it is not possible to simply create an instance of Database without pulling the data from the bank, because the property List<int> Numbers owns the set private. Of course, you could edit the class Database to include a constructor that accepts a list to save to the property but this constructor would only be useful for testing, and would never be used during normal application operation. A possible solution would be to use interfaces.

public interface IDatabase
{
    List<int> Numbers { get; }
}

The normal seat class:

public class Database : IDatabase
{
    public List<int> Numbers { get; private set; }

    public Database()
    {
        //conecta-se ao db, puxa os dados e salva em Numbers
    }
}

The only difference is that now the class Database inherits from the interface IDatabase. In this way, it is possible to create a class that also inherits from IDatabase but is used for testing:

public class DatabaseMock : IDatabase
{
    public List<int> Numbers { get; private set; }

    public DatabaseMock()
    {
        Numbers = new List<int>
        {
            1, 2, 3, 4, 5, 6
        };
    }
}

This way, in all your tests, instances of Databasemock will have the same data.

All that’s left is to change the builder of DataProcessing of Database for IDatabase

public class DataProcessing
{
    private Database _database;

    public DataProcessing(IDatabase database)
    {
        _database = database;
    }

    public void SumNumbers()
    {
        //Process data
    }
}

If you have questions let me know that I try to explain better, if you want I can also try to pass the code to Java.

And that you would do with anything that has dependencies. Keep in mind that you could also pass, in the case of this example, a Database mock with the null property and see if your Database classes treat this problem correctly.

Ps.: My use of the term mock is not according to the exact definition of it. I should have used the term dubs but anyway. That answer explains this well (more specifically the difference between stubs and mocks).

In addition there is a beautiful article that explains even more in depth: http://martinfowler.com/articles/mocksArentStubs.html

  • So it’s basically creating classes/components that will create a false environment to see how the class behaves? You can do this with things like networks or some more external and unstable resource?

  • 1

    This. The idea of unit testing is to test whether that specific unit of code behaves correctly, given that everything else works. So there is no problem in forging an ideal environment (or a failure).

  • 1

    Exactly what @Pablo said. Once you know the possible states of something you want to simulate (in the case of a network, for example, a connection or data sending problem may occur), you can, in addition to treating such states in your code, test them to make sureif they’re being treated properly. This is the beauty of unit testing, you don’t need to take the network cable to see if your program won’t explode when a connection failure occurs :)

  • I still prefer to use the Repository Pattern to bring the abstraction of my mock to a level above, since business classes should not even know about the existence of a database or any persistence mechanism.

  • @Viniciuszaramella I share your preference but I thought doing the same in the example would make it very extensive.

1

To corroborate with the explanations already given, they would like to point out three cases where testing does not make sense in my opinion.

First is in classes that represent only one data structure, class with only getters and setters.

Second are control classes, usually called Managers, Processor, etc. These classes usually only delegate tasks to others and serve as a facade to simplify work and decrease code repetition of those who will use your code. Because they have no business rule in itself, the only thing that can be tested in these classes is whether a sequence of methods has actually been called. But this kind of testing only makes your code harder to change.

The third is not testing third-party code. If you are using a third-party library you should trust that they have tested their code and that the code works as expected. If when using a library you start doing tests that only check the functionality of the library and not your code you are wasting time.

Finally, focus unit tests on classes that represent business rules and make sure that these classes are isolated from interfaces ( Http, User Input...) and from persistence mechanisms by an abstraction layer.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.