Статьи

Модульное тестирование с TestNG и JMockit

TestNG — это среда тестирования для разработки модульных тестов. JMockit — это фреймворк для фиктивных объектов, который обеспечивает функциональность фиктивных объектов с помощью пакета java.lang.instrument из jdk 1.5. Вместе эти платформы могут предоставить инструменты для создания очень надежных тестовых случаев без ограничений дизайна других доступных в настоящее время структур тестирования.

В первой части этого учебного пособия, состоящего из двух частей, мы расскажем о создании тестового примера и реализации связанного класса для тестирования. Мы будем использовать тот же сценарий, что и в моем уроке: модульное тестирование с JUnit и EasyMock . Если вы новичок в модульном тестировании в целом, я предлагаю вам также изучить руководство по JUnit, так как оно содержит раздел о модульном тестировании в целом. Во второй части мы рассмотрим сценарии, в которых используются среды TestNG и jmockit, в том числе тестирование с использованием макетов без внедрения и организация тестов по группам.

Для начала завершите мое модульное тестирование с помощью учебника по JUnit и EasyMock . Затем запустите JNnit-конвертер TestNG. Вот и все. Учебник окончен. Шучу (хотя у TestNG есть конвертер, который вы можете запустить для преобразования существующих тестов JUnit в тесты TestNG).

Настроить

Хотя TestNG предоставляет инструмент преобразования JUnit, этот подход мы не будем использовать здесь. Итак, начнем. Я буду использовать Eclipse 3.3 Europa для этого урока.

  • Для начала создайте новый проект Java и назовите его TestNGTutorial .
  • Щелкните правой кнопкой мыши по вашему новому проекту и выберите New -> Folder . Назовите его lib и нажмите Finish.
  • Обычно вы не хотите упаковывать свой тестовый код в обычный код, поэтому давайте создадим дополнительный исходный каталог test . Для этого щелкните правой кнопкой мыши ваш новый проект и выберите « Исходная папка» .
  • Далее нам нужно добавить TestNG в наш путь сборки. Чтобы сделать нашу жизнь проще и поскольку мы серьезно относимся к модульному тестированию;) мы будем использовать плагин TestNG для Eclipse. Он включает в себя тестовый прогон и другую функциональность, которая облегчит разработку тестов. Чтобы установить плагин, в Eclipse перейдите в Справка -> Обновления программного обеспечения -> Найти и установить …
    Оттуда выберите «Поиск новой функции для установки» и нажмите «Далее>». В правом верхнем углу нажмите «Новый удаленный сайт». Введите « Плагин TestNG » в качестве имени и http://beust.com/eclipseв качестве URL и нажмите кнопку ОК. Убедитесь, что TestNG — единственная вещь, рядом с которой стоит галочка, и нажмите «Готово». В появившемся новом окне установите флажок рядом с testng и нажмите Далее>. На следующем экране примите лицензионное соглашение и нажмите Далее>. Нажмите Готово.
  • После завершения загрузки Eclipse спросит вас, что установить. Нажмите Установить все. После завершения установки перезапустите Eclipse.
  • Когда Eclipse вернется, щелкните правой кнопкой мыши ваш проект и выберите Свойства. Выберите Java Build Path слева и нажмите на вкладку Библиотеки. Справа нажмите Добавить переменную и выберите TESTNG_HOME. Нажмите кнопку «Расширить …» справа и выберите /lib/testng-jdk15.jar и нажмите «ОК». Нажмите кнопку ОК, чтобы закрыть окно свойств.

Теперь нам нужно получить jmockit. Это макет фреймворка, который мы будем использовать для этого урока. Чтобы скачать jmockit, перейдите сюда и загрузите релиз JMockit 0.94c. После завершения загрузки извлеките файл jmockit.jar из архива и поместите его в папку lib, которую мы создали ранее.

Чтобы добавить новую банку в путь к классам, щелкните правой кнопкой мыши свой проект и выберите «Свойства». Перейдите в Java Build Path и выберите вкладку Libraries. Оттуда нажмите Add JARs …. Выберите jmockit jar, который мы только что скопировали в каталог lib, и нажмите Ok. Нажмите Ok, чтобы выйти из окна свойств. Теперь вы готовы к коду!

Сценарий

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

Диаграмма классов TestNG

Интерфейсы

Как было сказано ранее, мы будем тестировать тот же сценарий, что и в предыдущем уроке. Для обзора мы начнем наше кодирование с определения двух интерфейсов: LoginService и UserDAO. Мы реализуем LoginService, однако, поскольку в этом уроке UserDAO будет издеваться над нами, мы не будем сейчас его реализовывать. Для LoginService у нас есть единственный метод, который принимает String userName и String пароль и возвращает логическое значение (true, если пользователь был найден, false, если это не так). Интерфейс выглядит так:

