{"id":25776503,"url":"https://github.com/leeonky/jfactory","last_synced_at":"2026-01-17T08:37:19.984Z","repository":{"id":45724702,"uuid":"284011611","full_name":"leeonky/jfactory","owner":"leeonky","description":null,"archived":false,"fork":false,"pushed_at":"2023-12-09T14:25:02.000Z","size":832,"stargazers_count":14,"open_issues_count":1,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-27T06:06:37.357Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Gherkin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/leeonky.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2020-07-31T10:42:19.000Z","updated_at":"2024-08-24T10:57:05.000Z","dependencies_parsed_at":"2023-10-19T14:29:28.752Z","dependency_job_id":"b99d4624-8473-4a44-b466-521396e0e032","html_url":"https://github.com/leeonky/jfactory","commit_stats":null,"previous_names":[],"tags_count":34,"template":false,"template_full_name":null,"purl":"pkg:github/leeonky/jfactory","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leeonky%2Fjfactory","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leeonky%2Fjfactory/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leeonky%2Fjfactory/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leeonky%2Fjfactory/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/leeonky","download_url":"https://codeload.github.com/leeonky/jfactory/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leeonky%2Fjfactory/sbom","scorecard":{"id":582813,"data":{"date":"2025-08-11","repo":{"name":"github.com/leeonky/jfactory","commit":"38422da96ebef29b6c3d4d8fcca878a3243b1ba9"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.4,"checks":[{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":9,"reason":"binaries present in source code","details":["Warn: binary detected: gradle/wrapper/gradle-wrapper.jar:1"],"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-20T19:37:38.981Z","repository_id":45724702,"created_at":"2025-08-20T19:37:38.981Z","updated_at":"2025-08-20T19:37:38.981Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28504370,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-17T06:57:29.758Z","status":"ssl_error","status_checked_at":"2026-01-17T06:56:03.931Z","response_time":85,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-02-27T06:01:25.636Z","updated_at":"2026-01-17T08:37:19.894Z","avatar_url":"https://github.com/leeonky.png","language":"Gherkin","funding_links":[],"categories":["测试"],"sub_categories":[],"readme":"# JFactory\n\n[![CircleCI](https://dl.circleci.com/status-badge/img/gh/leeonky/jfactory/tree/master.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/leeonky/jfactory/tree/master)\n[![coveralls](https://img.shields.io/coveralls/github/leeonky/jfactory/master.svg)](https://coveralls.io/github/leeonky/jfactory)\n[![Mutation testing badge](https://img.shields.io/endpoint?style=flat\u0026url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fleeonky%2Fjfactory%2Fmaster)](https://dashboard.stryker-mutator.io/reports/github.com/leeonky/jfactory/master)\n[![Lost commit](https://img.shields.io/github/last-commit/leeonky/jfactory.svg)](https://github.com/leeonky/jfactory)\n[![Maven Central](https://img.shields.io/maven-central/v/com.github.leeonky/jfactory.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.leeonky/jfactory)\n[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)\n[![Codacy Badge](https://app.codacy.com/project/badge/Grade/6fd6832505594ed09070add129b570a6)](https://www.codacy.com/gh/leeonky/jfactory/dashboard?utm_source=github.com\u0026amp;utm_medium=referral\u0026amp;utm_content=leeonky/jfactory\u0026amp;utm_campaign=Badge_Grade)\n[![Maintainability](https://api.codeclimate.com/v1/badges/62a8a3826b05eefd1f3b/maintainability)](https://codeclimate.com/github/leeonky/jfactory/maintainability)\n[![Code Climate issues](https://img.shields.io/codeclimate/issues/leeonky/jfactory.svg)](https://codeclimate.com/github/leeonky/jfactory/maintainability)\n[![Code Climate maintainability (percentage)](https://img.shields.io/codeclimate/maintainability-percentage/leeonky/jfactory.svg)](https://codeclimate.com/github/leeonky/jfactory/maintainability)\n\n---\n\n通过工厂方法创建具有某些默认属性测试数据的工具库，默认数据通过预定义数据的默认属性和关联属性实现，还可以定义数据Trait，然后创建具有某些Trait的测试数据\n\n# 快速开始\n创建具有默认属性值的对象，实例代码默认使用[Lombok](https://projectlombok.org/)自动生成属性访问起代码。\n```java\n@Getter\n@Setter\npublic class Bean {\n    private String stringValue;\n    private int intValue;\n}\n\nJFactory jFactory = new JFactory();\n\nBean bean = jFactory.create(Bean.class);\n// bean.stringValue: \"stringValue#1\"\n// bean.intValue: 1\nBean bean2 = jFactory.create(Bean.class);\n// bean.stringValue: \"stringValue#2\"\n// bean.intValue: 2\n```\n\n默认情况下属性值会根据属性名和属性所在类型的创建次数生成一个组合值，也可以在创建过程中给定一个输入值：\n```java\npublic class Bean {\n    private String stringValue;\n    private int intValue;\n}\n\nBean bean = jFactory.type(Bean.class).property(\"intValue\", 100).create();\n// bean.intValue: 100\n```\n\n# 自定义创建\n\n## 类型默认Spec\nJFactory通过Spec定义对象各个属性值的产生策略：\n```java\npublic class Bean {\n    private String stringValue;\n    private long nowEpochSecond;\n}\n\njFactory.factory(Bean.class).spec(instance -\u003e instance.spec()\n    .property(stringValue).value(\"Hello\")\n    .property(nowEpochSecond).value(() -\u003e Instant.now().getEpochSecond())\n);\n\nBean bean = jFactory.create(Bean.class);\n// bean.stringValue: Hello\n// bean.nowEpochSecond is epoch second\n```\n\nSpec的详细定义都是通过如下代码实现，并且每一项配置仅表示属性的缺省值信息。\n```java\nproperty(stringValue).value(\"Hello\")\n```\n之所以说是缺省值信息，是因为可以在最终创建对象时通过直接指定属性值的方式覆盖原先的任何Spec定义\n```java\nBean bean = jFactory.type(Bean.class).propery(\"stringValue\", \"Bye\").create();\n// bean.stringValue: \"Bye\"\n```\n- 指定类型的构造器\n\n如果有些数据类型没有默认构造器，则可以提供一个对象构造器：\n```java\npublic class Bean {\n    private int i;\n    public Bean(int i) {\n        this.i = i;\n    }\n};\n\njFactory.factory(Bean.class).constructor(instance -\u003e new Bean(instance.getSequence()))\njFactory.create(Bean.class);\n```\n- 定义数据Trait\n\n有时从测试的表达性而言，我们往往更关心创建具有某些特征的数据，而不是数据具有某些值的细节。比如：\n\n```java\npublic class Person {\n    private int age;\n    private String gender;\n};\n\nPerson person = jFactory.type(Person.class)\n    .property(\"age\", 20)\n    .property(\"gender\", \"MALE\").create()\n```\n可以预先定义类型的一些具名Spec，然后在构造数据时组合使用：\n```java\njFactory.factory(Person.class)\n    .spec(\"成年\", instance -\u003e instance.spec().property(\"age\").value(20))\n    .spec(\"男性\", instance -\u003e instance.spec().property(\"gender\").value(\"MALE\"))\n    );\n\nPerson person = jFactory.type(Person.class).traits(\"成年\", \"男性\").create();\n```\n\n## 用Java类定义Spec\n通过JFactory.factory(Class\u003c?\u003e)定义的Spec是这个类型的默认或者说全局Spec，如果一个类型需要有多种截然不同的Spec，则可以通过一个Java类来描述这个Spec：\n```java\npublic class Person {\n    private int age;\n    private String gender;\n};\n\npublic class 女人 extends Spec\u003cPerson\u003e {\n\n    @Override\n    public void main() {\n        property(\"gender\").value(\"FEMALE\");\n    }\n\n    @Trait\n    public 女人 老年的() {\n        property(\"age\").value(80);\n        return this;\n    }\n};\n\npublic class 男人 extends Spec\u003cPerson\u003e {\n\n    @Override\n    public void main() {\n        property(\"gender\").value(\"MALE\");\n    }\n}\n\njFactory.spec(女人.class).traits(\"老年的\").create()\n```\n其中main方法定义具体的Spec，@Trait注解表示定义Trait。JFactory提供了多种通过Spec类来创建对象的方式：\n```java\njFactory.createAs(女人.class);\njFactory.createAs(女人.class, 女人::老年的);\njFactory.spec(女人.class).create();\njFactory.spec(女人.class, 女人::老年的).create();\n```\n也可以事先注册规Spec，然后通过字符串引用：\n```java\njFactory.register(女人.class);\n\njFactory.createAs(\"老年的\", \"女人\");\njFactory.spec(\"女人\").traits(\"老年的\").create();\n```\n需要注意的是，通过类来描述Spec类似于继承，Spec类都会继承类型的默认全局Spec。比如：\n```java\npublic class Bean {\n    private String stringValue;\n}\n\npublic class ABean extends Spec\u003cBean\u003e {\n}\n\njFactory.factory(Bean.class).spec(instance -\u003e instance.spec()\n    .property(stringValue).value(\"string from base\")\n);\n\nBean bean = jFactory.createAs(ABean.class);\n// bean.stringValue: \"string from base\"\n```\n\n## 保存/查询创建过的数据\nJFactory会用一个实现了DataRepository接口的数据库按类型存储创建过的所有数据。并且支持按照条件查找出曾经创建过的对象\n```java\npublic class Bean {\n    private String stringValue;\n}\n\nBean bean1 = jFactory.type(Bean.class).property(\"stringValue\", \"str1\").create();\nBean bean2 = jFactory.type(Bean.class).property(\"stringValue\", \"str2\").create();\n\nCollection\u003cBean\u003e queryAll = jFactory.type(Bean.class).queryAll();\nBean query1 = jFactory.type(Bean.class).property(\"stringValue\", \"str1\").query();\nBean query2 = jFactory.type(Bean.class).property(\"stringValue\", \"str1\").query();\n// queryAll is [bean1, bean2]\n// query1 == bean1\n// query2 == bean2\n\njFactory.getDataRepository().clear();\n// clear DB\n```\n\n## 创建关联对象\n在有些测试场景，往往需要一个相对完整，但又不太在意细节的数据，比如有如下两个类型：\n```java\npublic class Product {\n    private String name;\n    private String color;\n}\n\npublic class Order {\n    private Product product;\n}\n```\n假如需要构造一个“有效“的订单，这里所谓有效是指Order.product属性不能为null，但其实又不关心具体是什么产品，之所以有效是为了不影响当前测试关注点以外被测系统的运行。\n通常通过依次创建对象然后手动关联。\n```java\nProduct product = jFactory.create(Product.class);\nOrder order = jFactory.type(Order.class).property(\"product\", product).create();\n```\n- 在Spec中指定子属性对象\n\nJFactroy提供了简便创建关联对象的方法可以在一个create调用中创建出具有级联关系的Order实例：\n```java\njFactory.factory(Order.class).spec(instance -\u003e instance.spec()\n        .property(\"product\").byFactroy());\n\nOrder order = jFactory.create(Order.class);\n//order.product is a default sub created object\n```\n- 通过输入属性指定\n\n还有一种方法是可以在创建时直接指定子对象的某个属性值来得到关联对象：\n```java\njFactory.type(Order.class).property(\"product.name\", \"book\").create();\n```\n与前者不同的是，JFactory首先尝试在曾经创建过的所有对象中线性搜索有没有满足name为book的Product，如果有就把那个Product赋值给Order的product属性，如果没有找到就自动构建一个name为book的Product，总之一定会给Order关联到一个name为book的Product。\n\n在指定属性值时，也可以在property中指定子属性对象创建的规格和特质：\n```java\npublic class AProduct extends Spec\u003cProduct\u003e {\n\n    @Trait\n    public AProduct 红色的() {\n        property(\"color\").value(\"red\");\n    }\n}\njFactory.register(AProduct.class);\njFactory.type(Order.class).property(\"product(红色的 AProduct).name\", \"book\").create();\n```\n注意：\n这里的Spec和Trait只能通过字符串的形式指定。在搜索已创建的对象做关联时不会比较备选对象的Spec和Trait，也就是以上代码只能保证创建出的Order的product的name属性为book。Trait和Spec要写在圆括号内，Trait写在前（可以组合写多个），Spec写在最后，中间用空格或英文逗号分割。 另外Product对象在创建后也会先于Order对象保存进数据库。\n\n\n- 强制创建子属性对象\n\n可以用!强制创建子对象而不是查找关联已有对象：\n```java\njFactory.type(Order.class).property(\"product(红色的 AProduct)!.name\", \"book\").create();\n```\n这样无论是否创建过name为book的Product，上面的代码总是会新创建一个name为book的Product，并关联到Order。\n\n- 引用当前对象\n\n如果想在创建对象时引用对象自己，比如：\n```java\npublic class Bean {\n    private Bean self;\n}\n```\n想要构造出bean.self = bean的场景，需要在Spec中引用当前所要创建的对象“实例”，不过这个实例是以Supplier\u003cObject\u003e形式提供的：\n```java\njFactory.factory(Bean.class).spec(instance -\u003e  instance.spec()\n    .property(\"self\").value(instance.reference())\n);\n\nBean bean = jFactory.create(Bean.class);\n// bean.self == bean\n```\n\n有些有父子关系的对象需要在子对象的某个属性引用父对象。比如：\n```java\npublic class Farther {\n    private Son son;\n}\n\npublic class Son {\n    private Farther farther;\n}\n```\n\n如果想创建出一个Father对象father，并且father.son.father是father，就需要在son中反向引用父对象。\n```java\njFactory.factory(Farther.class).spec(instance -\u003e instance.spec()\n    .property(\"son\").byFactroy()\n    .property(\"son\").reverseAssociation(\"father\")\n);\n```\n并且建立反向引用后，父子对象在创建后保存到数据库的次序会发生改变，没有反向关联的对象会先保存子对象，有反向关联关系的情况下会先保存父对象，再保存子对象。\n\n## 属性依赖\n\n有的业务数据需要属性之间有某种依赖关系，比如：\n\n```java\npublic class Expression {\n    private int number1, number2, sum;\n}\n```\n\n为了不让测试意外失败，默认创建出的对象必须满足sum = number1 + number2。JFactory支持创建属性依赖Spec：\n\n```java\njFactory.factory(Expression.class).spec(instance -\u003e instance.spec()\n    .property(\"sum\").dependsOn(asList(\"number1\", \"number2\"), numbers -\u003e (int)numbers[0] + (int)numbers[1])\n);\n\nExpression exp1 = jFactory.create(Expression.class);\nExpression exp2 = jFactory.type(Expression.class).property(\"number1\", 100).create();\nExpression exp3 = jFactory.type(Expression.class).property(\"number1\", 100).property(\"number2\", 200).create();\n```\n但这种依赖也不会永远有效。比如：\n```java\nExpression exp4 = jFactory.type(Expression.class).property(\"sum\", 300).create();\n```\n这实际上是强制指定了sum的值，因此依赖规则不再有效。\n\n## 属性连接\n\n有的业务需要多个数据的多个属性保持一致，比如：\n\n```java\npublic class Product {\n    private int price;\n}\n\npublic class Order {\n    private Product product;\n    private int total;\n}\n```\n\n从有效订单的角度讲，应该最大程度的保证创建出的Order对象的total属性和product.price相等。这个特性在JFactory中可以通过连接属性Spec实现：\n\n```java\njFactory.factory(Order.class ).spec(instance -\u003e instance.spec()\n    .property(\"product\").byFactroy()\n    .link(\"total\", \"product.price\")\n);\n```\n\n同样如果在创建时强制指定了不同的product.price和total属性值，这种连接也会失效。\n\n多个属性连接后最终的值是多少会根据原先各个属性Spec按如下的优先级得出：\n- 创建时赋予的属性值\n- 只读值（关联已创建过对象的某个属性）\n- 属性依赖\n- 通过value方法给定的值\n- 默认策略创建的值\n\n##### 属性依赖和连接属性都可以操作子属性，这可能会导致复杂的依赖关系甚至是循环依赖，这两个特性本身也存在部分局限，应尽量避免在多层对象中过多使用这两个特性。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleeonky%2Fjfactory","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fleeonky%2Fjfactory","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleeonky%2Fjfactory/lists"}