Вступление
- Теперь, когда я официально выпустил свое первое приложение, которое можно найти ЗДЕСЬ в магазине Google Play. Я хочу добавить больше функций в свое приложение, однако, я думаю, что следующий лучший шаг – добавить несколько тестов. Итак, эта серия будет практической серией, посвященной пониманию тестирования в среде Android.
Исходный код
- Вы можете найти мой исходный код здесь
Видеоверсия
Вы можете найти видеоверсию ЗДЕСЬ
Предупреждение!
- Если вы хотите использовать отраслевой стандарт для тестирования и создания асинхронного кода, то используйте RxAndroid . Однако моя кодовая база достаточно мала, и я считаю, что библиотека не нужна. Это означает, что мы будем использовать какое-то старое доброе ПАРАЛЛЕЛЬНОЕ ПРОГРАММИРОВАНИЕ!!!!!!!
Потоки и процессы
- Прежде чем мы сможем продвинуться дальше, нам нужно поговорить о двух основных единицах выполнения в параллельном программировании:
1) Процессы 2) Нити
1) Процессы
- Процесс – это автономная среда выполнения, каждый процесс имеет свое собственное пространство памяти. В зависимости от среды у вас может быть один процессор несколько процессов взаимодействующих друг с другом
2) Нити
- Потоки иногда называют легковесными процессами. Как потоки, так и процессы обеспечивают среду выполнения, но для создания потока требуется меньше ресурсов, чем для создания нового процесса.
Потоки существуют внутри процесса, и каждый процесс имеет по крайней мере один поток.
Потоки совместно используют ресурсы процесса, включая память. Это обеспечивает эффективную, но потенциально проблематичную коммуникацию.
Процессы и потоки в Android
- В большинстве случаев каждое приложение для Android запускается в своем собственном процессе Linux (кстати, Android работает под управлением Linux).По умолчанию все компоненты одного и того же приложения используют один и тот же процесс и поток, называемый “основным” потоком.
Основная нить
Когда приложение запускается, система создает поток выполнения для приложения с именем
main
. Основной поток очень важен, потому что он отвечает за отправку событий соответствующему пользователю. Это также поток, в котором приложение взаимодействует с компонентами из Android UI toolkit (компоненты из пакетов виджетов и представлений). Теперь при работе с основным потоком main thread всегда следует соблюдать два простых правила:1) не блокируйте основной поток
2) не получайте доступ к инструментарию пользовательского интерфейса Android извне основного потока
Чтобы соблюдать эти правила, мы собираемся внедрить
рабочие потоки
Рабочие потоки
- Чтобы ваше приложение оставалось отзывчивым и не блокировало основной поток, мы обязательно выполняем задачи, которые не реагируют немедленно в Такие задачи, как декодирование растрового изображения, доступ к хранилищу, работа с моделью машинного обучения или выполнение сетевого запроса, должны выполняться в рабочем потоке. Не волнуйтесь, я более подробно расскажу о рабочих потоках, как только мы начнем их использовать.
Создание тестовой базы данных
Во-первых, я уже предположил, что вы настроили базу данных комнат и DAO (объект доступа к данным). Если вы этого не сделали, вы можете следовать инструкциям ЗДЕСЬ , вернитесь и продолжите, как только вы оба настроите.
Теперь создайте класс и назовите его TestDatabase:
public class TestDatabase { // should be an instance of your real world database private volatile CalfRoomDatabase calfDatabase; }
- Обратите внимание на ключевое слово
volatile
, оно используется для устранения риска ошибок согласованности памяти. Подробнее о ключевом слове volatile читайте здесь ЗДЕСЬ . Игнорируйте мое соглашение об именованииБазы данных комнат для телят
, замените его тем, что называется вашей реализацией базы данных комнат. Теперь давайте перейдем к созданию фоновых потоков (рабочих потоков).
Создание рабочих потоков
public class TestDatabase { // should be an instance of your real world database private volatile CalfRoomDatabase calfDatabase; private static final int NUMBER_OF_THREADS = 4; public static final ExecutorService databaseWriteExecutor = Executors.newFixedThreadPool(NUMBER_OF_THREADS); }
- Самый простой и простой способ создания рабочих потоков – это использовать пул потоков, а точнее
фиксированный пул потоков/| . Фиксированный пул потоков содержит определенное количество потоков (рабочих потоков). Затем мы отправим асинхронные задачи в фиксированный пул, который будет хранить задачи через внутреннюю очередь, которая будет удерживать задачу до тех пор, пока не станет доступен рабочий поток. Если рабочий поток недоступен, то задача будет ждать, пока один из них не освободится для выполнения задачи. Мы создаем наш фиксированный пул потоков с помощью инструкции:
public static final ExecutorService database WriteExecutor = Исполнители.newFixedThreadPool(NUMBER_OF_THREADS);
- Теперь мы можем иметь доступ к потокам вне основного потока для выполнения асинхронных задач.
В базе данных памяти
- Для целей тестирования
ТОЛЬКО
мы будем создавать базу данных в памяти. Этот тип базы данных хранит информацию в базе данных памяти (на устройстве), которая исчезнет, когда процесс будет остановлен. Это означает, что когда тест закончится, то же самое произойдет и с базой данных. - Теперь, ради хороших привычек тестирования, мы будем создавать и уничтожать базу данных для каждого теста. Таким образом, мы будем знать текущее состояние каждой базы данных. Мы сможем добиться этого с помощью
@After
и@Перед
аннотациями, которые предоставляет нам Junit. Как вы могли догадаться, любой метод, отмеченный символомBefore
будет выполняться перед тестом, а метод, помеченный@After
, будет выполняться после теста.
public class TestDatabase { //... still include other methods @Before public void init(){ calfDatabase = Room.inMemoryDatabaseBuilder( ApplicationProvider.getApplicationContext(), CalfRoomDatabase.class ).build(); } @After public void finish(){ calfDatabase.close(); } }
- Метод
@Before
содержит весь код, необходимый для создания базы данных для нас. Это означает, что перед каждым тестом будет создаваться новая база данных. Метод@After
будет выполняться после каждого метода и уничтожит базу данных, вызвав.close ()
ДАО
- Далее нам нужен доступ к DAO (объекту доступа к данным). Это даст нам доступ к операциям DAO, которые нам нужно протестировать. Если вы еще не создали DAO или не знакомы с его работой, воспользуйтесь ссылкой ЗДЕСЬ . Чтобы получить доступ к DAO, мы просто создаем метод:
public class TestDatabase { //... still include other methods public CalfDao getCalfDao(){ // getCalfDao() implemented by Room return calfDatabase.getCalfDao(); } }
- Опять же, игнорируйте соглашения об именовании моего кода (мое приложение имеет коровью тематику). Разница между вашим кодом и моим кодом заключается в том, что вы должны изменить
calf Database
с любым вызываемым вашим классом базы данных.
НАСТОЯЩИЙ ТЕСТ!!!
- Хорошо, теперь, когда мы получили все детали, давайте создадим несколько тестов. Итак, то, что мы на самом деле тестируем, – это DAO, потому что именно так мы получаем доступ к нашей базе данных. Создайте новый класс с именем
DAO Test
и попросите его расширить нашуТестовую базу данных
:
public class DAOTest extends TestDatabase { }
- Благодаря наследованию классов у нас теперь есть доступ ко всему внутри класса тестовой базы данных. Таким образом, мы можем избежать необходимости иметь дело с конкретными реализациями. Следующий шаг, который мы предпримем, – это создание метода тестирования.
public class DAOTest extends TestDatabase { @Test public void insertTest() throws Exception{ } }
- Обозначение
@Test
взято из Junit и указывает системе, что этот метод будет использоваться для тестирования. Это означает, что@Перед
и@After
будут запущены методы. Затем мы можем создать тестовый объект, который будет вставлен, и тестовое значение, которое будет возвращено после успешной вставки.
Calf calfTest1 = new Calf(1,"test-1", "TEST 1", new Date(),"Bull","test-1"); int testingReturnValue = 1;
- Теперь пришло время действительно стать асинхронным, используя будущее. Будущее – это объект, представляющий результат асинхронного вычисления. Результат может быть получен только с помощью метода
get()
после завершения вычисления, блокируя до тех пор, пока результат не будет достигнут.
public class DAOTest extends TestDatabase { @Test public void insertTest() throws Exception{ Futurecalf = CalfDatabaseTest .databaseWriteExecutor.submit(new Runnable() { @Override public void run() { getCalfDao().insert(calfTest1); } }, testingReturnValue); //RETURN A FUTURE OBJECT int returningCalfValue = calf.get(); } }
- Мы предоставляем Future параметр целочисленного типа, потому что наше возвращаемое значение
testing
является целым числом. Затем мы получаем доступ к нашему фиксированному пулу потоков,Проверка базы данных Calf.databaseWriteExecutor
. Это дает нам доступ к рабочим потокам, которым мы можем назначать асинхронные задачи. Мы создаем задачу с помощью метода отправки и предоставляем ей возможность выполнения.submit(new Runnable(),testing Return Value)
, поскольку у Runnable нет возвращаемого значения, мы предоставляем его с помощьюtestingReturnValue
. Внутри Runnable мы переопределяем методrun
и вызываем задачу, которую мы хотим выполнить в рабочем потоке. Поскольку этот код является асинхронным, нам нужно заблокировать и дождаться значения. Мы делаем это с помощью методаget()
. Методget()
будет блокироваться до тех пор, пока не получит возвращаемое значение.
Утверждать
- Наконец, мы можем сделать утверждение, что
возвращаемое значение Calf
совпадает стестируемым возвращаемым значением
, что указывает на успешную вставку.
Assert.assertEquals(testingReturnValue,returningCalfValue);
- С помощью этого мы можем нажать на маленькую зеленую стрелку рядом с методом, чтобы запустить тест и увидеть, что он прошел.
- Спасибо, что нашли время в свой рабочий день, чтобы прочитать этот мой пост в блоге. Если у вас есть какие-либо вопросы или сомнения, пожалуйста, прокомментируйте ниже или свяжитесь со мной по адресу Твиттер .
Оригинал: “https://dev.to/theplebdev/testing-in-android-testing-the-room-database-3fj8”