17

DI の概念は完全に理にかなっているからです。DI の優れた「副産物」の 1 つは (Jake Wharton がプレゼンテーションの 1 つに述べたように)、テスト容易性です。

だから今、私は基本的に Espresso を使用していくつかの機能テストを行っており、ダミー/モック データをアプリケーションに挿入し、アクティビティにそれらを表示できるようにしたいと考えています。これは DI の最大の利点の 1 つであるため、比較的簡単な質問であると思います。しかし、なぜか頭が回らないようです。どんな助けでも大歓迎です。これが私がこれまでに持っているものです(現在の設定を反映する例を書きました):

public class MyActivity
    extends MyBaseActivity {

    @Inject Navigator _navigator;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        MyApplication.get(this).inject(this);

        // ...

        setupViews();
    }

    private void setupViews() {
        myTextView.setText(getMyLabel());
    }

    public String getMyLabel() {
        return _navigator.getSpecialText(); // "Special Text"
    }
}

これらは私の短剣モジュールです:

// Navigation Module

@Module(library = true)
public class NavigationModule {

    private Navigator _nav;

    @Provides
    @Singleton
    Navigator provideANavigator() {
        if (_nav == null) {
            _nav = new Navigator();
        }
        return _nav;
    }
}

// App level module

@Module(
    includes = { SessionModule.class, NavigationModule.class },
    injects = { MyApplication.class,
                MyActivity.class,
                // ...
})
public class App {
    private final Context _appContext;
    AppModule(Context appContext) {
        _appContext = appContext;
    }
    // ...
}

私のエスプレッソ テストでは、次のようなモック モジュールを挿入しようとしています。

public class MyActivityTest
    extends ActivityInstrumentationTestCase2<MyActivity> {

    public MyActivityTest() {
        super(MyActivity.class);
    }

    @Override
    public void setUp() throws Exception {
        super.setUp();
        ObjectGraph og = ((MyApplication) getActivity().getApplication()).getObjectGraph().plus(new TestNavigationModule());
        og.inject(getActivity());
    }

    public void test_SeeSpecialText() {
        onView(withId(R.id.my_text_view)).check(matches(withText(
            "Special Dummy Text")));
    }

    @Module(includes = NavigationModule.class,
            injects = { MyActivityTest.class, MyActivity.class },
            overrides = true,
            library = true)
    static class TestNavigationModule {

        @Provides
        @Singleton
        Navigator provideANavigator() {
            return new DummyNavigator(); // that returns "Special Dummy Text"
        }
    }
}

