1

Flex をフロントエンドとして、Tomcat 6 で実行する Web アプリケーションを開発しています。TestNG でバックエンドをテストしています。現在、Java バックエンドで次のメソッドをテストしようとしています。

public class UserDAO extends AbstractDAO {
    (...)
    public UserPE login(String mail, String password) {
        UserPE dbuser = findUserByMail(mail); 
        if (dbuser == null || !dbuser.getPassword().equals(password))
            throw new RuntimeException("Invalid username and/or password");
        // Save logged in user
        FlexSession session = FlexContext.getFlexSession();
        session.setAttribute("user", dbuser);
        return dbuser;
    }
}    

このメソッドは、サーブレット コンテナーで実行する場合にのみ存在する FlexContext へのアクセスを必要とします (Flex を知らなくても気にしないでください。一般的には Java のモッキングに関する質問です)。そうしないと、呼び出し時に Nullpointer 例外が発生しsession.setAttribute()ます。残念ながら、FlexContext を外部から設定することはできません。これにより、テストから設定できるようになります。メソッド内で取得されるだけです。

メソッドまたはメソッドを含むクラスを変更せずに、モッキングフレームワークでこのメソッドをテストする最良の方法は何ですか? そして、このユースケースで最も簡単なフレームワークはどれですか (アプリでモックする必要があるものは他にほとんどなく、非常にシンプルです)。

申し訳ありませんが、それらすべてを自分で試して、これを機能させる方法を確認できましたが、いくつかの良いアドバイスでクイックスタートできることを願っています!

4

2 に答える 2

2

明らかな1つのアプローチは、FlexContextなどを挿入できるようにリファクタリングすることです。ただし、これが常に可能であるとは限りません。しばらく前、私が参加していたチームは、アクセスできない内部クラスのもの(コンテキストなど)をモックアウトしなければならない状況に陥りました。最終的に、静的呼び出しを含む個々のメソッドを効果的にモックできるjmockitというAPIを使用することになりました。

このテクノロジーを使用して、非常に厄介なサーバー実装を回避でき、ライブサーバーにデプロイしてブラックボックステストを行うのではなく、ハードコーディングされた効果的なサーバーテクノロジーをオーバーライドすることで、細かいレベルで単体テストを行うことができました。

jmockitのようなものを使用することについて私が行う唯一の推奨事項は、テストコードに、メインのモックフレームワークからのjomockitの明確なドキュメントと分離があることを確認することです(easymockまたはmockitoが私の推奨事項です)。そうしないと、パズルの各部分のさまざまな責任について開発者を混乱させるリスクがあります。これは通常、質の低いテストやうまく機能しないテストにつながります。理想的には、最終的には、jmockitコードをテストフィクスチャにラップして、開発者がそれについてさえ知らないようにします。ほとんどの人にとって、1つのAPIを処理するだけで十分です。

これは、IBMクラスのテストを修正するために使用したコードです。基本的に2つのことをする必要があります。

  1. メソッドによって返される独自のモックを注入する機能があります。
  2. 実行中のサーバーを探しに行ったコンストラクターを強制終了します。
  3. ソースコードにアクセスせずに上記を実行します。

コードは次のとおりです。

import java.util.HashMap;
import java.util.Map;

import mockit.Mock;
import mockit.MockClass;
import mockit.Mockit;

import com.ibm.ws.sca.internal.manager.impl.ServiceManagerImpl;

/**
 * This class makes use of JMockit to inject it's own version of the
 * locateService method into the IBM ServiceManager. It can then be used to
 * return mock objects instead of the concrete implementations.
 * <p>
 * This is done because the IBM implementation of SCA hard codes the static
 * methods which provide the component lookups and therefore there is no method
 * (including reflection) that developers can use to use mocks instead.
 * <p>
 * Note: we also override the constructor because the default implementations
 * also go after IBM setup which is not needed and will take a large amount of
 * time.
 * 
 * @see AbstractSCAUnitTest
 * 
 * @author Derek Clarkson
 * @version ${version}
 * 
 */

