如何在简单的游戏设置中正确测试 Android 模型 (MVP)?

How to properly test Android model (MVP) in simple game setup?

我正在做一个简单的游戏,有一些关于模型的问题setup/testing!如果可能的话,我正在尝试遵循 MVP 模式。

以下是我的一些模型classes的简化版本,主要是处理"logic".

的那些
public class GameManager implements LocationManagerDelegate {

    private LocationManager mLocationManager;
    private BattleManager mBattleManager;

    public GameManager(Location initialLocation) {
        mLocationManager = new LocationManager(initialLocation, self);
        mBattleManager = new BattleManager();
    }

    public void setLocation(Location location) {
        mLocationManager.setLocation(location);
    }

    public void handleLocationAction(Action action) {
        if (action.type == BATTLE) {
            mBattleManager.startBattle();
        }
    }
}

public class LocationManager {

    private LocationManagerDelegate mDelegate;
    private Location mLocation;

    public LocationManager(Location initialLocation, LocationManagerDelegate delegate) {
        mLocation = initialLocation;
        mDelegate = delegate;
    }

    public void setLocation(Location location) {
        mLocation = location;

        Action action = location.getRandomAction();

        mDelegate.handleLocationAction(action);
    }
}

public interface LocationManagerDelegate {
    void handleLocationAction(Action action);
}

public class BattleManager {

    public BattleManager() {
    }

    public void startBattle() {
        // Do stuff...
    }
}

我有兴趣正确测试这些 classes。我可以轻松测试 LocationManagerBattleManager - 它们是分开的,并且我可以在需要时为代表使用模拟接口(即 LocationManagerDelegate

但是 GameManager 呢?它在自己的 class 中创建了具体的 LocationManager/BattleManager 实例,所以如果我想使用 GameManager 测试 classes 的整体逻辑流程,它实际上将测试 GameManager 以及 LocationManager/BattleManager 逻辑。我想不出一个干净的方法来拆分它...

也许这样更正确?

public class GameManager implements LocationManagerDelegate {

    private ILocationManager mLocationManager;
    private IBattleManager mBattleManager;

    public GameManager(Location initialLocation, ILocationManager locationManager, IBattleManager battleManager) {
        mLocationManager = locationManager;
        mLocationManager.setInitialLocation(initialLocation);
        mLocationManager.setDelegate(this);

        mBattleManager = battleManager;
        mBattleManager.setDelegate(this);
    }

    public void setLocation(Location location) {
        mLocationManager.setLocation(location);
    }

    public void handleLocationAction(Action action) {
        if (action.type == BATTLE) {
            mBattleManager.startBattle();
        }
    }
}

public interface ILocationManager {
    void setInitialLocation(Location initialLocation);
    void setDelegate(LocationManagerDelegate delegate);
    void setLocation(Location location);    
}

public class LocationManager implements ILocationManager {

    private LocationManagerDelegate mDelegate;
    private Location mLocation;

    public void setInitialLocation(Location initialLocation) {
        mLocation = initialLocation;
    }

    public void setDelegate(LocationManagerDelegate delegate) {
        mDelegate = delegate;
    }

    public void setLocation(Location location) {
        mLocation = location;

        Action action = location.getRandomAction();

        mDelegate.handleLocationAction(action);
    }
}

public interface LocationManagerDelegate {
    void handleLocationAction(Action action);
}

public interface IBattleManager {
    void startBattle();
}

public class BattleManager implements IBattleManager {

    public void startBattle() {
        // Do stuff...
    }
}

然后在我们实际创建 GameManager 实例的任何地方,我们都会做类似的事情:

ILocationManager locationManager = new LocationManager();
IBattleManager battleManager = new BattleManager();
Location initialLocation = ...;

GameManager manager = new GameManager(initialLocation, locationManager, battleManager);

这将允许我放入模拟 LocationManager/BattleManager 对象,但可能不会太大,因为这也暴露了 [=17] 的 "inner workings" =] class。它不一定是 public 我需要传递给这些其他经理 classes 等的信息...

那么最好做什么呢?有什么想法!

我会说你在这里有两个选择:

1.依赖项的构造函数注入

正如您在上面概述的那样。依赖项在 class 之外创建,并在创建时传入。这样你就可以在测试时传递你的模拟。如果您使用诸如 Dagger 之类的依赖项注入库,这也是您的做法。

2。将依赖包设为私有

由于 class 的测试与 class 本身位于同一个包中,因此它们可以访问包私有变量。在这种情况下,您将更改两个依赖项的访问级别:

public class GameManager implements LocationManagerDelegate {

    LocationManager mLocationManager;
    BattleManager mBattleManager;

    //...
}

然后根据您的测试,您可以执行以下操作:

@Before
public void setUp() throws Exception {

    gameManager = new GameManager();
    gameManager.mLocationManager = locationMock;
    gameManager.mBattleManager = battleMock;
}

这两种方法都需要权衡打开被测 class 的内部。就我个人而言,我目前更喜欢使用构造函数注入,因为我已经为 DI 设置了 classes。