web-dev-qa-db-ja.com

単体テスト環境でのSpring Beanの再定義

アプリケーションの目的にはSpringを使用し、単体テストにはSpring Testingフレームワークを使用しています。ただし、小さな問題があります。アプリケーションコードは、クラスパスの場所(xmlファイル)のリストからSpringアプリケーションコンテキストをロードします。しかし、ユニットテストを実行するときは、Spring Beanの一部を本格的な実装クラスではなくモックにする必要があります。さらに、一部の単体テストでは一部のBeanをモックにしたいのに対し、他の単体テストでは他のBeanをモックにしたいので、アプリケーションのさまざまなレイヤーをテストしています。

つまり、アプリケーションコンテキストの特定のBeanを再定義し、必要に応じてコンテキストを更新したいということです。これを行いながら、1つ(または複数)の元のxml Bean定義ファイルにあるBeanのごく一部のみを再定義したいと思います。簡単な方法が見つかりません。 Springはユニットテストに適したフレームワークであると常に考えられているため、ここで何かを見逃しているに違いありません。

あなたはそれを行う方法についてのアイデアを持っていますか?

ありがとう。

50
Stas

spring bean.xmlの場所について、カスタムTestClassといくつかの簡単なルールを提案します。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
    "classpath*:spring/*.xml",
    "classpath*:spring/persistence/*.xml",
    "classpath*:spring/mock/*.xml"})
@Transactional
@TestExecutionListeners({
    DependencyInjectionTestExecutionListener.class,
    TransactionalTestExecutionListener.class,
    DirtiesContextTestExecutionListener.class})
public abstract class AbstractHibernateTests implements ApplicationContextAware 
{

    /**
     * Logger for Subclasses.
     */
    protected final Logger LOG = LoggerFactory.getLogger(getClass());

    /**
     * The {@link ApplicationContext} that was injected into this test instance
     * via {@link #setApplicationContext(ApplicationContext)}.
     */
    protected ApplicationContext applicationContext;

    /**
     * Set the {@link ApplicationContext} to be used by this test instance,
     * provided via {@link ApplicationContextAware} semantics.
     */
    @Override
    public final void setApplicationContext(
            final ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }
}

指定された場所にmock-bean.xmlがある場合、「通常の」場所にあるすべての「実際の」bean.xmlをオーバーライドします。通常の場所は異なる場合があります

しかし...アプリケーションが古くなると、モックBeanと非モックBeanを混在させることはできず、問題を追跡することは困難です。

18
Michael Pralow

スプリングがテストに適していると言われる理由の1つは、単体テストでnewまたはモックのものを簡単に作成できるためです。

別の方法として、次のセットアップを使用して大成功を収めましたが、あなたが望むものに非常に近いと思います、私は強くお勧めします:

異なるコンテキストで異なる実装を必要とするすべてのBeanについては、注釈ベースの配線に切り替えます。他はそのままにしておくことができます。

次の注釈セットを実装します

 <context:component-scan base-package="com.foobar">
     <context:include-filter type="annotation" expression="com.foobar.annotations.StubRepository"/>
     <context:include-filter type="annotation" expression="com.foobar.annotations.TestScopedComponent"/>
     <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
 </context:component-scan>

次に、live実装に@Repositoryを、stub実装に注釈を付けます@ StubRepository、@ TestScopedComponentを使用した単体テストフィクスチャにのみ存在する必要があるコード。さらにいくつかの注釈が必要になるかもしれませんが、これらは素晴らしい出発点です。

多くのspring.xmlがある場合は、基本的にコンポーネントスキャン定義のみを含むいくつかの新しいspring xmlファイルを作成する必要があります。通常、これらのファイルを通常の@ContextConfigurationリストに追加するだけです。この理由は、コンテキストスキャンのさまざまな構成で頻繁に終わるためです(私を信頼してください、あなたwillは、少なくとも1つ以上の注釈を追加します「Webテストを行っており、4つの関連する組み合わせがあります)

次に、基本的に

@ContextConfiguration(locations = { "classpath:/path/to/root-config.xml" })
@RunWith(SpringJUnit4ClassRunner.class)

このセットアップでは、notを使用して、スタブ/ライブデータの組み合わせを交互に設定できることに注意してください。私たちはこれを試しましたが、だれにも勧められない混乱をもたらしたと思います;)私たちは、スタブのフルセットまたはライブサービスのフルセットを配線します。

通常、依存関係が非常に重要なものに近いGUIをテストする場合、主に自動配線スタブの依存関係を使用します。コードのよりクリーンな領域では、より定期的な単体テストを使用します。

このシステムには、コンポーネントスキャン用の次のxmlファイルがあります。

  • 通常のウェブ制作向け
  • スタブのみでWebを起動する場合
  • 統合テスト用(junit内)
  • 単体テスト用(junit内)
  • selenium Webテスト用(junit内)

これは、アプリケーションを起動できる5つの異なるシステム全体の構成があることを意味します。注釈のみを使用するため、スプリングは、配線したい単体テストでも自動配線するのに十分高速です。これは伝統的なものではないことは知っていますが、本当に素晴らしいことです。

完全なライブセットアップで統合テストを実行し、1回または2回、実際にreallyを取得し、5つのライブワイヤリングと1つのモックを作成することにしました:

public class HybridTest {
   @Autowired
   MyTestSubject myTestSubject;


