Статьи

Написание ваших первых модульных тестов

Модульные тесты необходимы для тестирования отдельных разделов кода. В Java это обычно будет класс. Они дают уверенность программистам, позволяя вносить изменения. И, выполняя тесты на протяжении всей разработки, любые изменения, которые нарушают тесты, могут быть переоценены, независимо от того, приводит ли это к исправлению производственного кода или изменению теста для его прохождения. В этой статье я расскажу о правилах FIRST, которые определены в книге «  Чистый код»,  написанной дядей Бобом Мартином.

ПЕРВЫЙ означает, что тесты должны быть:

  • Быстро : тесты должны быть достаточно быстрыми, чтобы не отчаиваться от их использования. Если вы вносите изменения в класс или метод, к которым прикреплено несколько тестов, вы с большей вероятностью будете запускать эти тесты после внесения изменений, если для их выполнения требуется около секунды, по сравнению с более медленными тестами, которые не могут занять некоторое время. для инициализации, но каждый тестовый пример также может занять более 1 секунды для запуска. Например, я запустил интеграционные тесты, которые занимали бы более 20 секунд, и я ненавидел запускать их после изменений кода. Модульные тесты, однако, не должны тестировать несколько компонентов, как это делают интеграционные тесты, и поэтому их работа должна быть менее 1 секунды.
  • Независимо : тесты не должны зависеть от состояния предыдущего теста, будь то состояние объекта или проверенных методов. Это позволяет вам запускать каждый тест отдельно, когда это необходимо, что может произойти, если тест прервется, поскольку вы захотите выполнить отладку в методе, и вам не придется запускать другие тесты, прежде чем вы увидите, что происходит не так. Если у вас есть тест, который ищет наличие некоторых данных, то эти данные должны быть созданы в настройке теста и, предпочтительно, удалены впоследствии, чтобы не повлиять на последующие тесты.
  • Повторяемость : тесты должны повторяться в любой среде без изменения результатов. Если они не зависят от сети или базы данных, это устраняет возможные причины неудачи тестов, поскольку единственное, от чего они зависят, — это код класса или метода, который тестируется. Если тест не пройден, метод работает неправильно или тест настроен неправильно — и ничего больше.
  • Самопроверка : каждый тест будет иметь один логический результат: пройдено или не выполнено. Вам не следует проверять правильность вывода метода при каждом запуске теста. Тест должен сказать вам, был ли результат теста действительным или нет. Обычно это делается с помощью таких утверждений, как assertTrue или assertEquals , что приведет к тому, что тест будет пройден или не пройден в зависимости от их результатов.
  • Своевременно : модульные тесты должны быть написаны непосредственно перед рабочим кодом, который проходит тестирование. Это то, что вы будете следовать, если вы делаете TDD (Test Driven Development), но в противном случае это может не применяться. Я лично не использую TDD и поэтому всегда пишу свои тесты после написания своего производственного кода. Хотя я вижу причины для этого, я считаю, что это правило, которое можно пропустить, если оно не подходит.

Ниже приведен небольшой модульный тест, написанный мной по правилам выше. Обратите внимание, что это простой тест, и если вы считаете, что есть сценарии, которые не тестируются, вы правы! (Но пример уже достаточно длинный.)

@RunWith(JUnit4.class)
public class PersonValidatorTest {

    private PersonValidator testSubject = new PersonValidator();

    private Person person;

    @Test
    public void personCalledDanNewtonIsNotValid() {
        person = new Person("Dan","Newton",23,1000);
        assertFalse(testSubject.isValid(person));
    }

    @Test
    public void personNotCalledDanNewtonIsValid() {
        person = new Person("Bob","Martin",60,170);
        assertTrue(testSubject.isValid(person));
    }

    @Test
    public void personIsOlderThanTwentyFiveIsValid() {
        person = new Person("Bob","Martin",60,170);
        assertTrue(testSubject.isValid(person));
    }

    @Test
    public void personIsYoungerThanTwentyFiveIsNotValid() {
        person = new Person("Bob","Martin",10,170);
        assertFalse(testSubject.isValid(person));
    }

    @Test
    public void personIsTwentyFiveIsNotValid() {
        person = new Person("Bob","Martin",25,170);
        assertFalse(testSubject.isValid(person));
    }
}
public class PersonValidator {

    public boolean isValid(final Person person) {
        return isNotCalledDanNewton(person) && person.getAge() > 25 && person.getHeight() < 180;

    }

    private boolean isNotCalledDanNewton(final Person person) {
        return !person.getFirstName().equals("Dan") || !person.getLastName().equals("Newton");
    }

}

Так следуют ли эти тесты ПЕРВЫМ?

  • Быстро: они не делают много, поэтому очевидно, что они быстро бегают.
  • Independent: Each test sets up a new person and passes in all the parameters that are required for the test.
  • Repeatable: This test does not depend on any other classes or require a connection to a network or database.
  • Self-validating: Each test has a single assert, which will determine whether the test passes or fails.
  • Timely: Failure!! I did not write these tests before writing the code in PersonValidator, but again, this is because I do not use TDD.

By following the FIRST rules in this post, your unit tests should see some improvement. I am not saying that following these rules alone will make your unit tests perfect, as there are still other factors that come into making a good test, but you will have a good foundation to build upon.

As mentioned earlier, a good place to look for more information on this subject, writing unit tests in general, and much more, have a look at the Clean Code book.