Ответ 1
Так как метод getApplicationContext
находится внутри класса, который вы расширяете, он становится несколько проблематичным. Есть несколько проблем, которые следует учитывать:
- Вы действительно не можете издеваться над классом, который находится под тестированием, что является одним из многих недостатков с наследованием объектов (например, подклассификация).
- Другая проблема заключается в том, что
ApplicationContext
является singleton, что делает его еще более злым для тестирования, поскольку вы не можете легко высмеять глобальное состояние, которое запрограммировано как незаменимое.
Что вы можете сделать в этой ситуации, так это предпочесть структуру объекта над наследованием. Поэтому, чтобы сделать ваш Activity
testable, вам нужно немного разделить логику. Допустим, что ваш Activity
называется MyActivity
. Он должен состоять из логического компонента (или класса), давайте назовите его MyActivityLogic
. Вот простой рисунок диаграммы:
Чтобы решить проблему singleton, мы позволяем логике "вводить" контекст приложения, поэтому ее можно протестировать с помощью макета. Затем нам нужно только проверить, что объект MyActivity
помещает корректный контекст приложения в MyActivityLogic
. Как мы в основном решаем обе проблемы, через еще один уровень абстракции (перефразированный из Батлера Лампсона). Новый слой, который мы добавляем в этом случае, - это логика активности, перемещенная за пределы объекта активности.
Для вашего примера классы должны выглядеть примерно так:
public final class MyActivityLogic {
private MyApp mMyApp;
public MyActivityLogic(MyApp pMyApp) {
mMyApp = pMyApp;
}
public MyApp getMyApp() {
return mMyApp;
}
public void onClick(View pView) {
getMyApp().setNewState();
}
}
public final class MyActivity extends Activity {
// The activity logic is in mLogic
private final MyActivityLogic mLogic;
// Logic is created in constructor
public MyActivity() {
super();
mLogic = new MyActivityLogic(
(MyApp) getApplicationContext());
}
// Getter, you could make a setter as well, but I leave
// that as an exercise for you
public MyActivityLogic getMyActivityLogic() {
return mLogic;
}
// The method to be tested
public void onClick(View pView) {
mLogic.onClick(pView);
}
// Surely you have other code here...
}
Все должно выглядеть примерно так:
Для тестирования MyActivityLogic
вам понадобится только простой jUnit TestCase
вместо ActivityUnitTestCase
(поскольку это не Activity), и вы можете издеваться над своим контекстом приложения, используя вашу насмешливую структуру выбора (с тех пор, как выполняется перенос ваши собственные макеты немного перетаскивают). В примере используется Mockito:
MyActivityLogic mLogic; // The CUT, Component Under Test
MyApplication mMyApplication; // Will be mocked
protected void setUp() {
// Create the mock using mockito.
mMyApplication = mock(MyApplication.class);
// "Inject" the mock into the CUT
mLogic = new MyActivityLogic(mMyApplication);
}
public void testOnClickShouldSetNewStateOnAppContext() {
// Test composed of the three A
// ARRANGE: Most stuff is already done in setUp
// ACT: Do the test by calling the logic
mLogic.onClick(null);
// ASSERT: Make sure the application.setNewState is called
verify(mMyApplication).setNewState();
}
Чтобы протестировать MyActivity
, вы используете ActivityUnitTestCase
, как обычно, нам нужно только убедиться, что он создает MyActivityLogic
с правильным ApplicationContext
. Пример скриншотного тестового кода, который делает все это:
// ARRANGE:
MyActivity vMyActivity = getActivity();
MyApp expectedAppContext = vMyActivity.getApplicationContext();
// ACT:
// No need to "act" much since MyActivityLogic object is created in the
// constructor of the activity
MyActivityLogic vLogic = vMyActivity.getMyActivityLogic();
// ASSERT: Make sure the same ApplicationContext singleton is inside
// the MyActivityLogic object
MyApp actualAppContext = vLogic.getMyApp();
assertSame(expectedAppContext, actualAppContext);
Надеюсь, что все это имеет смысл для вас и поможет вам.