При написании локальных модульных тестов в Android одно из ограничений, с которыми вы сталкиваетесь, заключается в том, что тесты запускаются для версии android.jar, в которой нет кода. Как объясняется в документации , любая зависимость от кода Android должна быть проверена.
Быстрый пример простого юнит-теста:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
public class ClassUnderTest {public String methodUnderTest(String str){ if (PhoneNumberUtils.isGlobalPhoneNumber(str)) { return "yes"; } else { return "no"; } }}@RunWith(JUnit4.class)public class TestThatFails {private ClassUnderTest classUnderTest; @Before public void setup() { classUnderTest = new ClassUnderTest(); } @Test public void testTheClass() { String result = classUnderTest.methodUnderTest("1234"); assertEquals("yes", result); }} |
Когда этот тест запущен, он потерпит неудачу со следующей ошибкой:
|
1
|
java.lang.RuntimeException: Method isGlobalPhoneNumber in android.telephony.PhoneNumberUtils not mocked. See http://g.co/androidstudio/not-mocked for details |
Класс, который мы тестируем, зависит от библиотеки утилит Android PhoneNumberUtils . Для успешного выполнения теста необходимо проверить зависимость Android.
Весь пример кода для этого поста доступен в этой сути .
Мокито: нет статическим методам
Предложенный Google способ смоделировать зависимости Android — использовать Mockito . В целом это было бы хорошо, однако в нашем примере это не будет работать, потому что Mockito не поддерживает насмешливые статические методы.
Это обсуждение показывает, что участники Mockito считают статические методы анти-паттернами по разным причинам, например
- Зависимость от статического метода становится жесткой в коде.
- Это затрудняет издевательство и тестирование.
Следовательно, они не поддерживают это, поскольку они не хотят поощрять плохой дизайн.
Итак, как еще можно заставить наш тест работать?
- Если бы это была простая старая Java вместо Android, я мог бы использовать PowerMockito, чтобы высмеивать статические методы. Однако я обнаружил, что использование PowerMock проблематично в Android.
- Если вы используете только несколько статических методов, вы можете просто скопировать код в свое приложение, предполагая, что источник доступен. Конечно, это означает, что нужно поддерживать больше кода, и оно не является устойчивым, если вы используете много статических методов.
- Вы можете заключить вызов статического метода и внутренне делегировать статический метод. Обертка может быть затем издевались. Это вариант, который мы будем обсуждать.
Классы Обертки
Одним из решений является создание класса-оболочки для классов Android, имеющих статический метод, и добавление этой оболочки в качестве зависимости.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public class PhoneNumberUtilsWrapper { public boolean isGlobalPhoneNumber(String phoneNumber) { return PhoneNumberUtils.isGlobalPhoneNumber(phoneNumber); }}public class ClassUnderTestWithWrapper { private PhoneNumberUtilsWrapper wrapper; public ClassUnderTestWithWrapper(PhoneNumberUtilsWrapper wrapper) { this.wrapper = wrapper; } public String methodUnderTest(String str) { if (wrapper.isGlobalPhoneNumber(str)) { return "yes"; } else { return "no"; } }} |
Здесь я создал класс-оболочку для PhoneNumberUtils, который теперь является зависимостью от тестируемого класса.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
@RunWith(JUnit4.class)public class TestWithWrapper { @Mock PhoneNumberUtilsWrapper wrapper; private ClassUnderTestWithWrapper classUnderTest; @Before public void setup() { MockitoAnnotations.initMocks(this); classUnderTest = new ClassUnderTestWithWrapper(wrapper); } @Test public void testTheClass() { when(wrapper.isGlobalPhoneNumber(anyString())).thenReturn(true); String result = classUnderTest.methodUnderTest("1234"); assertEquals("yes", result); }} |
Поскольку класс-обертка может быть смоделирован, а вызов метода в тестируемом классе не является статическим, тест теперь можно пройти.
Одна из проблем этого решения заключается в том, что тестируемый класс зависит от статических методов из многих библиотек Android. Например, что произойдет, если тестируемый класс также должен будет использовать TextUtils , DateUtils и т. Д. Внезапно вы получите гораздо больше стандартного кода, больше параметров конструктора и т. Д.
Методы Обертки
Другой способ — обернуть вызов статического метода в нестатический метод в тестируемом классе.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
public class ClassUnderTestWithWrappedMethod { public String methodUnderTest(String str) { if (isGlobalPhoneNumber(str)) { return "yes"; } else { return "no"; } } // can't be private access boolean isGlobalPhoneNumber(String phoneNumber) { return PhoneNumberUtils.isGlobalPhoneNumber(phoneNumber); }} |
Чтобы это работало, в тесте мы должны использовать шпион Mockito . Также обратите внимание, что упакованные методы должны быть доступны в тесте и поэтому не могут быть приватными.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
@RunWith(JUnit4.class)public class TestWithWrappedMethod { private ClassUnderTestWithWrappedMethod classUnderTest; private ClassUnderTestWithWrappedMethod classUnderTestSpy; @Before public void setup() { MockitoAnnotations.initMocks(this); classUnderTest = new ClassUnderTestWithWrappedMethod(); classUnderTestSpy = Mockito.spy(classUnderTest); } @Test public void testTheClass() { doReturn(true).when(classUnderTestSpy) .isGlobalPhoneNumber(anyString()); String result = classUnderTestSpy.methodUnderTest("1234"); assertEquals("yes", result); }} |
Здесь мы запускаем тест для класса spy, который делегирует вызовы методов тестируемому реальному классу. Однако мы можем создать заглушки для методов, которые переносят статические вызовы методов в библиотеки Android.
Как я уже упоминал, одним из недостатков является то, что упакованные методы не могут быть закрытыми, что не является идеальным с точки зрения разработки ОО. Но тогда вы должны пойти на подобные компромиссы, если вы используете такие библиотеки, как Dagger или Butterknife .
Вывод
Оба эти решения могут работать, но, возможно, неплохо быть последовательными и придерживаться одного способа, если можете. Какой метод работает лучше, может зависеть от архитектуры вашего приложения, например, используете ли вы внедрение зависимостей.
Статические методы: хорошо или плохо? Это имеет значение?
В этом посте я не спорю о том, являются ли статические методы хорошими или плохими (хотя я лично считаю, что их использование должно быть ограничено). В интернете уже много споров по этому вопросу.
Однако в мире Java они являются фактом жизни.
Многие служебные классы в Java, Android и многие популярные библиотеки — это не настоящие ОО-классы, а наборы процедурных функций. Функции часто пишутся как статические методы и группируются по функциональности.
Нравится ли вам статические методы или нет, мы все должны научиться обращаться с ними прагматично.
| Опубликовано на Java Code Geeks с разрешения Дэвида Вонга, партнера нашей программы JCG . Смотрите оригинальную статью здесь: Пересмешивающие статические методы в Android: давайте подведем итоги
Мнения, высказанные участниками Java Code Geeks, являются их собственными. |