How to write unit tests if you don’t feel like

    All of us at work every now and then try to get to write unit tests. Many have already understood that from them one harm. Writing tests takes a lot of time for which you could do something more useful. If the test suddenly begins to fall, the assembly on the server of continuous integration breaks down, the release does not roll out in time, the business loses money and you, the author of the fallen unit test, are the last. When refactoring, tests cause a headache, because they begin to fall and you have to deal with it.


    Nevertheless, evil supervisors require more tests, talking about the so-called "quality control". Particularly tricky managers even consider coverage and do not let you go until you reach it. Your code is wrapped in a review if there are no tests in it or if they didn’t like something. Sheer frustration!


    What to do?


    Fortunately, there are ways to write reliable unit tests that never fall. I did not come up with these methods, they are successfully practiced in a number of open-source projects. All the examples I give are taken from the real code. Therefore, there is no reason for you not to take advantage of what is already being used in practice by other developers!


    The first and most obvious way: do not check anything in the unit test. Here is a simple example :


    publicvoidtestSetFile(){
        System.out.println("setFile");
        File f = null;
        BlastXMLParser instance = new BlastXMLParser();
        instance.setFile(f);
    }

    Does the boss require one hundred percent coverage? Well, let's test that the empty default constructor and the trivial setter do not fall with the exception. The fact that the setter has really installed something will not be checked, especially since in fact we have overwritten null with null. It is reliable, such a test should not fall.


    Too trite and you can not push through this on the review? You can do smarter :


    @TestpublicvoidgetParametersTest(){
        List<IGeneratorParameter<?>> parameters = generator.getParameters();
        containsParameterType(parameters, AtomColor.class);
        containsParameterType(parameters, AtomColorer.class);
        containsParameterType(parameters, AtomRadius.class);
        containsParameterType(parameters, ColorByType.class);
        ...
    }

    It looks solid, as if something is really checked. But let's look at the containsParameterType method :


    public <T> booleancontainsParameterType(List<IGeneratorParameter<?>> list, Class<T> type){
        for (IGeneratorParameter<?> item : list) {
            if (item.getClass().getName().equals(type.getName())) returntrue;
        }
        returnfalse;
    }

    Elegant, right? Both methods separately look reasonable, but together they check nothing. This can give a ride, especially if the methods are committed separately and sent to different reviewers. Especially since they are in different classes!


    However, you can not last so long. Evil bosses suspect that something is wrong, without seeing any asserts in the code. Asserts still worth adding. But, for example, so that they are not executed. Here is a rough approach :


    for (int i = 0; i < 0; i++)
    {
        Assert.assertTrue(errorProbabilities[i] > 0.0d);
    }

    Cycle for 0 iterations. This is to miss unless a very drunk reviewer. However, the following option is much more elegant:


    List<JavaOperationSignature> sigs = new ArrayList<>();
    List<JavaOperationSignature> sigs2 = new ArrayList<>();
    for (int i = 0; i < sigs.size(); i++) { // делаем вид, что заполняем списки
        sigs.add(JavaOperationSignature.buildFor(nodes.get(i)));
        sigs2.add(JavaOperationSignature.buildFor(nodes2.get(i)));
    }
    for (int i = 0; i < sigs.size() - 1; i++) { // делаем вид, что сравниваем
        assertTrue(sigs.get(i) == sigs.get(i + 1));
        assertTrue(sigs2.get(i) == sigs2.get(i + 1));
    }

    There are already many reviewers will not notice the trick! Both cycles are never executed, because the border is the size of an empty list. Take note.


    Suppose your colleague wrote a test ten years ago to verify that the method throws an exception. He made it the old-fashioned way, through catch. Fine, add any new asserts to the end of the method . No one will notice that they are not executed:


    try {
        getDs().save(e);
    } catch (Exception ex) {
        return; // нормальный выход из теста здесь!
    }
    // Следующая строчка выполнится, если что-то пойдёт не так
    Assert.assertFalse("Should have got rejection for dot in field names", true); 
    // А это не выполнится никогда
    e = getDs().get(e);
    Assert.assertEquals("a", e.mymap.get("a.b")); // Но никто этого не заметит!
    Assert.assertEquals("b", e.mymap.get("c.e.g"));

    Are your managers completely insolent and looking at covering not only the main code, but also the tests? Now they notice such things? Okay, and with this you can fight. We will write asserts that check all nonsense. For example , that the newly created object is not null:


    Assert.assertNotNull(new Electronegativity());

    If the operator newreturns null for you, then your virtual machine has serious problems. Therefore, such an assert is reliable as a rock. Although, of course, an experienced reviewer will be immediately struck. A more clever way to trick the system is to check the boolean value :


    DocumentImplementation document = new DocumentImplementation(props);
    assertNotNull(document.toString().contains(KEY));
    assertNotNull(document.toString().contains(VALUE));

    Thanks to autoboxing, the primitive is booleanwrapped in an object Boolean, which, of course, will never be null. This assert is no longer so striking, it is honestly executed and it doesn’t give a damn, true it will return there or false. Similar focus works with other primitive types:


    Assert.assertNotNull("could not get nr. of eqr: ", afpChain.getNrEQR());

    Unclassified, the code looks quite reasonable. The reviewer will have to show true meticulousness in order to notice that the method getNrEQRreturns a primitive one intand therefore such an assert cannot fall.


    Another great way to check nothing is to write a long message to an assertion with a concatenation of different components, and remove the second argument:


    Assert.assertNotNull("Attempt to test atom type which is not defined in the " +
         getAtomTypeListName() + ": " + exception.getMessage());

    Do you see? It seems that since we have a long, long message, then something serious is being checked. In fact, it is checked that this message itself is not null, which is not possible, because string concatenation in Java will always produce a non-zero object.


    In general, of course, if you want to throw dust in your eyes, then assertNotNullyour best friend. But not the only friend! For example, assertEqualsyou can perfectly use it to compare a number with yourself :


    Assert.assertEquals(ac2.getAtomCount(), ac2.getAtomCount());

    And if you are caught by the hand, you can always justify that you checked the stability of the method getAtomCount. Maybe someone instead of a simple getter there random number generator shove!


    If you work with the double type, then it's time to recall the comparison with NaN. Unfortunately, assertNotEqualshere you are not an assistant, he is too clever. But you can always useassertTrue :


    Assert.assertTrue(result1.get(i) != Double.NaN);

    As you know, NaN is not equal to anything, even to itself, and therefore such a comparison is always true.


    Also assertTrueuseful for obvious instanceof-checks :


    Assert.assertNotNull(cf.getRealFormat());
    Assert.assertNotNull(cf.getImaginaryFormat());
    Assert.assertTrue(cf.getRealFormat() instanceof NumberFormat);
    Assert.assertTrue(cf.getImaginaryFormat() instanceof NumberFormat);

    Methods getRealFormatand getImaginaryFormatand so return NumberFormat, so that instanceof only verifies that the inequality is null. But on zero we already checked it higher. In this simple way the number of asserts can be doubled.


    There are a number of ways in which I quickly did not find examples. Let's say you can use the method assertThatfrom AssertJ and not use the result (for example, assertThat(somethingIsTrue())instead assertThat(somethingIsTrue()).is(true)). You can wrap the text of the test in large try { ... } catch(Throwable t) {}so as to catch and ignore AssertionError. The same effect can be achieved smarter. For example, send asserts to a background stream through CompletableFuture.runAsync()and not join the result. I am sure that you yourself will come up with many ways to resist managerial outrage.


    But be careful. The authorities in any case should not learn about the static analysis. Otherwise, all in vain. Most of the methods mentioned in the article, unfortunately, are easily found to be a good static analyzer. So tccc!


    Also popular now: