参考&使用客观化实体模型进行模拟测试时的关键问题

时间:2015-02-17 09:57:39

标签: java google-app-engine objectify

我不确定我是否应该坚持不要这样做,但我通常习惯于嘲笑我的应用程序的很多方面,并且当我在我的实体中使用Ref和Keys时会遇到问题。

我已经跟踪了docs中你在实体中使用ref的模式,但是你屏蔽了ref,所以你可以访问它引用的驱动程序对象,如下所示:

@Entity
class Car {
    @Id Long id;
    @Load Ref<Person> driver;    // Person is an @Entity

    public Person getDriver() { return driver.get(); }
    public void setDriver(Person value) { driver = Ref.create(value); }
}

我的特殊情况是使用导入器,我正在解析xml并构建我的实体以准备保存它们。因为我的单元测试只是实际测试xml的导入和解析我模拟了我的dao实现,所以我实际上并没有使用数据存储区。

当我调用setDriver时,我会遇到问题,这只会创建一个Ref对象。然后我使用getDriver方法,该方法将返回null,因为Ref.get直接依赖于数据存储区。

有没有人遇到过这个问题,有没有办法创建一个模拟Ref对象?我在考虑没有在我的实体中直接引用Ref,而是引用了一个可以提供我在测试中可以控制的Ref的辅助类?

3 个答案:

答案 0 :(得分:2)

选项的简短列表是:

  1. 使用PowerMock
  2. 添加您自己的RefCreator对象
  3. 使用虚假数据存储而不是模拟
  4. 我从不嘲笑数据存储层。本地单元测试线束是GAE最好的东西之一;它为您提供了数据存储的全功能伪造:

    https://cloud.google.com/appengine/docs/java/tools/localunittesting

答案 1 :(得分:0)

我将Ref包装在番石榴供应商中,以避免在我的pojos单元测试过程中依赖Objectify。供应商以与参考

类似的方式转换为数据存储区密钥

此课程主要是从Objectify RefTranslatorFactory

复制而来
public class RefSupplierTranslatorFactory
    extends ValueTranslatorFactory<Supplier<?>, com.google.appengine.api.datastore.Key> {
@SuppressWarnings({ "unchecked", "rawtypes" })
public RefSupplierTranslatorFactory() {
    super((Class) Supplier.class);
}

@Override
protected ValueTranslator<Supplier<?>, com.google.appengine.api.datastore.Key> createValueTranslator(
        TypeKey<Supplier<?>> tk, CreateContext ctx, Path path) {

    final LoadConditions loadConditions = new LoadConditions(tk.getAnnotation(Load.class));

    return new ValueTranslator<Supplier<?>, com.google.appengine.api.datastore.Key>(
            com.google.appengine.api.datastore.Key.class) {

        @Override
        protected Supplier<?> loadValue(com.google.appengine.api.datastore.Key value, LoadContext ctx, Path path)
                throws SkipException {
            Ref<Object> ref = ctx.loadRef(Key.create(value), loadConditions);
            return new RefSupplier(ref);
        }

        @Override
        protected com.google.appengine.api.datastore.Key saveValue(Supplier<?> value, boolean index,
                SaveContext ctx, Path path) throws SkipException {
            return ctx.saveRef(Ref.create(value.get()), loadConditions);
        }
    };
}

public static class RefSupplier
        implements Serializable, Supplier<Object> {
    private static final long serialVersionUID = 1L;
    final private Ref<?> ref;

    public RefSupplier(Ref<?> ref) {
        this.ref = ref;
    }

    @Override
    public Object get() {
        return ref.get();
    }

}
}

说我有以下Pojos:

@Entity
public static class CarWithSupplier {
    @Id
    Long id;
    Supplier<SteeringWheel> steeringWheel;
    List<Supplier<Tire>> tires;
}

@Entity
public static class SteeringWheel {
    @Id
    Long id;
}

@Entity
public static class Tire {
    @Id
    Long id;
}

我可以在不依赖Objectify的情况下运行单元测试:

@Test
public void testSupplier() {
    CarWithSupplier car = carWithSupplier();
    assertNotNull(car.steeringWheel);
    assertNotNull(car.tires);
    assertEquals(2, car.tires.size());
}

protected CarWithSupplier carWithSupplier() {
    CarWithSupplier car = new CarWithSupplier();
    car.steeringWheel = Suppliers.ofInstance(steeringWheel());
    final Supplier<Tire> leftFrontTire = Suppliers.ofInstance(tire());
    final Supplier<Tire> rightFrontTire = Suppliers.ofInstance(tire());
    car.tires = ImmutableList.of(leftFrontTire, rightFrontTire);
    return car;
}

扩展单元测试,但在测试设置期间设置必要的客观化资源我能够对数据存储区运行相同的单元测试:

@Before
public void setUpObjectify() throws Exception {
    helper.setUp();
    closeable = ObjectifyService.begin();
    final ObjectifyFactory factory = ObjectifyService.factory();
    factory.getTranslators().add(new RefSupplierTranslatorFactory());
    factory.register(CarWithSupplier.class);
    factory.register(SteeringWheel.class);
    factory.register(Tire.class);
}

@Override
protected CarWithSupplier carWithSupplier() {
    final CarWithSupplier car = super.carWithSupplier();
    final Objectify ofy = ObjectifyService.ofy();
    Key<CarWithSupplier> key = ofy.save().entity(car).now();
    return ofy.load().key(key).now();
}

@Override
protected Tire tire() {
    final Tire tire = super.tire();
    ObjectifyService.ofy().save().entity(tire).now();
    return tire;
}

@Override
protected SteeringWheel steeringWheel() {
    final SteeringWheel steeringWheel = super.steeringWheel();
    ObjectifyService.ofy().save().entity(steeringWheel).now();
    return steeringWheel;
}

我的pojos的单元测试很有价值,因为它们最初使用来自第三方Web API服务(使用Gson)的JSON响应进行填充。我发现将Gson解析测试与测试客观数据存储功能分开是有价值的。我稍后在集成测试期间完全测试它们。

我还没有广泛使用它,所以我欢迎来自@stickfigure的输入,如果这可能导致问题或以其他方式消除直接使用Ref的优势。

答案 2 :(得分:0)

我写了code to mock Objectify's Key and Ref classes here

要使用:

Ref<MyEntity> ref = MockObjectify.ref(myEntity);

来源:

package present.objectify;

import com.google.appengine.api.datastore.KeyFactory;
import com.google.apphosting.api.ApiProxy;
import com.google.common.cache.LoadingCache;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.ObjectifyFactory;
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.impl.KeyMetadata;
import com.googlecode.objectify.impl.Path;
import com.googlecode.objectify.impl.translate.CreateContext;
import java.util.Collections;
import java.util.Map;
import java.util.function.Supplier;
import present.engine.Caches;

/**
 * Creates Objectify mocks.
 *
 * @author Bob Lee
 */
public class MockObjectify {

  /** Creates a reference to the given instance. */
  public static <T> Ref<T> ref(T instance) {
    return new Ref<T>() {
      @Override public T get() {
        return instance;
      }

      @Override public boolean isLoaded() {
        return true;
      }

      @Override public Key<T> key() {
        return MockObjectify.key(instance);
      }
    };
  }

  /** Creates a key with a mock application ID. */
  public static <T> Key<T> key(T instance) {
    @SuppressWarnings("unchecked")
    KeyMetadata<T> metadata = (KeyMetadata<T>) keyMetadatas.getUnchecked(instance.getClass());
    return inMockEnvironment(() -> Key.create(metadata.getRawKey(instance)));
  }

  /** Creates a key with a mock application ID. */
  public static <T> Key<T> key(Class<? extends T> kindClass, long id) {
    KeyMetadata<T> metadata = keyMetadata(kindClass);
    return inMockEnvironment(() -> Key.create(KeyFactory.createKey(metadata.getKind(), id)));
  }

  /** Creates a key with a mock application ID. */
  public static <T> Key<T> key(Class<? extends T> kindClass, String name) {
    KeyMetadata<T> metadata = keyMetadata(kindClass);
    return inMockEnvironment(() -> Key.create(KeyFactory.createKey(metadata.getKind(), name)));
  }

  /** Creates a key with a mock application ID. */
  public static <T> Key<T> key(Key<?> parent, Class<? extends T> kindClass, long id) {
    KeyMetadata<T> metadata = keyMetadata(kindClass);
    return inMockEnvironment(() -> Key.create(KeyFactory.createKey(parent.getRaw(), metadata.getKind(), id)));
  }

  /** Creates a key with a mock application ID. */
  public static <T> Key<T> key(Key<?> parent, Class<? extends T> kindClass, String name) {
    KeyMetadata<T> metadata = keyMetadata(kindClass);
    return inMockEnvironment(() -> Key.create(KeyFactory.createKey(parent.getRaw(), metadata.getKind(), name)));
  }

  private static final ObjectifyFactory factory = new ObjectifyFactory();

  private static final LoadingCache<Class<?>, KeyMetadata<?>> keyMetadatas = Caches.create(
      type -> new KeyMetadata<>(type, new CreateContext(factory), Path.root()));

  @SuppressWarnings("unchecked")
  private static <T> KeyMetadata<T> keyMetadata(Class<? extends T> clazz) {
    return (KeyMetadata<T>) keyMetadatas.getUnchecked(clazz);
  }

  private static <T> T inMockEnvironment(Supplier<T> supplier) {
    ApiProxy.Environment original = ApiProxy.getCurrentEnvironment();
    try {
      ApiProxy.setEnvironmentForCurrentThread(mockEnvironment);
      return supplier.get();
    } finally {
      ApiProxy.setEnvironmentForCurrentThread(original);
    }
  }

  private static final ApiProxy.Environment mockEnvironment = new ApiProxy.Environment() {
    @Override public String getAppId() {
      return "mock";
    }

    @Override public String getModuleId() {
      throw new UnsupportedOperationException();
    }

    @Override public String getVersionId() {
      throw new UnsupportedOperationException();
    }

    @Override public String getEmail() {
      throw new UnsupportedOperationException();
    }

    @Override public boolean isLoggedIn() {
      throw new UnsupportedOperationException();
    }

    @Override public boolean isAdmin() {
      throw new UnsupportedOperationException();
    }

    @Override public String getAuthDomain() {
      throw new UnsupportedOperationException();
    }

    @Override public String getRequestNamespace() {
      throw new UnsupportedOperationException();
    }

    @Override public Map<String, Object> getAttributes() {
      return Collections.emptyMap();
    }

    @Override public long getRemainingMillis() {
      throw new UnsupportedOperationException();
    }
  };
}