Слайд 2
![Что такое Unit-тест и с чем его едят Фрагмент кода,](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-1.jpg)
Что такое Unit-тест
и с чем его едят
Фрагмент кода, написанный разработчиком,
для проверки очень маленького, специфичного фрагмента функциональности кода.
Слайд 3
![Зачем нужно возиться с тестами они делают жизнь проще они](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-2.jpg)
Зачем нужно возиться с тестами
они делают жизнь проще
они делают дизайн приложения
лучше
они значительно уменьшают время, затрачиваемое на отладку.
Слайд 4
![Чего мы добиваемся написанием тестов Ответов на вопросы: Делает ли](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-3.jpg)
Чего мы добиваемся написанием тестов
Ответов на вопросы:
Делает ли код то, что
ожидает от него разработчик?
Делает ли он это все время?
Можно ли положиться на код?
Побочные эффекты:
Документирование кода и способов работы с ним
Слайд 5
![Как писать юнит-тесты Ответить на вопрос: как мы будем тестировать](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-4.jpg)
Как писать юнит-тесты
Ответить на вопрос: как мы будем тестировать новый метод.
Написать
тест и тестируемый класс.
Запустить тест.
Запустить ВСЕ тесты системы.
Слайд 6
![Типичные отговорки Написание тестов занимает слишком много времени Запуск тестов](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-5.jpg)
Типичные отговорки
Написание тестов занимает слишком много времени
Запуск тестов занимает слишком много
времени
Это не моя работа – тестировать мой код
Я не знаю точно, как код должен работать, поэтому я не могу написать тест
Но он же компилируется!
Мне платят за написание кода, а не тестов
Я чувствую вину, оставляя QA без работы
Моя компания не позволит запустить юнит-тесты на live-системе
Слайд 7
![Как исправлять баги Идентифицировать баг Написать тест, который «поломается» от](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-6.jpg)
Как исправлять баги
Идентифицировать баг
Написать тест, который «поломается» от этого бага, чтобы
подтвердить наличие бага.
Исправить код так, чтобы тест выполнился.
Запустить ВСЕ остальные тесты.
Слайд 8
![Red-Green-Refactor](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-7.jpg)
Слайд 9
![JUnit Библиотека (фреймворк) для модульного тестирования программного обеспечения на языке Java.](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-8.jpg)
JUnit
Библиотека (фреймворк) для модульного тестирования программного обеспечения на языке Java.
Слайд 10
![JUnit API: Assertion (Утверждения) assertEquals assertFalse assertTrue assertNotNull assertNull assertNotSame assertSame](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-9.jpg)
JUnit API: Assertion (Утверждения)
assertEquals
assertFalse
assertTrue
assertNotNull
assertNull
assertNotSame
assertSame
Слайд 11
![Junit API: Annotations @Test @Before @After @Ignore](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-10.jpg)
Junit API: Annotations
@Test
@Before
@After
@Ignore
Слайд 12
![JUnit Example: Class under test public class User { private](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-11.jpg)
JUnit Example: Class under test
public class User {
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public boolean isNamesake(User user) {
return getName().equals(user.getName());
}
}
Слайд 13
![Junit Example: Test class public class UserTest { @Test public](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-12.jpg)
Junit Example: Test class
public class UserTest {
@Test
public final void
testIsNameSake() {
User user1 = new User("Софья");
User user2 = new User("Алёшка");
Assert.assertNotNull(user1);
Assert.assertNotNull(user2);
Assert.assertNotSame(user1, user2);
Assert.assertFalse(user1.isNamesake(user2));
}
}
Слайд 14
![Легко? Всё меняется, когда приходят ОНИ](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-13.jpg)
Легко? Всё меняется, когда приходят ОНИ
Слайд 15
![Внешние зависимости Внешняя зависимость — это объект, с которым взаимодействует](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-14.jpg)
Внешние зависимости
Внешняя зависимость — это объект, с которым взаимодействует код и
над которым нет прямого контроля. Для ликвидации внешних зависимостей в модульных тестах используются тестовые объекты
Слайд 16
![Внешняя зависимость от БД public class User { private static](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-15.jpg)
Внешняя зависимость от БД
public class User {
private static final UserDAO
userDAO = new UserDAO();
private Integer id;
private String name;
public User(Integer id) {this.id = id;}
public String getName() {
if (name == null) {name = getUserDAO().getName(id);}
return name;
}
public void setName(String name) {this.name = name;}
UserDAO getUserDAO() {return userDAO;}
public boolean isNamesake(User user) {return getName().equals(user.getName());}
public void save() {getUserDAO().save(this);}
}
Слайд 17
![Виды тестовых объектов dummy stub spy mock fake](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-16.jpg)
Виды тестовых объектов
dummy
stub
spy
mock
fake
Слайд 18
![Dummy (Пустышка) Объект, который обычно передается в тестируемый класс в](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-17.jpg)
Dummy (Пустышка)
Объект, который обычно передается в тестируемый класс в качестве параметра,
но не имеет поведения, с ним ничего не происходит, никакие методы не вызываются. Примером таких dummy-объектов являются new object(), null, «Ignored String» и т.д.
Слайд 19
![Stub (Заглушка) Используется для получения данных из внешней зависимости, подменяя](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-18.jpg)
Stub (Заглушка)
Используется для получения данных из внешней зависимости, подменяя её. При
этом игнорирует все данные, могущие поступать из тестируемого объекта в stub. Один из самых популярных видов тестовых объектов. Тестируемый объект использует чтение из конфигурационного файла? Передаем ему ConfigFileStub, возвращающий тестовые строки конфигурации для избавления зависимости на файловую систему.
Слайд 20
![Spy (Шпион) Используется для тестов взаимодействия, основной функцией является запись](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-19.jpg)
Spy (Шпион)
Используется для тестов взаимодействия, основной функцией является запись данных и
вызовов, поступающих из тестируемого объекта для последующей проверки корректности вызова зависимого объекта. Позволяет проверить логику именно нашего тестируемого объекта, без проверок зависимых объектов.
Слайд 21
![Mock Очень похож на spy, однако не записывает последовательность вызовов](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-20.jpg)
Mock
Очень похож на spy, однако не записывает последовательность вызовов с переданными
параметрами для последующей проверки, а может сам выкидывать исключения при некорректно переданных данных. Т.е. именно мок-объект проверяет корректность поведения тестируемого объекта.
Слайд 22
![Fake Используется в основном чтобы запускать (незапускаемые) тесты (быстрее) и](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-21.jpg)
Fake
Используется в основном чтобы запускать (незапускаемые) тесты (быстрее) и ускорения их
работы. Эдакая замена тяжеловесного внешнего зависимого объекта его легковесной реализацией. Основные примеры — эмулятор для конкретного приложения БД в памяти (fake database) или фальшивый вебсервис.
Слайд 23
![Stubs vs Mocks](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-22.jpg)
Слайд 24
![“Ручные” “моки” и “стабы”](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-23.jpg)
“Ручные” “моки” и “стабы”
Слайд 25
![Stub public class UserStub extends User { private String name;](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-24.jpg)
Stub
public class UserStub extends User {
private String name;
public UserStub(String
name) {
super(null);
this.name = name;
}
@Override
public String getName() {return name;}
@Override
public void setName(String name) {}
@Override
public boolean isNamesake(User user) {return false;}
@Override
public void save() {}
}
Слайд 26
![Stub: Test case public class UserTest { @Test public final](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-25.jpg)
Stub: Test case
public class UserTest {
@Test
public final void testManualStub()
{
User user1 = new UserStub("Софья");
User user2 = new UserStub("Алёшка");
Assert.assertFalse(user1.getName().equals(user2.getName()));
}
}
Слайд 27
![Mock public class UserMock extends User { private UserDAO userDAO;](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-26.jpg)
Mock
public class UserMock extends User {
private UserDAO userDAO;
public UserMock(Integer
id, UserDAO userDAO) {
super(id);
this.userDAO = userDAO;
}
@Override
public UserDAO getUserDAO() {
return userDAO;
}
}
Слайд 28
![Stub for Mock public class UserDAOStub extends UserDAO { @Override](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-27.jpg)
Stub for Mock
public class UserDAOStub extends UserDAO {
@Override
public String
getName(Integer id) {
switch(id) {
case 1:
return "Софья";
case 2:
return "Алёшка";
default:
return "Noname";
}
}
@Override
public void save(User user) {}
}
Слайд 29
![Mock with Stub: Test case public class UserTest { @Test](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-28.jpg)
Mock with Stub: Test case
public class UserTest {
@Test
public final
void testManualMock() {
UserDAO userDAOStub = new UserDAOStub();
User user1 = new UserMock(1, userDAOStub);
User user2 = new UserMock(2, userDAOStub);
User user3 = new UserMock(1, userDAOStub);
Assert.assertFalse(user1.isNamesake(user2));
Assert.assertTrue(user1.isNamesake(user3));
}
}
Слайд 30
![Слишком сложно? Отведайте mockito! import static org.mockito.Mockito.*;](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-29.jpg)
Слишком сложно?
Отведайте mockito!
import static org.mockito.Mockito.*;
Слайд 31
![Mockito: stubbing public class UserTest { @Test public final void](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-30.jpg)
Mockito: stubbing
public class UserTest {
@Test
public final void testMockitoStub() {
User user1 = mock(User.class);
when(user1.getName()).thenReturn("Софья");
User user2 = mock(User.class);
when(user2.getName()).thenReturn("Алёшка");
Assert.assertFalse(user1.getName().equals(user2.getName()));
}
}
Слайд 32
![Mockito: mocking public class UserTest { @Test public final void](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-31.jpg)
Mockito: mocking
public class UserTest {
@Test
public final void testMockitoMock1() {
User user1 = mock(User.class);
when(user1.getName()).thenReturn("Софья");
when(user1.isNamesake(any(User.class))).thenCallRealMethod();
User user2 = mock(User.class);
when(user2.getName()).thenReturn("Алёшка");
Assert.assertFalse(user1.isNamesake(user2));
}
}
Слайд 33
![Mockito: spying & stubbing public class UserTest { @Test public](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-32.jpg)
Mockito: spying & stubbing
public class UserTest {
@Test
public final void
testIsMockitoMock2() {
UserDAO userDAOStub = mock(UserDAO.class); when(userDAOStub.getName(anyInt())).thenReturn("Софья").
thenReturn("Алёшка");
doNothing().when(userDAOStub).save(any(User.class));
User user1 = spy(new User(1));
when(user1.getUserDAO()).thenReturn(userDAOStub);
User user2 = spy(new User(2));
when(user2.getUserDAO()).thenReturn(userDAOStub);
Assert.assertFalse(user1.isNamesake(user2));
}
}
Слайд 34
![Mockito: spying public class UserTest { @Test public final void](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-33.jpg)
Mockito: spying
public class UserTest {
@Test
public final void testMockitoSpy() {
UserDAO userDAOStub = mock(UserDAO.class);
when(userDAOStub.getName(anyInt())).thenAnswer(new Answer() {
@Override
public String answer(InvocationOnMock invocation) throws Throwable {
Integer id = (Integer) invocation.getArguments()[0];
switch (id) {
case 1:
return "Софья";
case 2:
return "Алёшка";
default:
return "Noname";
}
}
});
doThrow(new RuntimeException()).when(userDAOStub).save(any(User.class));
User user1 = spy(new User(1));
when(user1.getUserDAO()).thenReturn(userDAOStub);
User user2 = spy(new User(2));
when(user2.getUserDAO()).thenReturn(userDAOStub);
Assert.assertFalse(user1.isNamesake(user2));
}
}
Слайд 35
![Подведём итоги Тестируйте все, что может сломаться Тестируйте все, что](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/142429/slide-34.jpg)
Подведём итоги
Тестируйте все, что может сломаться
Тестируйте все, что уже сломалось
Новый код
признается виновным, пока не доказана его невиновность.
Тестового кода должно быть как минимум сколько же, сколько и production-кода.
Запускайте юнит-тесты локально при каждой компиляции.
Запускайте все юнит-тесты перед check-in-ом в репозиторий.