Статьи

Модульное тестирование с использованием макетов — Методы тестирования 5

Мой последний блог был четвертым в серии блогов, посвященных подходам к тестированию кода, и демонстрировал, как создать модульный тест, который изолирует тестируемый объект с помощью объекта-заглушки. Сегодняшний блог рассматривает то, что иногда рассматривается как противоположный метод: модульное тестирование с фиктивными объектами Опять же, я использую мой простой сценарий получения адреса из базы данных:

… И тестирование класса AddressService :

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
@Component
public class AddressService {
 
  private static final Logger logger = LoggerFactory.getLogger(AddressService.class);
 
  private AddressDao addressDao;
 
  /**
   * Given an id, retrieve an address. Apply phony business rules.
   *
   * @param id
   *            The id of the address object.
   */
  public Address findAddress(int id) {
 
    logger.info("In Address Service with id: " + id);
    Address address = addressDao.findAddress(id);
 
    address = businessMethod(address);
 
    logger.info("Leaving Address Service with id: " + id);
    return address;
  }
 
  private Address businessMethod(Address address) {
 
    logger.info("in business method");
 
    // Apply the Special Case Pattern (See MartinFowler.com)
    if (isNull(address)) {
      address = Address.INVALID_ADDRESS;
    }
 
    // Do some jiggery-pokery here....
 
    return address;
  }
 
  private boolean isNull(Object obj) {
    return obj == null;
  }
 
  @Autowired
  @Qualifier("addressDao")
  void setAddressDao(AddressDao addressDao) {
    this.addressDao = addressDao;
  }
}

… Заменив объект доступа к данным фиктивным объектом.

Прежде чем продолжить, было бы неплохо определить, что такое фиктивный объект и чем он отличается от заглушки. Если вы прочтете мой последний блог, вы вспомните, что я позволил Мартину Фаулеру определить объект-заглушку как:

«Заглушки обеспечивают постоянные ответы на вызовы, сделанные во время теста, обычно вообще не реагируя ни на что, кроме того, что запрограммировано для теста».

… который взят из его эссе » Насмешки — это не окурки» .

Итак, чем макет отличается от заглушек? Когда вы слышите, как люди говорят о поддельных объектах, они часто упоминают, что они насмехаются над поведением или насмешливыми ролями , но что это значит? Ответ заключается в том, как юнит-тест и фиктивный объект работают вместе, чтобы протестировать ваш объект. Сценарий фиктивного объекта выглядит следующим образом:

  1. Макет объекта определяется в тесте.
  2. Поддельный объект вводится в тестируемый объект
  3. В тесте указывается, какие методы для фиктивного объекта будут вызываться, а также аргументы и возвращаемые значения. Это известно как « установка ожиданий ».
  4. Затем тест выполняется.
  5. Затем тест просит макет проверить, что все вызовы методов, указанные на третьем шаге, были вызваны правильно. Если они были тогда, тест проходит. Если это не так, то тест не пройден.

Следовательно, поддельное поведение или поддельные роли действительно означают проверку того, что тестируемый объект правильно вызывает методы для ложного объекта, и провал теста, если это не так; следовательно, вы утверждаете на правильность вызовов методов и пути выполнения через ваш код, а не в случае обычного модульного теста возвращаемое значение тестируемого метода.

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

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
public class HomeMadeMockDao implements AddressDao {
 
  /** The return value for the findAddress method */
  private Address expectedReturn;
 
  /** The expected arg value for the findAddress method */
  private int expectedId;
 
  /** The actual arg value passed in when the test runs */
  private int actualId;
 
  /** used to verify that the findAddress method has been called */
  private boolean called;
 
  /**
   * Set and expectation: the return value for the findAddress method
   */
  public void setExpectationReturnValue(Address expectedReturn) {
    this.expectedReturn = expectedReturn;
  }
 
  public void setExpectationInputArg(int expectedId) {
    this.expectedId = expectedId;
  }
 
  /**
   * Verify that the expectations have been met
   */
  public void verify() {
 
    assertTrue(called);
    assertEquals("Invalid arg. Expected: " + expectedId + " actual: " + expectedId, expectedId, actualId);
  }
 
