Лекция 23 Тестирование. Пакет JUnit Сегодня все большую популярность приобретает test-driven development(TDD), техника разработки ПО, при которой сначала.

Презентация:



Advertisements
Похожие презентации
Software engineering Дмитриев Андрей Владиславович ©
Advertisements

Test 6 Вопрос 1. Как можно уничтожить объект в Java? a)присвоить null всем ссылкам на объект b)вызвать Runtime.getRuntime().gc() c)вызвать метод finalize()
Saint Petersburg, 2011 Java Lecture #06 Exceptions.
Перегрузка операторов x = a + b результат 1-й операнд2-й операнд оператор По количеству операндов операторы делятся на: унарные (один операнд) бинарные.
Java. Part 2. Спецификаторы доступа public private protected не указан – доступ в пределах пакета Могут использоваться перед классами, методами, полями.
1 Обработка исключений в Java Одно из важнейших преимуществ Java – разработанный на уровне языка механизм обработки исключений. Исключение в Java - это.
Изучение динамического определения типов. Класс Class. Динамическая загрузка и инстанцирование классов. В Java вся информация о классе хранится в специальном.
Наследование Наследование – это отношение является между классами. class Person { string first_name; int birth_year;... } class Student : Person { float.
Синтаксис языка Java. Символы и синтаксис Перевод строчки эквивалентен пробелу Регистр в именах различается.
Обработка исключительных ситуаций Исключительная ситуация (исключение) – это ошибка, возникающая во время выполнения программы. Например, ошибка работы.
EXtreme Programming XP Тема 3. XP Пусть есть некоторая информационная система для банков. В качестве основной валюты для расчетов используется доллар,
Гайдар Магдануров Microsoft.
Программная инженерия Дмитриев Андрей Владиславович
САОД кафедра ОСУ 1 Основные абстрактные типы данных Схема процесса создания программ для решения прикладных задач ВУ.
Гайдар Магдануров Microsoft.
b5_java_s4
Исключения в Java. Исключения – это механизм взаимодействия между кодом, приведшим к ошибке, и кодом, обрабатывающим ошибку Исключение выбрасывается (throw),
Ассоциативные списки Поиск данных происходит не по индексу или положению объекта, а по его ассоциативной связи: public interface Map { // Доступ к объектам.
Высокоуровневые методы информатики и программирования Лекция 10 События.
Лекция 2 Наследование Наследование в Java имеет тот же смысл, что и в С++. Однако наследование в Java осуществляется при помощи ключевого слова extends.
Транксрипт:

Лекция 23

Тестирование. Пакет JUnit Сегодня все большую популярность приобретает test-driven development(TDD), техника разработки ПО, при которой сначала пишется тест на определенный функционал, а затем пишется реализация этого функционала. В результате код не только написан и протестирован, но тесты как бы неявно задают требования к функционалу, а также показывают пример использования этого функционала.

Самым известным фреймворком является JUnit. Используется он в двух вариантах JUnit 3 и JUnit 4 JUnit 3 Для создания теста нужно унаследовать тест-класс от TestCase, переопределить методы setUp и tearDown если надо, а также создать тестовые методы(должны начинаться с test). При запуске теста сначала создается экземпляр тест- класса(для каждого теста в классе отдельный экземпляр класса), затем выполняется метод setUp, запускается сам тест, ну и в завершение выполняется метод tearDown. Если какой-либо из методов выбрасывает исключение, тест считается провалившимся. Тестовые методы должны быть public void, могут быть static.

Сами тесты состоят из выполнения некоторого кода и проверок. Проверки чаще всего выполняются с помощью класса Assert хотя иногда используют ключевое слово assert. Рассмотрим пример. Пусть имеется класс, который имеет два метода - нахождение факториала и суммы двух чисел: public class MathFunc { private int variable; public MathFunc() { variable = 0; } public MathFunc(int var) { variable = var; }

public int getVariable() { return variable; } public void setVariable (int variable){ this.variable = variable; } public long factorial() { long result = 1; if (variable > 1) { for (int i=1; i

Напишем для этого класса тесты, используя JUnit 3. Удобнее всего, писать тесты, рассматривая некий класс как черный ящик, писать отдельный тест на каждый значимый метод в этом классе, для каждого набора входных параметров какой-то ожидаемый результат. Для написания тестового класса нужно создать наследника junit.framework.TestCase. Затем необходимо определить конструктор, принимающий в качестве параметра строку (String) и передающую ее родительскому классу.

import junit.framework.*; public class TestClass extends TestCase { public TestClass(String testName) { super(testName); } public void testFactorialNull() { MathFunc math = new MathFunc(); assertTrue(math.factorial() == 1); } public void testFactorialPositive() { MathFunc math = new MathFunc(5); assertTrue(math.factorial() == 120); } public void testPlus() { MathFunc math = new MathFunc(45); assertTrue(math.plus(123) == 168); } }

Метод assertTrue проверяет, является ли результат выражения верным. Некоторые другие методы, которые могут пригодиться - assertEquals, assertFalse, assertNull, assertNotNull, assertSame. Для того, чтобы объединить тесты, можно воспользоваться классом TestSuite с его методом addTest. Наконец, для запуска всех тестов нужно воспользоваться TestRunner. Можно использовать текстовый junit.textui.TestRunner (есть также графические версии - junit.swingui.TestRunner, junit.awtui.TestRunner). В итоге метод main имеет вид:

import junit.framework.*; import junit.textui.*; public class Main{ public static void main(String[] args) { TestRunner runner = new TestRunner(); TestSuite suite = new TestSuite(); suite.addTest(new TestClass(testFactorialNull)); suite.addTest( new TestClass(testFactorialPositive)); suite.addTest(new TestClass(testPlus)); runner.doRun(suite); } }

На экране получим: Time: 0 Ok(3 tests) Для тестирования более комплексных классов, могут пригодится методы setUp и tearDown. Первый метод может проинициализировать один или несколько экземпляров тестируемого класса для использования в нескольких TestCases, второй метод отпускает захваченные при инициализации ресурсы.

Наследуя тест-класс от ExceptionTestCase, можно проверить что-либо на выброс исключения. JUnit 4 Здесь была добавлена поддержка новых возможностей из Java 5, тесты теперь могут быть объявлены с помощью аннотаций. При этом существует обратная совместимость с предыдущей версией фреймворка.

Основные аннотации обозначает методы, которые будут вызваны до исполнения теста, методы должны быть public void. Здесь обычно размещаются предустановки для теста. обозначает методы, которые будут вызваны до создания экземпляра тест-класса, методы должны быть public static void. Имеет смысл размещать предустановки для теста в случае, когда класс содержит несколько тестов использующих различные предустановки, либо когда несколько тестов используют одни и те же данные.

обозначает методы, которые будут вызваны после выполнения теста, методы должны быть public void. Здесь размещаются операции освобождения ресурсов после теста. связана по смыслу но выполняет методы после теста, как и в случае методы должны быть public static void.

обозначает тестовые методы. Как и ранее, эти методы должны быть public void. Здесь размещаются сами проверки. Кроме того, у данной аннотации есть два параметра, expected задает ожидаемое исключение и timeout задает время, по истечению которого тест считается провалившимся. = NullPointerException.class) public void testToHexStringWrong() { StringUtils.toHexString(null); = 1000) public void infinity() { while (true); }

Если какой-либо тест по какой-либо серьезной причине нужно отключить(например, этот тест постоянно валится, но его исправление отложено до светлого будущего) его можно Пример: = 1000) public void infinity() { while (true); }

Рассмотрим пример: public class StringUtilsJUnit4Test extends Assert { private final Map toHexStringData = new HashMap public static void setUpToHexStringData() { toHexStringData.put("", new byte[0]); toHexStringData.put("01020d112d7f", new byte[] { 1, 2, 13, 17, 45, 127 }); toHexStringData.put("00fff21180", new byte[] { 0, -1, -14, 17, -128 }); //... }

@After public static void tearDownToHexStringData() { toHexStringData.clear(); public void testToHexString() { for (Map.Entry entry : toHexStringData.entrySet()){ final byte[] testData = entry.getValue(); final String expected = entry.getKey(); final String actual = StringUtils.toHexString(testData); assertEquals(expected, actual); }

Запуск теста. Запуск теста можно сконфигурировать с При этом класс, указанный в аннотации должен наследоваться от Runner. Рассмотрим различные запускалки JUnit4 предназначен для запуска JUnit 4 тестов. JUnit38ClassRunner предназначен для запуска тестов, написанных с использованием JUnit 3.

Categories попытка организовать тесты в категории(группы). Для этого тестам задается категория с затем настраиваются запускаемые категории тестов. Это может выглядеть так: public class StringUtilsJUnit4CategoriesTest extends Assert public void testIsEmpty() { //... } //... }

@RunWith(Categories.class) OtherJUnit4Test.class, StringUtilsJUnit4CategoriesTest.class }) //настройка категории public class JUnit4TestSuite { } Parameterized позволяет писать параметризированные тесты. Для этого в тест-классе объявляется статический метод возвращающий список данных, которые затем будут использованы в качестве аргументов конструктора класса.

@RunWith(Parameterized.class) public class StringUtilsJUnit4ParameterizedTest extends Assert { private final CharSequence testData; private final boolean expected; public StringUtilsJUnit4ParameterizedTest( final CharSequence testData, final boolean expected ){ this.testData = testData; this.expected = expected; }

@Test public void testIsEmpty() { final boolean actual = StringUtils.isEmpty(testData); assertEquals(expected, actual); public static List isEmptyData() { return Arrays.asList(new Object[][] { { null, true }, { "", true }, { " ", false }, { "some string", false }, }); }

Theories чем-то схож с предыдущим, но параметризирует тестовый метод, а не конструктор. Данные помечаются с тестовый метод с Тест использующий этот функционал будет выглядеть примерно public class StringUtilsJUnit4TheoryTest extends Assert {

@DataPoints public static Object[][] isEmptyData = new Object[][] { { "", true }, { " ", false }, { "some string", false } public static Object[] nullData = new Object[] { null, true public void testEmpty(final Object... testData) { final boolean actual = StringUtils.isEmpty((CharSequence)testData[0]); assertEquals(testData[1], actual); } }

Рассмотрим пример. Класс MathFunc оставим без изменений. Класс TestClass имеет вид import org.junit.runner.*; import org.junit.Assert; import org.junit.experimental.theories.*; import public class TestClass extends Assert{ public TestClass() { public void testFactorialNull() { MathFunc math = new MathFunc(); assertTrue(math.factorial() == 1); }

@DataPoints public static int[][] Data = new int[][] {{5, public void testFactorialPositive(final int[] obj) { MathFunc math = new MathFunc(obj[0]); assertTrue(math.factorial() == obj[1]); public void testPlus() { MathFunc math = new MathFunc(45); assertTrue(math.plus(123) == 168); } }

Запуск теста будет иметь вид: >java –cp.;junit-4.92b2.jar org.junit.runner.JUnitCore TestClass На экране Time: OK(3 tests)

Журналирование Log4j При небольшом приложении вполне хватет возможностей System.out.println(), но по мере увеличения и усложнения приложения его возможностей уже нехватает. В частности нельзя (или затруднительно) сделать с помощью System.out.println(): 1) управление логгированием: отключить логгирование одних сообщений, и включить логгирование других

2) запись логов в файл, БД и т.п. 3) вывод дополнительной информации о сообщении: время события, место события, контекст 4) форматирование вывода Для решения этих проблем были созданы системы логгирования. Две наиболее популярные это Log4j от Apache и Java Logging API входящее в состав JDK 1.4 и старше. Рассмотрим пример использования Log4j:

import org.apache.log4j.Logger; import org.apache.log4j.BasicConfigurator; public class Test { private static Logger logger = Logger.getLogger(Test.class); public static void main(String[] args) throws Exception{ BasicConfigurator.configure(); logger.info("Hello world!"); logger.error("Error!", new Exception("An exception")); }

Для запуска данного приложения нужен пакет log4j jar. На экране получим: 0 [main] INFO ru.vingrad.log.Test - Hello world! 0 [main] ERROR ru.vingrad.log.Test - Error! java.lang.Exception: An exception at Test.main(Test.java:12) Базовым классом через которое и будет идти все логгирование является org.apache.log4j.Logger. Для получения логгера используется статический метод getLogger() куда в качестве параметра надо передать имя этого логгера.

Каждый логгер имеет уникальное имя, и вызов getLogger() с одним и тем же именем вернет один и тот же логгер. Логгер синхронизирован и его можно свободно использовать в многопоточной среде. Существует корневой логгер, являющийся родительским для всех остальных, получить его можно: Logger.getRootLogger(). Важным фактом является то, что если логгер не сконфигурирован индивидуально, то он будет использовать настройки своего родительского логгера.

Т.е. можно сконфигурировать только корневой логгер, а все логгеры будут использовать его настройки. Логгер может писать сообщения разных уровней (org.apache.log4j.Level). Уровни упорядочены по старшинству (в порядке убывания): FATAL, ERROR, WARN, INFO, DEBUG, TRACE. Уровни FATAL, ERROR, WARN предназначены для сообщений об ошибках, разной степени серьезности, INFO информационные сообщения, DEBUG - отладочные, TRACE - очень подробная отладочная информация.

Когда конфигурируется система логгирования то указывается сообщения какого уровня надо писать в лог. В лог будут писаться сообщения этого уровня и выше, т.е если система настроена на уровень INFO то будут писаться сообщения с уровнями FATAL, ERROR, WARN, INFO. Плюс еще есть спец уровни ALL и OFF, которые предназначены для включения/выключения записи всех сообщений.

Следующая составляющая системы логгирования это - аппендер (org.apache.log4j.Appender). Аппендер - это класс который занимается непосредственно записью сообщений. org.apache.log4j.Appender это интерфейс, но у него есть реализации для записи сообщений в консоль, файл, БД, и т.д. Так же у аппендера можно установить фильтры (org.apache.log4j.spi.Filter) для фильтрации сообщений. И форматер сообщений (org.apache.log4j.Layout) для форматирования выводимых сообщений.

Рассмотрим процесс записи логов: 1.Вызываем logger.info("Hello world!") 2. Происходит проверка уровня логгирования для данного логгера, если данному логгеру не был явно задан уровень, то тогда проверка осуществляется для его родителей. Если уровень логгера выше уровня сообщения, то оно отбрасывается и запись его не происходит. В нашем случае корневой аппендер сконфигурирован на уровень DEBUG а у сообщения уровень INFO, т.е. уровень логгера ниже уровня сообщения и оно проходит проверку.

3. Далее сообщение передается аппендерам установленным для данного логгера (если есть). 4. Если свойство additivity установлено в true (по умолчанию оно установлено), то сообщение так же передается родительским логгерам. 5. Аппендер получив сообщение проверяет его установленными фильтрами (если есть). Если сообщение прошло фильтры оно форматируется и записывается в лог.

Рассмотрим конфигурацию логгеров. Для конфигурирования Log4j используются файлы конфигурации log4j.xml и log4j.properties. Далее рассмотрим конфигуратор log4j.xml Cоздаем в папке с исходниками файл log4j.xml: (файл log4j.dtd можно найти внутри log4j.jar)

Добавим аппендеры. Консоль:

CONSOLE-DEBUG имя аппендера, по этому имени он будет упоминаться в этом конфиге. org.apache.log4j.ConsoleAppender - имя класса аппендера. - устанавливает параметр аппендера который определяет куда писать лог

Аналогично конфигурируется аппендер для ошибок разница в том, ошибки будут писаться в System.err.

Конфигурируем запись в файл параметр file задает имя файла.

Теперь сконфигурируем логгеры. Предположим что используется Hibernate и необходимо видеть только сообщения об ошибках и писать их только в консоль но не файл.

Но в то же самое время, необходимы отладочные сообщения в org.hibernate.SQL (где пишутся выполняемые Hibernate SQL запросы).

Для всех остальных категорий включаем уровень DEBUG и запись в консоль и файл

Протестировать данный xml файл можно следующим кодом: import org.apache.log4j.Logger; import org.apache.log4j.BasicConfigurator; public class Test { private static Logger logger = Logger.getLogger(Test.class); public static void main(String[] args) throws Exception { logger.debug("Inside main()"); logger.info("Hello world!"); logger.error("Error!", new Exception("An exception")); Logger hibernateGeneral = Logger.getLogger("org.hibernate"); hibernateGeneral.debug("Starting Hibernate"); Logger hibernateSql = Logger.getLogger("org.hibernate.SQL"); hibernateSql.debug("select * from my table"); hibernateGeneral.error("Hibernate error"); } }

Содержимое файла app.log имеет вид: :47:28,968 [DEBUG] Test Inside main() at Test.main(Test.java:9) :47:28,968 [ INFO] Test Hello world! at Test.main(Test.java:10) :47:28,968 [ERROR] Test Error! at Test.main(Test.java:11) java.lang.Exception: An exception at Test.main(Test.java:11)

Рассмотрим пример: import org.apache.log4j.Logger; import org.apache.log4j.FileAppender; import org.apache.log4j.SimpleLayout; import org.apache.log4j.Level; import java.io.IOException; public class DemoLog { static Logger logger = Logger.getLogger(DemoLog.class); public static void main(String[] args) { try { factorial(9); factorial(-3); } catch (IllegalArgumentException e) { logger.error("negative argument", e); }

public static int factorial(int n) { if (n < 0) throw new IllegalArgumentException( "argument " + n +" less then zero"); logger.debug("Argument n is " + n); int result = 1; for (int i = n; i >= 1; i--) result *= i; logger.info("Result is " + result); return result; } При этом в корне проекта должен находиться конфигурационный файл "log4j.xml" со следующим содержимым: