Scala 或 Java 等同于 Ruby factory_girl 或 Python factory_boy (用于单元测试的便利工厂模式)

Scala or Java equivalent of Ruby factory_girl or Python factory_boy (convenient factory pattern for unit testing)

当我在动态类型Ruby或Python中编写单元测试时,我分别使用库factory_girl and factory_boy,以便方便地生成被测对象。它们提供了比直接对象实例化更方便的特性,例如:

在用静态类型 Java 或 Scala 编写单元测试时,我可以使用哪些 libraries/frameworks 来实现类似的效果和类似的好处?

提前致谢!

我在过去发现了一个类似的 Whosebug 问题 here,但不幸的是,最重要的答案是(转述),"there is no direct equivalent because that would be pointless"。

Mockito 有一种模式,它将递归地 return 动态生成模拟对象。如果你需要做非常复杂的模拟(例如构造或静态方法),你可以使用 PowerMock 来提供它,代价是进行字节码操作。

但我敦促您考虑更多地道的方法,而不是尝试编写 "Ruby/Python in Java/Scala" - 学习一门不会改变您的思维方式的语言是没有意义的。您/应该/利用类型系统来确保正确性,从而消除对多种测试的需求。您应该公开一个小接口,该接口本质上对 "traditional" 模拟(即在默认模式下使用 Easymock 或 Mockito)甚至手动存根友好。特别是在 Scala 中,寻找类似 http://michaelxavier.net/posts/2014-04-27-Cool-Idea-Free-Monads-for-Testing-Redis-Calls.html(Haskell,但想法是相同的)的东西,以允许您编写更高级别的测试,为您的方法正在做的事情提供非常有力的保证。

有一个名为 Fixture-Factory(https://github.com/six2six/fixture-factory) 的项目。它是基于 Factory-Girl 的想法。

您可以轻松创建对象的模板定义:

Fixture.of(Client.class).addTemplate("valid", new Rule(){{
  add("id", random(Long.class, range(1L, 200L)));
  add("name", random("Anderson Parra", "Arthur Hirata"));
  add("nickname", random("nerd", "geek"));
  add("email", "${nickname}@gmail.com");
  add("birthday", instant("18 years ago"));
  add("address", one(Address.class, "valid"));
}});

然后您就可以在测试中轻松使用它了:
Client client = Fixture.from(Client.class).gimme("valid");

有 Fritter Factory 库: https://github.com/equinox-one/fritterfactory

基本示例:

FritterFactory fritterFactory = new FritterFactory();
List<Person> persons = fritterFactory.buildList(Person.class, 3);

模型自定义示例:

FritterFactory fritterFactory = new FritterFactory();
MapMold personMold = new MapMold();
personMold.put(PersonSymbols.NAME, new FirstNameProvider());
personMold.put(PersonSymbols.SURNAME, new FirstNameProvider());
ModelProvider<Person> personProvider = new ModelProvider<Person>(fritterFactory, Person.class, personMold);
fritterFactory.addProvider(Person.class, createPersonProvider(fritterFactory));
List<Person> persons = fritterFactory.buildList(Person.class, 3);

这个库的一个有趣之处在于它也可以在 Android 中使用。

我创建了一个新的 java 框架 Factory Duke 以提供与 Factory_Girl https://github.com/regis-leray/factory_duke

相同的功能

真的好用,定义一个模板(本例默认)

FactoryDuke.define(User.class, u -> {
    u.setLastName("Scott");
    u.setName("Malcom");
    u.setRole(model.Role.USER);
});

并使用它

User user = FactoryDuke.build(User.class);

请阅读文档以查看高级功能

我编写(并发布)了一个用于定义对象工厂的库:https://github.com/arosini/wildstyle-generator

首先定义并注册生成器/工厂:

WildstyleGenerator.createObjectGenerator(Employee.class)
  .mapField("firstName", new FirstNameValueGenerator(true))
  .mapField("lastName", new LastNameValueGenerator(true))
  .mapField("yearsEmployed", 10)
  .mapField("hourlyWage", new DoubleValueGenerator(10.0, 100.0))
  .mapField("employeeType", new EnumValueGenerator(EmployeeType.class, true)) 
  .register();

然后就可以使用生成器/工厂了:

Employee employee = WildstyleGenerator.generate(Employee.class);

您可以在项目主页上阅读一些更高级的功能。注意必须启用断言,否则库将无法正常运行。

有一个豆母图书馆:https://github.com/keepcosmos/beanmother

这有助于使用用于测试的固定装置超级轻松地创建各种复杂的对象。 Beanmother 是 ObjectMother 模式的实现,也是 fixture 替换工具。

另一种解决方案,与 factory-bot(factory-girl 的新名称)非常相似 topicusoverheid/java-factory-bot你可以这样使用:

给定文章和用户的模型(getter 和 setter 被省略):

@Data
public class Article {
    private String title;
    private String content;
    private Date creationDate;
    private User author;
}

@Data
public class User {
    private String username;
    private String firstName;
    private String lastName;
    private String email;
}

你可以像这样定义工厂


class ArticleFactory extends Factory<Article> {
    Map<String, Attribute> attributes = [
            title       : value { faker.lorem().sentence() },
            content     : value { faker.lorem().paragraph() },
            creationDate: value { faker.date().past(20, TimeUnit.DAYS) },
            author      : hasOne(UserFactory)
    ]
}

class UserFactory extends Factory<User> {
    Map<String, Attribute> attributes = [
            username : value { faker.name().username() },
            firstName: value { faker.name().firstName() },
            lastName : value { faker.name().lastName() },
            email    : value { "${get("firstName")}.${get("lastName")}@example.com" }
    ]
}

并使用

创建对象
Article article = new ArticleFactory().build()
which generates an article with default random but sane attributes. Individual attributes or relations can be overriden by passing them in a map:

Article article = new ArticleFactory().build([title: "Foo", user: [username: "johndoe"]])