Переважна палітура в Гісі


138

Я щойно почав грати з Guice, і я можу придумати те, що в тесті я просто хочу переокремити єдине в'язання. Думаю, я хотів би використати решту прив’язок рівня виробництва, щоб забезпечити все налаштування правильно і щоб уникнути дублювання.

Тож уявіть, у мене є наступний модуль

public class ProductionModule implements Module {
    public void configure(Binder binder) {
        binder.bind(InterfaceA.class).to(ConcreteA.class);
        binder.bind(InterfaceB.class).to(ConcreteB.class);
        binder.bind(InterfaceC.class).to(ConcreteC.class);
    }
}

І в моєму тесті я хочу лише перекрити InterfaceC, зберігаючи в інтерфейсі InterfaceA та InterfaceB, тому я хочу щось подібне:

Module testModule = new Module() {
    public void configure(Binder binder) {
        binder.bind(InterfaceC.class).to(MockC.class);
    }
};
Guice.createInjector(new ProductionModule(), testModule);

Я також спробував таке, не пощастило:

Module testModule = new ProductionModule() {
    public void configure(Binder binder) {
        super.configure(binder);
        binder.bind(InterfaceC.class).to(MockC.class);
    }
};
Guice.createInjector(testModule);

Хтось знає, чи можна робити те, що я хочу, чи я повністю гавкаю неправильне дерево ??

--- Продовження: Здавалося б, я можу досягти того, що хочу, якщо скористатись тегом @ImplementedBy в інтерфейсі, а потім просто забезпечити прив'язку в тестовому випадку, що добре працює, коли між 1-1 відображенням є інтерфейс та реалізація.

Крім того, після обговорення цього питання з колегою, здавалося б, ми б рухалися вниз шляхом перекриття всього модуля і гарантуємо, що наші модулі визначені правильно. Це здається, що це може спричинити проблеми, хоча там, де прив'язка не розміщена в модулі, і її потрібно перенести, таким чином, можливо, порушується навантаження тестів, оскільки прив'язки можуть бути недоступними для їх перегляду.


7
Як і фраза "гавкати неправильне дерево": D
Борис Павлович

Відповіді:


149

Можливо, це не відповідь, яку ви шукаєте, але якщо ви пишете одиничні тести, ви, мабуть, не повинні використовувати інжектор, а скоріше вводити макети або підроблені предмети вручну.

З іншого боку, якщо ви дійсно хочете замінити одне прив'язування, ви можете використовувати Modules.override(..):

public class ProductionModule implements Module {
    public void configure(Binder binder) {
        binder.bind(InterfaceA.class).to(ConcreteA.class);
        binder.bind(InterfaceB.class).to(ConcreteB.class);
        binder.bind(InterfaceC.class).to(ConcreteC.class);
    }
}
public class TestModule implements Module {
    public void configure(Binder binder) {
        binder.bind(InterfaceC.class).to(MockC.class);
    }
}
Guice.createInjector(Modules.override(new ProductionModule()).with(new TestModule()));

Деталі дивіться тут .

Але як Modules.overrides(..)рекомендує javadoc , ви повинні розробити свої модулі таким чином, щоб вам не потрібно було перекривати прив’язки. У наведеному прикладі ви могли це зробити, перемістивши прив’язку InterfaceCдо окремого модуля.


9
Дякую Альберту, що мене змушує якимось способом робити те, що я хочу. Це у виробничому випуску ще тхо! І це для інтеграційних тестів, а не одиничних тестів, і тому я хочу переконатися, що все інше будується правильно
tddmonkey

1
Я додав конкретний приклад до коду. Чи пройде вас далі?
Альберб

1
Якщо я не помиляюся, ovverideвтрачаючи належне Stage, роблячи це (тобто систематично використовується РОЗВИТОК).
pdeschen

4
Розмір має значення. Коли ваш графік залежності зростає, проводка вручну може бути дуже болючою. Також при зміні проводки вам потрібно вручну оновити всі місця вручну проводки. Переосмислення дозволяє впоратися з цим автоматично.
yoosiba

3
@pdeschen Це помилка в Guice 3, яку я виправив у Guice 4.
Tavian Barnes

9

Чому б не використати спадщину? Ви можете перекрити ваші конкретні прив'язки в overrideMeметоді, залишаючи спільні реалізації у configureметоді.

