Статьи

Что следует тестировать?

Вчера я был в офисе, разговаривая о тестировании с одним из моих коллег, который был немного убежден в написании модульных тестов.

Одна из причин, по которой он использовал это, заключалась в том, что некоторые тесты кажутся бессмысленными, что подводит меня к вопросу о том, что именно вы делаете, и что вам не нужно беспокоиться.

Рассмотрим простой неизменяемый бин Name с конструктором и несколькими получателями.

В этом примере я собираюсь позволить коду говорить за себя, так как я надеюсь, что очевидно, что любое тестирование будет бессмысленным.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Name {
 
  private final String firstName;
  private final String middleName;
  private final String surname;
 
  public Name(String christianName, String middleName, String surname) {
    this.firstName = christianName;
    this.middleName = middleName;
    this.surname = surname;
  }
 
  public String getFirstName() {
    return firstName;
  }
 
  public String getMiddleName() {
    return middleName;
  }
 
  public String getSurname() {
    return surname;
  }
}

… и чтобы подчеркнуть это, вот бессмысленный тестовый код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class NameTest {
 
  private Name instance;
 
  @Before
  public void setUp() {
    instance = new Name("John", "Stephen", "Smith");
  }
 
  @Test
  public void testGetFirstName() {
    String result = instance.getFirstName();
    assertEquals("John", result);
  }
 
  @Test
  public void testGetMiddleName() {
    String result = instance.getMiddleName();
    assertEquals("Stephen", result);
  }
 
  @Test
  public void testGetSurname() {
    String result = instance.getSurname();
    assertEquals("Smith", result);
  }
}

Причиной бессмысленного тестирования этого класса является то, что код не содержит никакой логики; однако в тот момент, когда вы добавляете что-то подобное в класс Name:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public String getFullName() {
 
    if (isValidString(firstName) && isValidString(middleName) && isValidString(surname)) {
      return firstName + " " + middleName + " " + surname;
    } else {
      throw new RuntimeException("Invalid Name Values");
    }
  }
 
  private boolean isValidString(String str) {
    return isNotNull(str) && str.length() > 0;
  }
 
  private boolean isNotNull(Object obj) {
    return obj != null;
  }

… тогда вся ситуация меняется. Добавление некоторой логики в виде оператора if создает целую кучу тестов:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@Test
  public void testGetFullName_with_valid_input() {
 
    instance = new Name("John", "Stephen", "Smith");
 
    final String expected = "John Stephen Smith";
 
    String result = instance.getFullName();
    assertEquals(expected, result);
  }
 
  @Test(expected = RuntimeException.class)
  public void testGetFullName_with_null_firstName() {
 
    instance = new Name(null, "Stephen", "Smith");
    instance.getFullName();
  }
 
  @Test(expected = RuntimeException.class)
  public void testGetFullName_with_null_middleName() {
 
    instance = new Name("John", null, "Smith");
    instance.getFullName();
  }
 
  @Test(expected = RuntimeException.class)
  public void testGetFullName_with_null_surname() {
 
    instance = new Name("John", "Stephen", null);
    instance.getFullName();
  }
 
  @Test(expected = RuntimeException.class)
  public void testGetFullName_with_no_firstName() {
 
    instance = new Name("", "Stephen", "Smith");
    instance.getFullName();
  }
 
  @Test(expected = RuntimeException.class)
  public void testGetFullName_with_no_middleName() {
 
    instance = new Name("John", "", "Smith");
    instance.getFullName();
  }
 
  @Test(expected = RuntimeException.class)
  public void testGetFullName_with_no_surname() {
 
    instance = new Name("John", "Stephen", "");
    instance.getFullName();
  }

Итак, учитывая, что я только что сказал, что вам не нужно проверять объекты, которые не содержат никаких логических операторов, и в список логических операторов я бы включил if и переключился вместе со всеми операторами (+ — * — ), и целый набор вещей, которые могут измениться и объекты состояния.

Учитывая эту предпосылку, я бы тогда предположил, что бессмысленно писать модульный тест для объекта доступа к адресным данным (DAO) в проекте Address, о котором я говорил в моей последней паре блогов. DAO определяется интерфейсом AddressDao и реализуется классом JdbcAddress :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class JdbcAddress extends JdbcDaoSupport implements AddressDao {
 
  /**
   * This is an instance of the query object that'll sort out the results of
   * the SQL and produce whatever values objects are required
   */
  private MyQueryClass query;
 
  /** This is the SQL with which to run this DAO */
  private static final String sql = "select * from addresses where id = ?";
 
  /**
   * A class that does the mapping of row data into a value object.
   */
  class MyQueryClass extends MappingSqlQuery<address> {
 
    public MyQueryClass(DataSource dataSource, String sql) {
      super(dataSource, sql);
      this.declareParameter(new SqlParameter(Types.INTEGER));
    }
 
    /**
     * This the implementation of the MappingSqlQuery abstract method. This
     * method creates and returns a instance of our value object associated
     * with the table / select statement.
     *
     * @param rs
     *            This is the current ResultSet
     * @param rowNum
     *            The rowNum
     * @throws SQLException
     *             This is taken care of by the Spring stuff...
     */
    @Override
    protected Address mapRow(ResultSet rs, int rowNum) throws SQLException {
 
      return new Address(rs.getInt("id"), rs.getString("street"),
          rs.getString("town"), rs.getString("post_code"),
          rs.getString("country"));
    }
  }
 
  /**
   * Override the JdbcDaoSupport method of this name, calling the super class
   * so that things get set-up correctly and then create the inner query
   * class.
   */
  @Override
  protected void initDao() throws Exception {
    super.initDao();
    query = new MyQueryClass(getDataSource(), sql);
  }
 
  /**
   * Return an address object based upon it's id
   */
  @Override
  public Address findAddress(int id) {
    return query.findObject(id);
  }
 
}

В приведенном выше коде единственный метод в интерфейсе:

1
2
3
4
@Override
  public Address findAddress(int id) {
    return query.findObject(id);
  }

… который действительно простой метод получения. Мне кажется, что это нормально, поскольку в DAO, который входит в AddressService , не должно быть какой-либо бизнес-логики, у которой должно быть множество модульных тестов.

Вы можете решить, хотите ли вы писать модульные тесты для MyQueryClass. Для меня это пограничный случай, поэтому я с нетерпением жду каких-либо комментариев …

Я предполагаю, что кто-то не согласится с этим подходом, скажем, вам следует протестировать объект JdbcAddress, и это правда, я лично написал бы для него интеграционный тест, чтобы убедиться, что с базой данных, с которой я работаю, все в порядке, что она понимает мою SQL и то, что две сущности (DAO и база данных) могут общаться друг с другом, но я не буду беспокоить юнит- тестирование.

В заключение, модульные тесты должны быть значимыми, и хорошее определение «значимых» состоит в том, что тестируемый объект должен содержать некоторую независимую логику.

Ссылка: Что следует тестировать? — Методы тестирования 3 от нашего партнера по JCG в блоге Captain Debug

Статьи по Теме :