2015-05-01 3 views
47

У меня есть приложение для Android, которое использует Dagger 2 для инъекций зависимостей. Я также использую новейшие инструменты построения градиентов, которые позволяют вариант сборки для модульного тестирования и один для контрольных тестов. Я использую java.util.Random в своем приложении, и я хочу издеваться над этим для тестирования. В классах, которые я тестирую, не используются никакие вещи Android, поэтому они просто обычные классы Java.Тесты для Android с кинжалом 2

В моем основном коде я определяю Component в классе, который расширяет класс Application, но в модульных тестах я не использую Application. Я попытался определить тест Module и Component, но Кинжал не будет генерировать Component. Я также попытался использовать Component, который я определил в своем приложении, и обмениваю Module при его создании, но Component приложения не имеет методов inject для моих тестовых классов. Как я могу обеспечить макетную реализацию Random для тестирования?

Вот некоторые примеры кода:

Применение:

public class PipeGameApplication extends Application { 

    private PipeGame pipeGame; 

    @Singleton 
    @Component(modules = PipeGameModule.class) 
    public interface PipeGame { 
     void inject(BoardFragment boardFragment); 
     void inject(ConveyorFragment conveyorFragment); 
    } 

    @Override 
    public void onCreate() { 
     super.onCreate(); 
     pipeGame = DaggerPipeGameApplication_PipeGame.create(); 
    } 

    public PipeGame component() { 
     return pipeGame; 
    } 
} 

Модуль:

@Module 
public class PipeGameModule { 

    @Provides 
    @Singleton 
    Random provideRandom() { 
     return new Random(); 
    } 
} 

Базовый класс для испытаний:

public class BaseModelTest { 

    PipeGameTest pipeGameTest; 

    @Singleton 
    @Component(modules = PipeGameTestModule.class) 
    public interface PipeGameTest { 
     void inject(BoardModelTest boardModelTest); 
     void inject(ConveyorModelTest conveyorModelTest); 
    } 

    @Before 
    public void setUp() { 
     pipeGameTest = DaggerBaseModelTest_PipeGameTest.create(); // Doesn't work 
    } 

    public PipeGameTest component() { 
     return pipeGameTest; 
    } 
} 

или:

public class BaseModelTest { 

    PipeGameApplication.PipeGame pipeGameTest; 

    // This works if I make the test module extend 
    // the prod module, but it can't inject my test classes 
    @Before 
    public void setUp() { 
     pipeGameTest = DaggerPipeGameApplication_PipeGame.builder().pipeGameModule(new PipeGameModuleTest()).build(); 
    } 

    public PipeGameApplication.PipeGame component() { 
     return pipeGameTest; 
    } 
} 

Test Module:

@Module 
public class PipeGameTestModule { 

    @Provides 
    @Singleton 
    Random provideRandom() { 
     return mock(Random.class); 
    } 
} 
+0

Это поможет https://stackoverflow.com/questions/26939340/how-do-you-override-a-module-dependency-in-a-unit-test-with-dagger-2-0 ? lq = 1 –

+0

Возможно, вам стоит подумать о том, что Dagger не рекомендуется для модульного тестирования: https://google.github.io/dagger/testing.html –

ответ

2

На мой взгляд, вы можете подойти к этой проблеме, глядя на него под другим углом. Вы легко сможете выполнить модульный тест своего класса, не зависимо от кинжала для тестируемого строительного класса с его издеваемыми зависимостями, введенными в него.

То, что я хочу сказать, что в тестовой конфигурации вы можете:

  • Mock зависимости класса испытуемого
  • Построить класс тестируемых вручную с помощью издевались зависимости

Нам не нужно проверять правильность ввода зависимостей, поскольку Dagger проверяет правильность графика зависимости во время компиляции. Таким образом, любые ошибки будут сообщаться в результате сбоя компиляции. И поэтому должно быть приемлемым ручное создание тестируемого класса в методе установки.

Пример кода, где зависимость вводится с помощью конструктора в классе под тест:

public class BoardModelTest { 

    private BoardModel boardModel; 
    private Random random; 

    @Before 
    public void setUp() { 
    random = mock(Random.class); 
    boardModel = new BoardModel(random); 
    } 

    @Test 
    ... 
} 

public class BoardModel { 
    private Random random; 

    @Inject 
    public BoardModel(Random random) { 
    this.random = random; 
    } 

    ... 
} 

Пример кода, где зависимость вводится с помощью поля в классе при испытании (в случае BoardModel строится рамки):

public class BoardModelTest { 

    private BoardModel boardModel; 
    private Random random; 

    @Before 
    public void setUp() { 
    random = mock(Random.class); 
    boardModel = new BoardModel(); 
    boardModel.random = random; 
    } 

    @Test 
    ... 
} 

public class BoardModel { 
    @Inject 
    Random random; 