public class DevModule implements Module {
    public void configure(Binder binder) {
        binder.bind(InterfaceA.class).to(TestDevImplA.class);
        overrideMe(binder);
    }

    protected void overrideMe(Binder binder){
        binder.bind(InterfaceC.class).to(ConcreteC.class);
    }
};

public class TestModule extends DevModule {
    @Override
    public void overrideMe(Binder binder) {
        binder.bind(InterfaceC.class).to(MockC.class);
    }
}

І нарешті створіть свій інжектор таким чином:

Guice.createInjector(new TestModule());

3
Схоже, @Overrideце не працює. Особливо, якщо це робиться методом @Providesчогось.
Сасанка Пангулурі

4

Якщо ви не хочете змінювати свій виробничий модуль і якщо у вас є структура проекту, схожа на Maven, як

src/test/java/...
src/main/java/...

Ви можете просто створити новий клас ConcreteCу своєму тестовому каталозі, використовуючи той самий пакет, що і для початкового класу. Потім Guice буде прив’язаний InterfaceCдо ConcreteCвашого тестового каталогу, тоді як всі інші інтерфейси будуть прив’язані до ваших виробничих класів.


2

Ви хочете використовувати Juckito, де ви можете оголосити свою власну конфігурацію для кожного тестового класу.

@RunWith(JukitoRunner.class)
class LogicTest {
    public static class Module extends JukitoModule {

        @Override
        protected void configureTest() {
            bind(InterfaceC.class).to(MockC.class);
        }
    }

    @Inject
    private InterfaceC logic;

    @Test
    public testLogicUsingMock() {
        logic.foo();
    }
}

1

У різних налаштуваннях у нас є кілька дій, визначених в окремих модулях. Діяльність, яку вводять, знаходиться в модулі бібліотеки Android, з власним визначенням модуля RoboGuice у файлі AndroidManifest.xml.

Установка виглядає приблизно так. У модулі бібліотеки є такі визначення:

AndroidManifest.xml:

<application android:allowBackup="true">
    <activity android:name="com.example.SomeActivity/>
    <meta-data
        android:name="roboguice.modules"
        android:value="com.example.MainModule" />
</application>

Потім у нас вводиться тип:

interface Foo { }

Деяка реалізація Foo за замовчуванням:

class FooThing implements Foo { }

MainModule налаштовує реалізацію FooThing для Foo:

public class MainModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(Foo.class).to(FooThing.class);
    }
}

І нарешті, діяльність, яка споживає Foo:

public class SomeActivity extends RoboActivity {
    @Inject
    private Foo foo;
}

У споживчому модулі додатків для Android ми хотіли б використовувати, SomeActivityале для тестування вводимо власну Foo.

public class SomeOtherActivity extends Activity {
    @Override
    protected void onResume() {
        super.onResume();

        Intent intent = new Intent(this, SomeActivity.class);
        startActivity(intent);
    }
}

Можна стверджувати, що піддавати обробку модуля клієнтській програмі, однак нам потрібно в основному приховати компоненти, які вводяться, тому що модуль бібліотеки є SDK, а викриття фрагментів має більші наслідки.

(Пам'ятайте, що це для тестування, тому ми знаємо внутрішню програму SomeActivity і знаємо, що вона споживає (видимий пакет) Foo).

Те, як я виявив, що працює, має сенс; використовувати тестування, що пропонується, для тестування :

public class SomeOtherActivity extends Activity {
    private class OverrideModule
            extends AbstractModule {

        @Override
        protected void configure() {
            bind(Foo.class).to(OtherFooThing.class);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        RoboGuice.overrideApplicationInjector(
                getApplication(),
                RoboGuice.newDefaultRoboModule(getApplication()),
                Modules
                        .override(new MainModule())
                        .with(new OverrideModule()));
    }

    @Override
    protected void onResume() {
        super.onResume();

        Intent intent = new Intent(this, SomeActivity.class);
        startActivity(intent);
    }
}

Тепер, коли SomeActivityце запущено, він отримає OtherFooThingдля свого введеного Fooекземпляра.

Це дуже специфічна ситуація, коли в нашому випадку OtherFooThing використовувався всередині для запису тестових ситуацій, тоді як FooThing використовувався за замовчуванням для всіх інших цілей.

Майте на увазі, ми які з допомогою #newDefaultRoboModuleнаших модульних тестів, і вона працює бездоганно.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.