   @Test
   public void testWith5LiveServicesAndOneMock(){
     MyServiceLive service = myTestSubject.getMyService();
     try {
          MyService mock = EasyMock.create(...)
          myTestSubject.setMyService( mock);

           .. do funky test  with lots of live but one mock object

     } finally {
          myTestSubject.setMyService( service);
     }


   }
}

このために、テストの純粋主義者が私全体にいることを知っています。しかし、時には非常に実用的なソリューションであり、代替案が本当にreallyい場合に非常にエレガントになることがあります。繰り返しになりますが、通常はこれらの近郊のエリアです。

16
krosenvold

これを参照してください @ InjectedMock注釈付きのチュートリアル

それは私に多くの時間を節約しました。あなたはただ使う

@Mock
SomeClass mockedSomeClass

@InjectMock
ClassUsingSomeClass service

@Before
public void setUp() {
    MockitoAnnotations.initMocks(this);
}

そして、すべての問題が解決されます。 Mockitoは、スプリング依存性注入をモックに置き換えます。私は自分でそれを使用しましたが、うまく機能します。

7
Ev0oD

ここにいくつかの非常に複雑で強力なソリューションがリストされています。

しかし、Stasが要求したことを実現するFAR、FAR simplerの方法があります。これは、テストメソッドの1行のコード以外を変更する必要はありません。これは、自動テストされた依存関係、プライベートフィールド、および保護されたフィールドに対して、単体テストとSpring統合テストの両方で機能します。

ここにあります:

junitx.util.PrivateAccessor.setField(testSubject, "fieldName", mockObject);
6
Daniel Alexiuc

ルックアップをまったく必要としないユニットテストを作成することもできます。

@ContextConfiguration(locations = { "classpath:/path/to/test-config.xml" })
@RunWith(SpringJUnit4ClassRunner.class)
public class MyBeanTest {

    @Autowired
    private MyBean myBean; // the component under test

    @Test
    public void testMyBean() {
        ...
    }
}

これにより、実際の構成ファイルとテスト構成ファイルを簡単に組み合わせることができます。

たとえば、休止状態を使用する場合、sessionFactory Beanを1つの構成ファイルに(テストとメインアプリの両方で使用)、dataSource Beanを別の構成ファイルに(DriveManagerManagerDataSourceを使用して、メモリdb、もう一方はJNDIルックアップを使用する場合があります)。

しかし、必ず注意してください @ cletus's warning ;-)

4
toolkit

簡単です。単体テストにはカスタムアプリケーションコンテキストを使用します。または、まったく使用せず、手動でBeanを作成して注入します。

あなたのテストが少し広すぎるかもしれないように思えます。ユニットテストとは、ユニットをテストすることです。 Spring Beanは、ユニットのかなり良い例です。そのためにアプリケーションコンテキスト全体を必要とするべきではありません。あなたのユニットテストが非常に高いレベルであり、数百のBean、データベース接続などが必要な場合、非常に壊れやすいユニットテストがあり、それは非常に次の変更で壊れ、維持するのが難しく、実際にはそうではありません多くの価値を追加する。

3
cletus

テストアプリケーションコンテキストで import 機能を使用して、prod Beanをロードし、必要なBeanをオーバーライドできます。たとえば、私のprodデータソースは通常JNDIルックアップを介して取得されますが、テスト時にはDriverManagerデータソースを使用するため、アプリサーバーを起動してテストする必要はありません。

2
duffymo

テストコンテキストを使用する必要はありません(XMLまたはJavaベース)は関係ありません。Springブート1.4以降、新しい注釈が利用可能になりました @MockBean は、Spring Beanのモックとスパイのネイティブサポートを導入しました。

1
luboskrnac

OP以来、これはやって来ました: Springockito

0
Michael Wiles

おそらくあなたはあなたの豆に修飾子を使うことができますか?モックアップするBeanを別のアプリケーションコンテキストで再定義し、修飾子 "test"でラベル付けします。単体テストでは、Beanを配線する際に、モックアップを使用するための修飾子「test」を常に指定します。

0
Il-Bhima

spring-reinject は、Beanをモックで置き換えるように設計されています。

0

私も同じことをしたいと思っています。

現在使用しているメカニズムはかなり手動ですが、動作します。

たとえば、タイプYのBeanをモックアウトしたいとします。私たちが行うことは、インターフェイスを実装する依存関係を持つすべてのBean、「IHasY」です。このインターフェースは

interface IHasY {
   public void setY(Y y);
}

次に、テストでutilメソッドを呼び出します...

 public static void insertMock(Y y) {
        Map invokers = BeanFactory.getInstance().getFactory("core").getBeansOfType(IHasY.class);
        for (Iterator iterator = invokers.values().iterator(); iterator.hasNext();) {
            IHasY invoker = (IHasY) iterator.next();
            invoker.setY(y);
        }
    }

この新しい依存関係を注入するためだけにxmlファイル全体を作成したくないので、これが気に入っています。

Xml構成ファイルを作成する場合は、モックBeanを使用して新しいファクトリを作成し、デフォルトファクトリをこのファクトリの親にすることをお勧めします。次に、新しい子ファクトリーからすべてのBeanをロードしてください。これを行うと、BeanのIDが同じ場合、サブファクトリーが親ファクトリーのBeanをオーバーライドします。

今、私のテストでプログラムでファクトリを作成できたら、それは素晴らしいことです。 xmlを使用するのは面倒です。コードでその子ファクトリを作成したいと考えています。その後、各テストでファクトリを希望どおりに構成できます。そのような工場が機能しない理由はありません。

0
Michael Wiles