    public BoardModel() {} 

    ... 
} 
+0

Это не будет работать в моем случае, поскольку в настоящее время у меня есть много вложенных зависимостей, но это, безусловно, будет работать для более простых случаев, и я определенно считаю, что это правильный подход, если вы можете это сделать. – Pikaling

+0

Почему вы говорите, чтобы использовать ** @ Inject Random random; **, если вы не используете кинжал? –

1

Если вы используете dagger2 с Android, вы можете использовать аппетитные приемы для предоставления насмешливых ресурсов.

Смотрите здесь демо вкусов в макете тестирования (без кортика): https://www.youtube.com/watch?v=vdasFFfXKOY

Эта кодовая есть пример: https://github.com/googlecodelabs/android-testing

В вашем /SRC/прод/ком/yourcompany/Component .java Вы предоставляете свои производственные компоненты.

В вашем /src/mock/com/yourcompany/Component.java вы предоставляете свои компоненты насмешливые.

Это позволяет создавать сборки вашего приложения с или без насмешек. Он также позволяет параллельную разработку (бэкенд одной командой, приложение для сторонних разработчиков другой командой), вы можете высмеивать до тех пор, пока не будут доступны методы api.

Как мои Gradle команды выглядят (его Makefile):

install_mock: 
    ./gradlew installMockDebug 

install: 
    ./gradlew installProdDebug 

test_unit: 
    ./gradlew testMockDebugUnitTest 

test_integration_mock: 
    ./gradlew connectedMockDebugAndroidTest 

test_integration_prod: 
    ./gradlew connectedProdDebugAndroidTest 
+1

Это репо не показывает компоненты кинжала 2. Как вы издеваетесь над ними в своем проекте? –

6

Вы попали в самую голову, говоря:

Компонент не имеет INJECT методы приложения для моего теста классы

Итак, чтобы обойти эту проблему, мы можем сделать тестовую версию вашего класса Application. Тогда у нас может быть тестовая версия вашего модуля. И чтобы все это выполнялось в тесте, мы можем использовать Robolectric.

1) Создайте тестовую версию своего класса Application

public class TestPipeGameApp extends PipeGameApp { 
    private PipeGameModule pipeGameModule; 

    @Override protected PipeGameModule getPipeGameModule() { 
     if (pipeGameModule == null) { 
      return super.pipeGameModule(); 
     } 
     return pipeGameModule; 
    } 

    public void setPipeGameModule(PipeGameModule pipeGameModule) { 
     this.pipeGameModule = pipeGameModule; 
     initComponent(); 
    }} 

2) Ваш исходный класс приложений должен иметь InitComponent() и pipeGameModule() методы

public class PipeGameApp extends Application { 
    protected void initComponent() { 
     DaggerPipeGameComponent.builder() 
      .pipeGameModule(getPipeGameModule()) 
      .build(); 
    } 

    protected PipeGameModule pipeGameModule() { 
     return new PipeGameModule(this); 
    }} 

3) Ваш PipeGameTestModule должен расширить производственный модуль конструктором:

public class PipeGameTestModule extends PipeGameModule { 
    public PipeGameTestModule(Application app) { 
     super(app); 
    }} 

4) Теперь, в настройки вашего тестирования JUnit (в) метод, установите этот модуль тестирования на тестовом приложении:

@Before 
public void setup() { 
    TestPipeGameApp app = (TestPipeGameApp) RuntimeEnvironment.application; 
    PipeGameTestModule module = new PipeGameTestModule(app); 
    app.setPipeGameModule(module); 
} 

Теперь вы можете настроить свой тестовый модуль, как вы изначально хотели.

+0

Что вы подразумеваете под 'RuntimeEvironment'? – superuserdo

+0

@superuserdo Это ** Robolectric **: http://robolectric.org/using-qualifiers/ –

+0

Отлично, Игорь, спасибо. Мне просто нужна еще одна информация: как это вставить экземпляры тестового класса? –

0

У меня на самом деле была такая же проблема, и я нашел очень простое решение. Это не лучшее решение, я думаю, но оно решит вашу проблему.

Создать подобный класс в модуле приложения:

public class ActivityTest<T extends ViewModelBase> { 

    @Inject 
    public T vm; 
} 

Затем в вашем AppComponent оных:

void inject(ActivityTest<LoginFragmentVM> activityTest); 

Тогда вы сможете привнести что в тестовом классе.

public class HelloWorldEspressoTest extends ActivityTest<LoginFragmentVM> { 

    @Rule 
    public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule(MainActivity.class); 

    @Test 
    public void listGoesOverTheFold() throws InterruptedException { 
     App.getComponent().inject(this); 
     vm.email.set("1234"); 
     closeSoftKeyboard(); 
    } 
} 
+0

Что такое ** ViewModelBase **? –

Смежные вопросы