// We are going to inject code into the service manager.
@MockClass(realClass = ServiceManagerImpl.class)
public class ServiceManagerInterceptor {

    /**
     * How we access this interceptor's cache of objects.
     */
    public static final ServiceManagerInterceptor   INSTANCE                = new ServiceManagerInterceptor();

    /**
     * Local map to store the registered services.
 */
    private Map<String, Object>                         serviceRegistry = new HashMap<String, Object>();

    /**
     * Before runnin your test, make sure you call this method to start
     * intercepting the calls to the service manager.
     * 
     */
    public static void interceptServiceManagerCalls() {
        Mockit.setUpMocks(INSTANCE);
    }

    /**
     * Call to stop intercepting after your tests.
     */
    public static void restoreServiceManagerCalls() {
        Mockit.tearDownMocks();
    }

    /**
     * Mock default constructor to stop extensive initialisation. Note the $init
     * name which is a special JMockit name used to denote a constructor. Do not
     * remove this or your tests will slow down or even crash out.
     */
    @Mock
    public void $init() {
        // Do not remove!
    }

    /**
     * Clears all registered mocks from the registry.
     * 
     */
    public void clearRegistry() {
        this.serviceRegistry.clear();
    }

    /**
     * Override method which is injected into the ServiceManager class by
     * JMockit. It's job is to intercept the call to the serviceManager's
     * locateService() method and to return an object from our cache instead.
     * <p>
     * This is called from the code you are testing.
     * 
     * @param referenceName
     *           the reference name of the service you are requesting.
     * @return
     */
    @Mock
    public Object locateService(String referenceName) {
        return serviceRegistry.get(referenceName);
    }

    /**
     * Use this to store a reference to a service. usually this will be a
     * reference to a mock object of some sort.
     * 
     * @param referenceName
     *           the reference name you want the mocked service to be stored
     *           under. This should match the name used in the code being tested
     *           to request the service.
     * @param serviceImpl
     *           this is the mocked implementation of the service.
     */
    public void registerService(String referenceName, Object serviceImpl) {
        serviceRegistry.put(referenceName, serviceImpl);
    }

}

これが、テストの親として使用した抽象クラスです。

public abstract class AbstractSCAUnitTest extends TestCase {

protected void setUp() throws Exception {
    super.setUp();
    ServiceManagerInterceptor.INSTANCE.clearRegistry();
    ServiceManagerInterceptor.interceptServiceManagerCalls();
}

protected void tearDown() throws Exception {
    ServiceManagerInterceptor.restoreServiceManagerCalls();
    super.tearDown();
}

}
于 2011-01-16T10:45:18.743 に答える
2

Derek Clarkson のおかげで、FlexContext のモック化に成功し、ログインがテスト可能になりました。残念ながら、私が見る限り、それはJUnitでのみ可能です(TestNGのすべてのバージョンをテストしましたが成功しませんでした-JMockit javaagentはTestNGが好きではありません。これこの問題を参照してください)。

だから、これは私が今やっている方法です:

public class MockTests {
    @MockClass(realClass = FlexContext.class)
    public static class MockFlexContext {
        @Mock
        public FlexSession getFlexSession() {
            System.out.println("I'm a Mock FlexContext.");
            return new FlexSession() {

                @Override
                public boolean isPushSupported() {
                    return false;
                }

                @Override
                public String getId() {
                    return null;
                }
            };
        }
    }

    @BeforeClass
    public static void setUpBeforeClass() throws Exception {
        Mockit.setUpMocks(MockFlexContext.class);
        // Test user is registered here
        (...)
    }

    @Test
    public void testLoginUser() {
        UserDAO userDAO = new UserDAO();
        assertEquals(userDAO.getUserList().size(), 1);
        // no NPE here 
        userDAO.login("asdf@asdf.de", "asdfasdf");
    }
}

さらにテストするには、セッションマップなどを自分で実装する必要があります。しかし、私のアプリと私のテストケースは非常に単純なので、それは問題ありません。

于 2011-01-16T12:29:06.857 に答える