これはまったく機能していません。私の Espresso テストは実行されますが、TestNavigationModule は完全に無視されます... arr... :(

私は何を間違っていますか?Espresso でモジュールをモックするより良い方法はありますか? Robolectric、Mockito などが使用されている例を検索して見ました。しかし、純粋な Espresso テストが必要なだけで、モジュールをモック モジュールと交換する必要があります。どうすればいいですか?

編集:

そこで、静的テスト モジュール リスト定義を作成し、null をチェックしてから Application クラスに追加する @ user3399328 アプローチを採用しました。ただし、クラスのテスト注入バージョンをまだ取得していません。しかし、私のエスプレッソライフサイクルではなく、短剣テストモジュールの定義に問題がある可能性があります。私が仮定している理由は、デバッグ ステートメントを追加し、静的テスト モジュールがアプリケーション クラスへの挿入時に空ではないことを発見したためです。私が間違っている可能性があることの方向性を教えてください。私の定義のコードスニペットは次のとおりです。

私のアプリケーション:

@Override
public void onCreate() {
    // ...
    mObjectGraph = ObjectGraph.create(Modules.list(this));
    // ...   
}

モジュール:

public class Modules {

    public static List<Object> _testModules = null;

    public static Object[] list(MyApplication app) {
        //        return new Object[]{ new AppModule(app) };
        List<Object> modules = new ArrayList<Object>();
        modules.add(new AppModule(app));

        if (_testModules == null) {
            Log.d("No test modules");
        } else {
            Log.d("Test modules found");
        }

        if (_testModules != null) {
            modules.addAll(_testModules);
        }

        return modules.toArray();
    }
}   

私のテストクラス内の変更されたテストモジュール:

@Module(overrides = true, library = true)
public static class TestNavigationModule {

    @Provides
    @Singleton
    Navigator provideANavigator()() {
        Navigator navigator = new Navigator();
        navigator.setSpecialText("Dummy Text");
        return navigator;
    }
}
4

4 に答える 4

8

マットが述べたように、アクティビティの実際の注入コードが実行されると、特別なオブジェクトグラフによって注入された変数が消去されます。

これを機能させるには 2 つの方法があります。

簡単な方法: アクティビティで public static 変数を作成して、テストでオーバーライド モジュールを割り当て、null でない場合は実際のアクティビティ コードに常にこのモジュールを含めることができるようにします (これはテストでのみ発生します)。アプリケーションではなく、アクティビティの基本クラスに対する私の回答に似ています。

より長く、おそらくより良い方法: コードをリファクタリングして、ActivityInjectHelper のような 1 つのクラスですべてのアクティビティ インジェクション (およびさらに重要なグラフ作成) が行われるようにします。テスト パッケージで、同じメソッドを実装するまったく同じパッケージ パスを持つ ActivityInjectHelper という名前の別のクラスを作成しますが、テスト モジュールも追加します。テスト クラスが最初に読み込まれるため、アプリケーションはテスト用の ActivityInjectHelper を使用して実行されます。ここでも、別のクラスに対する私の答えに似ています。

アップデート:

私はあなたがより多くのコードを投稿したように見えますが、それはほぼ機能していますが、葉巻はありません. アクティビティとアプリケーションの両方で、onCreate() を実行する前にテスト モジュールを挿入する必要があります。アクティビティ オブジェクト グラフを扱う場合、テストの getActivity() の前であればいつでも問題ありません。アプリケーションを扱うときは、 setUp() が実行されるまでに onCreate() がすでに呼び出されているため、少し難しくなります。幸いなことに、テストのコンストラクターで実行すると機能します。その時点ではアプリケーションは作成されていません。これについては、最初のリンクで簡単に説明します。

于 2014-04-23T13:39:36.757 に答える
1

getActivity を呼び出すと、実際にはプロセスで onCreate を呼び出すアクティビティが開始されます。これは、テスト モジュールをグラフに追加して使用するのに間に合わないことを意味します。activityInstrumentationTestcase2 を使用すると、実際にはアクティビティ スコープで適切に注入できません。アプリケーションを使用してアクティビティに依存関係を提供し、アクティビティが使用するモック オブジェクトをアプリケーションに挿入することで、この問題を回避しました。理想的ではありませんが、機能します。Otto のようなイベント バスを使用して、依存関係を提供できます。

于 2014-04-23T08:43:54.557 に答える
0

編集:以下の投稿フォームhttp://systemdotrun.blogspot.co.uk/2014/11/android-testing-with-dagger-retrofit.html

ActivityEspresso + Dagger を使用してテストするには、以下を実行しました

@ user3399328 からの回答に触発されてDaggerHelper、 Application クラス内にクラスがあり、これにより、テスト ケースはモックを提供@Providerする Test を使用して s をオーバーライドできます。@Modulesに限って

1) これは、testCasesgetActivity()呼び出しが行われる前に行われます (inject 呼び出しは、アクティビティ内で発生するためActivity.onCreate)

2) tearDown は、オブジェクト グラフからテスト モジュールを削除します。

以下に例を示します。

注: これは理想的ではありません。これは、IoC にファクトリ メソッドを使用する場合と同様の落とし穴があるためです。ただし、少なくともこの方法では、テスト対象のシステムを通常の状態に戻すために、tearDown() を 1 回呼び出すだけです。

DaggerHelper私のApplicationクラスの中

public static class DaggerHelper
{
    private static ObjectGraph sObjectGraph;

    private static final List<Object> productionModules;

    static
    {
        productionModules = new ArrayList<Object>();
        productionModules.add(new DefaultModule());
    }

    /**
     * Init the dagger object graph with production modules
     */
    public static void initProductionModules()
    {
        initWithModules(productionModules);
    }

    /**
     * If passing in test modules make sure to override = true in the @Module annotation
     */
    public static void initWithTestModules(Object... testModules)
    {
        initWithModules(getModulesAsList(testModules));
    }

    private static void initWithModules(List<Object> modules)
    {
        sObjectGraph = ObjectGraph.create(modules.toArray());
    }

    private static List<Object> getModulesAsList(Object... extraModules)
    {
        List<Object> allModules = new ArrayList<Object>();
        allModules.addAll(productionModules);
        allModules.addAll(Arrays.asList(extraModules));
        return allModules;
    }

    /**
     * Dagger convenience method - will inject the fields of the passed in object
     */
    public static void inject(Object object) {
        sObjectGraph.inject(object);
    }
}

私のテストクラス内の私のテストモジュール

@Module (
        overrides = true,
        injects = ActivityUnderTest.class
)
static class TestDataPersisterModule {
    @Provides
    @Singleton
    DataPersister provideMockDataPersister() {
        return new DataPersister(){
            @Override
            public void persistDose()
            {
                throw new RuntimeException("Mock DI!"); //just a test to see if being called
            }
        };
    }
}

試験方法

public void testSomething()
{ 
     MyApp.DaggerHelper.initWithTestModules(new TestDataPersisterModule());
     getActivity();
     ...
 }

取り壊す

@Override
public void tearDown() throws Exception
{
    super.tearDown();
    //reset
    MyApp.DaggerHelper.initProductionModules();
}
于 2014-08-19T11:35:09.720 に答える