  /**
   * The mock method - this is what we're mocking.
   *
   * @see com.captaindebug.address.AddressDao#findAddress(int)
   */
  @Override
  public Address findAddress(int id) {
 
    called = true;
    actualId = id;
    return expectedReturn;
  }
}

Код модульного теста, который поддерживает этот макет:

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
public class MockingAddressServiceWithHomeMadeMockTest {
 
  /** The object to test */
  private AddressService instance;
 
  /**
   * We've written a mock,,,
   */
  private HomeMadeMockDao mockDao;
 
  @Before
  public void setUp() throws Exception {
    /* Create the object to test and the mock */
    instance = new AddressService();
    mockDao = new HomeMadeMockDao();
    /* Inject the mock dependency */
    instance.setAddressDao(mockDao);
  }
 
  /**
   * Test method for
   * {@link com.captaindebug.address.AddressService#findAddress(int)}.
   */
  @Test
  public void testFindAddressWithEasyMock() {
 
    /* Setup the test data - stuff that's specific to this test */
    final int id = 1;
    Address expectedAddress = new Address(id, "15 My Street", "My Town", "POSTCODE", "My Country");
 
    /* Set the Mock Expectations */
    mockDao.setExpectationInputArg(id);
    mockDao.setExpectationReturnValue(expectedAddress);
 
    /* Run the test */
    instance.findAddress(id);
 
    /* Verify that the mock's expectations were met */
    mockDao.verify();
  }
}

Хорошо, хотя это демонстрирует шаги, необходимые для выполнения модульного теста с использованием фиктивного объекта, он довольно грубый и готов и очень специфичен для сценария AddressDao / AddressService . Чтобы доказать, что это уже сделано лучше, в следующем примере используется easyMock в качестве фреймворка. Код модульного теста в этом более профессиональном случае:

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
@RunWith(UnitilsJUnit4TestClassRunner.class)
public class MockingAddressServiceWithEasyMockTest {
 
  /** The object to test */
  private AddressService instance;
 
  /**
   * EasyMock creates the mock object
   */
  @Mock
  private AddressDao mockDao;
 
  /**
   * @throws java.lang.Exception
   */
  @Before
  public void setUp() throws Exception {
    /* Create the object to test */
    instance = new AddressService();
  }
 
  /**
   * Test method for
   * {@link com.captaindebug.address.AddressService#findAddress(int)}.
   */
  @Test
  public void testFindAddressWithEasyMock() {
 
    /* Inject the mock dependency */
    instance.setAddressDao(mockDao);
    /* Setup the test data - stuff that's specific to this test */
    final int id = 1;
    Address expectedAddress = new Address(id, "15 My Street", "My Town", "POSTCODE", "My Country");
    /* Set the expectations */
    expect(mockDao.findAddress(id)).andReturn(expectedAddress);
    replay();
 
    /* Run the test */
    instance.findAddress(id);
 
    /* Verify that the mock's expectations were met */
    verify();
  }
}

… что, я надеюсь, вы согласитесь, более прогрессивно, чем моя быстрая попытка написания насмешки.

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

С другой стороны, если вы знаете, как использовать такую ​​среду, как easyMock, создание модульных тестов, изолирующих тестируемый объект, может быть выполнено очень быстро и эффективно.

Самокритируя этот пример кода, я хотел бы отметить, что я считаю, что использование фиктивного объекта в этом сценарии является излишним, плюс вы также можете легко утверждать, что я использую макет в качестве заглушки.

Несколько лет назад, когда я впервые столкнулся с easyMock, я везде использовал mock, но недавно я стал отдавать предпочтение написанию заглушек вручную для классов границ приложений, таких как DAO, и объектов, которые просто возвращают данные. Это связано с тем, что тесты на основе заглушек, возможно, намного менее хрупки, чем тесты на основе имитаций, особенно когда все, что вам нужно, — это доступ к данным.

Зачем использовать издевательства? Хорошо показывает, что тестирует приложение, написанное с использованием метода « скажи, не спрашивай », чтобы убедиться, что вызывается метод с возвращением void.

Ссылка: модульное тестирование с использованием Mocks — Методы тестирования 5 от нашего партнера по JCG из блога Captain Debug

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