Тестирование. Я много думал о тестировании в последнее время. Как часть обзоров кода, которые я сделал для различных проектов, я видел тысячи строк непроверенного кода. Это не просто случай статистики охвата тестами, указывающий на это, это скорее случай отсутствия каких-либо тестов в этих проектах. И две причины, по которым я продолжаю слышать об этом печальном положении вещей? «У нас нет времени», за которым быстро следует «Мы сделаем это, когда закончим код».
То, что я представляю здесь, не является панацеей для тестирования. Он охватывает модульное тестирование и, в частности, модульное тестирование интерфейсов. Интерфейсы хорошие вещи. Интерфейсы определяют контракты. Интерфейсы, независимо от того, сколько у них реализаций, могут быть протестированы легко и без особых усилий. Давайте посмотрим, как, используя эту структуру класса в качестве примера.

CustomerService — это наш интерфейс. Он имеет два метода, чтобы сохранить пример простым, и описан ниже. Обратите внимание на javadoc — это то место, где описан контракт.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
public interface CustomerService{ /** * Retrieve the customer from somewhere. * @param userName the userName of the customer * @return a non-null Customer instance compliant with the userName * @throws CustomerNotFoundException if a customer with the given user name can not be found */ Customer get(String userName) throws CustomerNotFoundException; /** * Persist the customer. * @param customer the customer to persist * @return the customer as it now exists in its persisted form * @throws DuplicateCustomerException if a customer with the user name already exists */ Customer create(Customer customer) throws DuplicateCustomerException;} |
Как видно из диаграммы, у нас есть две реализации этого класса, RemoteCustomerService и CachingCustomerService. Их реализации не показаны, потому что они не имеют значения. Как я могу это сказать? Все просто — мы тестируем контракт. Мы пишем тесты для каждого метода в интерфейсе вместе с каждой перестановкой контракта. Например, для get () нам нужно проверить, что происходит, когда присутствует клиент с данным именем пользователя, и что происходит, когда его нет.
|
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
|
public abstract class CustomerServiceTest{ @Test public void testCreate() { CustomerService customerService = getCustomerService(); Customer customer = customerService.create(new Customer("userNameA")); Assert.assertNotNull(customer); Assert.assertEquals("userNameA", customer.getUserName()); } @Test(expected = DuplicateCustomerException.class) public void testCreate_duplicate() { CustomerService customerService = getCustomerService(); Customer customer = new Customer("userNameA"); customerService.create(customer); customerService.create(customer); } @Test public void testGet() { CustomerService customerService = getCustomerService(); customerService.create(new Customer("userNameA")); Customer customer = customerService.get("userNameA"); Assert.assertNotNull(customer); Assert.assertEquals("userNameA", result.getUserName()); } @Test(expected = CustomerNotFoundException.class) public void testGet_noUser() { CustomerService customerService = getCustomerService(); customerService.get("userNameA"); } public abstract CustomerService getCustomerService();} |
Теперь у нас есть тест для контракта, и мы ни разу не упомянули ни одну из реализаций. Это означает две вещи:
- Нам не нужно дублировать тесты для каждой реализации. Это очень хорошая вещь.
- Ни одна из реализаций не тестируется. Мы можем исправить это, добавив один тестовый класс на реализацию. Поскольку каждый тестовый класс будет практически идентичен, я просто покажу тест RemoteCustomerService.
|
1
2
3
4
5
6
7
|
public class RemoteCustomerServiceTest extends CustomerServiceTest{ public CustomerService getCustomerService() { return new RemoteCustomerService(); }} |
Вот и все! Теперь у нас есть очень простой способ протестировать несколько реализаций любого интерфейса, включив тяжелую работу заранее и сократив усилия по тестированию новых реализаций до одного простого метода.