/**
* Provides authenticated related processing.
*/
public interface LoginService {

/**
* Handles a request to login. Passwords are stored as an MD5 Hash in
* this system. The login service creates a hash based on the paramters
* received and looks up the user. If a user with the same userName and
* password hash are found, true is returned, else false is returned.
*
* @parameter userName
* @parameter password
* @return boolean
*/
boolean login(String userName, String password);
}

Интерфейс UserDAO будет очень похож на LoginService. У него будет единственный метод, который принимает userName и хэш. Хеш — это хешированная версия пароля MD5, предоставляемая вышеуказанным сервисом.

/**
* Provides database access for login related functions
*/
public interface UserDAO {

/**
* Loads a User object for the record that
* is returned with the same userName and password.
*
* @parameter userName
* @parameter password
* @return User
*/
User loadByUsernameAndPassword(String userName, String password);
}

TestNG

Прежде чем мы начнем разработку, мы разработаем наш тест. Тесты структурированы путем группировки методов, которые выполняют тестирование вместе в тестовом примере. TestNG хорошо поддерживает стили разработки POJO, не требуя расширения какого-либо класса или реализации какого-либо интерфейса. Жизненный цикл теста направляется через аннотации (в этом руководстве мы рассмотрим стандартные аннотации JDK 1.5+, TestNG также поддерживает JDK 1.4. Для него требуется дополнительный файл JAR и используются аннотации в стиле XDoclet). Аннотации, которые мы рассмотрим в этом уроке:

  • @Before*/ @After*
    Ряд аннотаций «до» и «после» указывает исполнителю теста выполнить аннотированный метод либо до, либо после указанного теста, группы, метода, набора или класса.
  • @Test
    @TestАннотации указывает бегун , что метод является метод испытания.

Метод, который мы будем тестировать, это loginметод на LoginService. Поскольку этот метод зависит от UserDAO, нам нужно создать для него макет. В этом уроке мы будем использовать фреймворк jmockit. Прежде чем мы начнем писать какой-либо тестовый код, давайте рассмотрим некоторые важные моменты, касающиеся инфраструктуры jmockit.

JMockit

Большинство фальшивых фреймворков (JMock, EasyMock и т. Д.) Основаны на java.lang.Proxyобъекте. Они создают прокси-объект, основанный на интерфейсе, который вы предоставляете для инъекции в тестируемый объект. Разработчик сообщает прокси-серверу, что ожидать и что возвращать, когда ожидание оправдано. Есть две проблемы с этим:

  1. Требуется интерфейс. Не поймите меня неправильно, программирование интерфейсов — это хорошо, но не все требует интерфейса. У EasyMock есть расширение, которое обрабатывает классы без интерфейса, но может быть затруднительно иметь два разных EasyMockобъекта, включенных в ваш тестовый класс, чтобы делать в основном одно и то же.
  2. Требуется введение зависимости какого-либо вида. Не поймите меня неправильно, я люблю Spring и использую его в своем текущем проекте. Но что, если вы не хотите, чтобы Spring управлял всем ? Вы не можете смоделировать объект, созданный в вашем методе. Единственный способ использовать фиктивные объекты — это управлять экземпляром объекта в другом месте (обычно на фабрике).

jmockit решает эти две проблемы, используя возможность переопределения. Если вы не знакомы с новой возможностью переопределения классов в JDK 1.5+, то, по сути, она позволяет программно сказать JVM переназначить класс в другой файл классов. Поэтому, когда вы создаете макет с помощью jmockit, вы создаете новый класс, которым JVM заменит зависимый класс.

Контрольный пример

Итак, теперь, когда мы немного знаем о TestNG и jmockit, давайте начнем разбирать наш тестовый пример. Для начала, создайте пустой тестовый набор TestNG с соответствующим импортом:

import org.testng.annotations.*;

public class LoginServiceTest {

}

Обратите внимание, что класс не расширяет какой-либо класс и не реализует интерфейс. Это потому, что TestNG не требует этого. Мы будем диктовать все этапы жизненного цикла с помощью аннотаций. Как мы уже отмечали ранее, loginметод зависит от UserDAO. Поэтому давайте создадим setupMocksметод, который будет запускаться перед каждым тестом. Этот setupMocksметод создаст все необходимые макеты, которые нам нужны, и соответственно добавит их.

import mockit.Expectations;

import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

public class LoginServiceTest extends Expectations {

private LoginServiceImpl service;
UserDAO mockDao;

@BeforeTest
public void setupMocks() {
service = new LoginServiceImpl();
service.setUserDao( mockDao );
}
}

Давайте рассмотрим, что у нас в вышеприведенном классе. У нас есть два необходимых импорта для этого тестового примера mockit.Mockit(основная утилита для jmockit) и org.testng.annotations.*для импорта необходимых аннотаций TestNG, которые мы будем использовать. Мы объявили два объекта, которые нас интересуют: тестируемый сервис и фиктивный DAO для инъекций. Обратите внимание, что LoginService является частной, а UserDAO — нет. jmockit автоматически создаст фиктивный объект для mockDao для любых не приватных полей.

