Мой последний блог был третьим в серии блогов, посвященных подходам к тестированию кода и обсуждению того, что вы делаете и не должны тестировать. Он основан на моем простом сценарии получения адреса из базы данных с использованием очень распространенного шаблона:
… и я высказал идею, что любой класс, который не содержит никакой логики, на самом деле не нуждается в модульном тестировании. В это я включил свой объект доступа к данным, DAO, и вместо этого предпочел интеграционное тестирование этого класса, чтобы убедиться, что он работает в сотрудничестве с базой данных.
Сегодняшний блог посвящен написанию обычного или классического модульного теста, который обеспечивает изоляцию объекта тестирования с использованием объектов-заглушек. Код, который мы будем тестировать, опять же, AddressService:
@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;
  }
}
 В книге Майкла Фезера «Эффективная работа с устаревшим кодом» говорится, что тест не является модульным тестом, если:
- Это говорит с базой данных.
 - Он общается через сеть.
 - Это касается файловой системы.
 - Вы должны сделать специальные вещи в вашей среде (например, редактирование файлов конфигурации), чтобы запустить его.
 
Чтобы соблюдать эти правила, вам необходимо изолировать тестируемый объект от остальной части вашей системы, и именно здесь вступают объекты-заглушки. Объекты-заглушки — это объекты, которые вводятся в ваш объект и используются для замены реальных объектов в тестовых ситуациях. Мартин Фаулер определяет заглушки в своем эссе
 : «Заглушки не заглушки» :
 
«Заглушки обеспечивают постоянные ответы на вызовы, сделанные во время теста, обычно вообще не реагируя ни на что, кроме того, что запрограммировано для теста. Заглушки могут также записывать информацию о вызовах, такую как заглушка шлюза электронной почты, которая запоминает сообщения, которые она «отправила», или, возможно, только сколько сообщений она «отправила» ».
Выбрать слово для описания заглушек очень сложно, я мог бы выбрать
пустышку или
подделку , но есть типы заменяющих объектов, которые известны как пустышки или подделки — также описанные Мартином Фаулером:
- Пустые объекты передаются, но никогда не используются. Обычно они просто используются для заполнения списков параметров.
 - Ложные объекты на самом деле имеют рабочие реализации, но обычно используют некоторые ярлыки, которые делают их непригодными для производства (хороший пример — база данных в памяти).
 
Тем не менее, я видел другие определения термина
  поддельный объект, например, Рой Ошеров в своей книге «Искусство модульного
  тестирования» определяет поддельный объект как:
  
- Подделка — это общий термин, который можно использовать для описания заглушки или фиктивного объекта … потому что оба выглядят как реальный объект.
 
… поэтому я, как и многие другие, склонен называть все заменяющие объекты либо
  фиктивными, либо
  заглушками, поскольку между ними есть разница, но об этом позже.
 
 
 При тестировании AddressService нам нужно заменить реальный объект доступа к данным на объект-заглушку, и в этом случае он выглядит примерно так:
public class StubAddressDao implements AddressDao {
  private final Address address;
  public StubAddressDao(Address address) {
    this.address = address;
  }
  /**
   * @see com.captaindebug.address.AddressDao#findAddress(int)
   */
  @Override
  public Address findAddress(int id) {
    return address;
  }
}
Обратите внимание на простоту кода заглушки. Он должен быть легко читаемым, обслуживаемым, НЕ содержать никакой логики и нуждаться в самостоятельном модульном тестировании. После того, как код заглушки был написан, затем следует модульный тест:
 
public class ClassicAddressServiceWithStubTest {
  private AddressService instance;
  @Before
  public void setUp() throws Exception {
    /* Create the object to test */
    /* Setup data that's used by ALL tests in this class */
    instance = new AddressService();
  }
  /**
   * Test method for
   * {@link com.captaindebug.address.AddressService#findAddress(int)}.
   */
  @Test
  public void testFindAddressWithStub() {
    /* Setup the test data - stuff that's specific to this test */
    Address expectedAddress = new Address(1, "15 My Street", "My Town",
        "POSTCODE", "My Country");
    instance.setAddressDao(new StubAddressDao(expectedAddress));
    /* Run the test */
    Address result = instance.findAddress(1);
    /* Assert the results */
    assertEquals(expectedAddress.getId(), result.getId());
    assertEquals(expectedAddress.getStreet(), result.getStreet());
    assertEquals(expectedAddress.getTown(), result.getTown());
    assertEquals(expectedAddress.getPostCode(), result.getPostCode());
    assertEquals(expectedAddress.getCountry(), result.getCountry());
  }
  @After
  public void tearDown() {
    /*
     * Clear up to ensure all tests in the class are isolated from each
     * other.
     */
  }
}
Обратите внимание, что при написании модульного теста мы стремимся к ясности. Часто допускаемая ошибка заключается в том, что тестовый код уступает производственному коду, в результате чего он часто более грязный и неразборчивый. Рой Ошеров в «Искусстве модульного
 тестирования» выдвигает идею, что тестовый код должен быть более читабельным, чем производственный код. Четкие тесты должны следовать следующим основным линейным шагам:
 
- Создайте тестируемый объект. В приведенном выше коде это делается в методе setUp (), поскольку я использую один и тот же тестируемый объект для всех (одного) тестов.
 - Настройте тест. Это делается в тестовом методе testFindAddressWithStub (), поскольку данные, используемые в тесте, специфичны для этого теста.
 - Запустить тест
 - Снеси тест. Это гарантирует, что тесты изолированы друг от друга и могут быть запущены в любом порядке.
 
Использование упрощенной заглушки дает два преимущества изоляции AddressService от внешнего мира и быстрых тестов.
Насколько хрупок этот вид теста? Если ваши требования меняются, то тест и заглушка меняются — не так ли все-таки хрупко?
Для сравнения, мой следующий блог переписывает этот тест с использованием EasyMock.
1 Исходный код доступен на GitHub по адресу:
git: //github.com/roghughe/captaindebug.git
С http://www.captaindebug.com/2011/11/regular-unit-tests-and-stubs-testing.html
