Одна из причин, по которой он использовал это, заключалась в том, что некоторые тесты кажутся бессмысленными, что подводит меня к вопросу о том, что именно вы делаете, и что вам не нужно беспокоиться.
Рассмотрим простой неизменяемый бин 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
Статьи по Теме :
- Методы тестирования — не писать тесты
- Неправильное использование сквозных тестов — Методы тестирования 2
- Регулярные юнит-тесты и заглушки — Методы испытаний 4
- Модульное тестирование с использованием макетов — Методы тестирования 5
- Создание заглушек для устаревшего кода — методы тестирования 6
- Подробнее о создании заглушек для устаревшего кода — Методы тестирования 7
- Почему вы должны писать модульные тесты — методы тестирования 8
- Некоторые определения — методы тестирования 9
- Использование FindBugs для создания значительно меньшего количества ошибочного кода
- Разработка и тестирование в облаке