Далее у нас есть наш setupMocksметод. Это @BeforeTestаннотация TestNG . Это говорит TestNG запускать этот метод один раз перед каждым тестом. Это даст нам свежий экземпляр и пробную версию для тестирования, так что нам не придется заниматься какой-либо очисткой, которая может потребоваться. В нашем setupMocksметоде мы создаем фиктивный DAO, вызывая Mockit.setUpMock()метод jmockit и передавая ему наш интерфейс. Затем мы создаем экземпляр нашего сервиса для тестирования и, наконец, внедряем фиктивный объект в нашу реализацию. Если это выглядит как EasyMock, так и должно быть. Использование jmockit и EasyMock очень похоже на использование интерфейсов. Когда вы используете классы, использование отличается.

Следующим шагом является создание нашего фактического метода тестирования. В нашем методе тестирования мы хотим протестировать следующий сценарий:

Диаграмма последовательности TestNG

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

...
/**
* This method will test the "rosy" scenario of passing a valid
* username and password and retrieveing the user. Once the user
* is returned to the service, the service will return true to
* the caller.
*/
@Test
public void testRosyScenario() {
User results = new User();
String userName = "testUserName";
String password = "testPassword";
String passwordHash =
"�Ӷ&I7���Ni=.";

invokeReturning(
mockDao.loadByUsernameAndPassword( userName,
passwordHash ),
results );
endRecording();

assert service.login( userName, password ) :
"Expected true, but was false";
}
...

Итак, давайте рассмотрим код выше. Мы начинаем с нашей аннотации @Test. Это говорит TestNG, что это метод тестирования. Оказавшись в методе, мы создаем ожидаемый результат нашего вызова DAO, результаты. В этом случае наш метод просто проверит, был ли возвращен объект, поэтому нам не нужно заполнять наш пользовательский объект чем-либо, нам просто нужен пустой экземпляр. Затем мы объявляем значения, которые будем передавать в наш сервисный вызов. Хэш пароля может застать вас врасплох. Хранить пароли в виде простого текста считается небезопасным, поэтому наш сервис сгенерирует хеш-пароль MD5, и это значение будет значением, которое мы передадим в наш DAO.

После объявления различных переменных, следующий вызов:

invokeReturning( 
mockDao.loadByUsernameAndPassword( userName, passwordHash ),
results );

сообщает jmockit, что метод loadByUsernameAndPasswordдолжен вызываться со значениями, ранее объявленными в userNameи passwordHash. Когда это произойдет, верните значение results. Следующая строка:

endRecording();

говорит jmockit, что мы закончили записывать ожидания для этого теста. В этот момент jmockit автоматически переходит в режим воспроизведения. Наконец, мы выполняем фактический «тест», выполняя тестируемый метод loginи утверждая, что это правда. Если это не так, мы представляем сообщение. TestNG рассматривает прохождение теста, если не выдается никаких исключений. Если исключение выдается из метода, который объявлен тестом, тест завершается неудачей.

Для запуска нашего теста нам нужно настроить еще одну вещь. Нам нужно сообщить JVM, что у нас будет запущен агент Java. Для этого щелкните правой кнопкой мыши LoginServiceTest, выберите «Запуск от имени» -> «Открыть диалоговое окно выполнения». Слева выберите TestNG. Вверху нажмите на новый значок. В новой области справа введите LoginServiceTest в поле Class. Нажмите вкладку Аргументы в верхней части. В поле VM Arguments введите следующее (где
это полный путь к папке lib вашего проекта):

-javaagent:
 /jmockit.jar

Осталось только нажать Run. Какая? Тест не удался ?! Это потому, что мы еще не реализовали метод, который тестируем. Код для этого ниже.

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;


public class LoginServiceImpl implements LoginService {
UserDAO userDao;

public void setUserDao(UserDAO userDao) {
this.userDao = userDao;
}

public boolean login(String userName, String password) {
boolean valid = false;
try {
String passwordHash = null;
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(password.getBytes());
passwordHash = new String(md5.digest());

User results =
userDao.loadByUsernameAndPassword(userName, passwordHash);
if(results != null) {
valid = true;
}
} catch (NoSuchAlgorithmException ignore) {}

return valid;
}
}

Вывод

Как видите, TestNG предоставляет надежный набор функций, который не чужд кому-то, кто привык к стилям юнит-тестов JUnit. Во второй части этого руководства мы проведем рефакторинг LoginService, чтобы лучше использовать инфраструктуру jmockit и создадим дополнительные тесты, которые мы можем организовать с помощью TestNG. До скорого!

Первоначально опубликовано на http://www.michaelminella.com/testing/unit-testing-with-testng-and-jmockit.html.