Upgrade to Pro — share decks privately, control downloads, hide ads and more …

依存関係のテストだけじゃないArchUnitのこんな使い方

YutaSaito
August 26, 2022

 依存関係のテストだけじゃないArchUnitのこんな使い方

2022/8/26(金) JJUGナイトセミナー LT資料
私のプロダクトではArchUnitでこんなテストをしていますという紹介です。

YutaSaito

August 26, 2022
Tweet

More Decks by YutaSaito

Other Decks in Technology

Transcript

  1. +6OJUͰͷ࢖༻ํ๏ @AnalyzeClasses(packages = "com.example.archunit") public class ClassNamingTest { @ArchTest public

    static final ArchRule Ϋϥε໊͕ControllerͰऴΘΔ = classes() .that() .resideInAPackage("com.example.archunit.controller..") .should() .haveSimpleNameEndingWith("Controller"); } ςετΫϥεʹ!"OBMZ[F$MBTTFTΛ෇༩ͯ͠ςετର৅ͷύοέʔδΛࢦఆ ͢Δɻ ςετର৅ϑΟʔϧυ ·ͨ͸ϝιου ʹ!"SDI5FTUΛ෇༩͢Δɻ
  2. +6OJUͰͷ࢖༻ํ๏ Architecture Violation [Priority: MEDIUM] - Rule 'classes that reside

    in a package 'com.example.archunit.controller..' should have simple name ending with 'Controller'' was violated (1 times): Class <com.example.archunit.controller.TestAction> does not have simple name ending with 'Controller' in (TestAction.java:0) ࣮ߦ͠ϧʔϧʹҧ൓͢Δίʔυ͕ଘࡏ͢Δ৔߹ʹ͸ɺ+6OJUͷςετ͕ࣦഊ͢ Δɻ
  3. ϨΠϠʔؒͷґଘؔ܎Λςετ͢Δ $POUSPMMFS 4FSWJDF 3FQPTJUPSZ EFQFOET EFQFOET layeredArchitecture() .consideringAllDependencies() .layer("Controller").definedBy("com.example.archunit.controller..") .layer("Service").definedBy("com.example.archunit.service..")

    .layer("Repository").definedBy("com.example.archunit.repository..") .whereLayer("Controller").mayNotBeAccessedByAnyLayer() .whereLayer("Service").mayOnlyBeAccessedByLayers("Controller") .whereLayer(“Repository").mayOnlyBeAccessedByLayers("Service"); ϨΠϠʔؒͷґଘؔ܎ͷςετίʔυͷॻ͖ํ
  4. 4USJOH6UJMTͷϥΠϒϥϦΛݶఆ private static DescribedPredicate<? super JavaClass> canDependPackageFindByClassName( String className, String

    allowPackageName) { return new DescribedPredicate<>(allowPackageName + "." + className + "ͷΈར༻Մೳ") { @Override public boolean test(JavaClass input) { String simpleName = input.getSimpleName(); String packageName = input.getPackageName(); if (!simpleName.equals(className)) { return true; } return packageName.equals(allowPackageName); } }; } w %FTDSJCFE1SFEJDBUFΛར༻ͨ͠ΧελϜϧʔϧΛ࡞੒
  5. 4USJOH6UJMTͷϥΠϒϥϦΛݶఆ public static final ArchRule StringUtils͸ApacheCommonsLang3ͷΈΛར༻Մೳ = classes() .should() .onlyDependOnClassesThat(

    canDependPackageFindByClassName("StringUtils", "org.apache.commons.lang3")); w ࡞੒ͨ͠ΧελϜϧʔϧΛར༻ͯ͠4USJOH6UJMT͸"QBDIF$PNNPOT-BOH ͷΈʹݶఆ Architecture Violation [Priority: MEDIUM] - Rule 'classes should only depend on classes that org.apache.commons.lang3.StringUtilsͷΈར༻Մೳ' was violated (1 times): Method <com.example.archunit.error.ApacheCommonsLangTest.test()> calls method <org.apache.commons.lang.StringUtils.isEmpty(java.lang.String)> in (ApacheCommonsLangTest.java:8)
  6. 4QSJOHͷΞϊςʔγϣϯͷར༻ՕॴΛݶఆ w ΞϊςʔγϣϯΛ෇༩ग़དྷΔ৔ॴΛݶఆ͢Δ @ArchTest public static final ArchRule TransactionalΞϊςʔγϣϯ͸ServiceʹͷΈઃఆՄೳ =

    methods() .that() .areDeclaredInClassesThat() .resideOutsideOfPackage("com.example.archunit.service..") .should() .notBeAnnotatedWith(Transactional.class); @ArchTest public static final ArchRule ControllerΞϊςʔγϣϯ͸ControllerͷΈʹઃఆՄೳ = classes() .that() .resideOutsideOfPackage("com.example.archunit.controller..") .should() .notBeAnnotatedWith(RestController.class);
  7. $POUSPMMFSͷύεΛࢦఆ w "SDI$POEJUJPOΛར༻ͨ͠ΧελϜϧʔϧΛ࡞੒ private static ArchCondition<JavaClass> havePathStartingWith(String apiVersion) { return

    new ArchCondition<JavaClass>("APIͷpath͸ /" + apiVersion + “/ Ͱ࢝ΊΔඞཁ͕͋Γ·͢ɻ") { @Override public void check(JavaClass item, ConditionEvents events) { RequestMapping requestMapping = item.getAnnotationOfType(RequestMapping.class); Set<String> pathList = Set.of(ArrayUtils.addAll(requestMapping.value(), requestMapping.path())); boolean isError = pathList.stream().anyMatch(path -> !path.startsWith("/" + apiVersion + "/")); if (isError) { String message = String.format( "Class <%s> ͷ@RequstMapping ʹ͸ /%s ΛؚΊ͍ͯͩ͘͞ɻ (%s)”, item.getName(), apiVersion, item.getSimpleName() + ".java:0"); events.add(SimpleConditionEvent.violated(item, message)); } } }; }
  8. $POUSPMMFSͷύεΛࢦఆ w ࡞੒ͨ͠ΧελϜϧʔϧΛར༻ͯ͠ςετ @ArchTest public static final ArchRule v1ύοέʔδ಺ͷPath͸v1Ͱ࢝·Δ =

    classes() .that() .resideInAPackage("com.example.archunit.controller.v1..") .should() .beAnnotatedWith(RestController.class) .andShould(havePathStartingWith("v1"));
  9. 4QSJOHEPDPQFOBQJͷهड़Λڧ੍ w 0QFO"1*ͷهड़Λඞਢͱ͢ΔͨΊͷςετΛ࡞੒ @ArchTest public static final ArchRule ϦΫΤετΛड͚෇͚ΔϝιουʹOpenAPIΞϊςʔγϣϯΛॻ͘͜ͱ =

    methods() .that() .areAnnotatedWith(GetMapping.class) .or().areAnnotatedWith(PostMapping.class) .or().areAnnotatedWith(PutMapping.class) .or().areAnnotatedWith(PatchMapping.class) .or().areAnnotatedWith(DeleteMapping.class) .should() .beAnnotatedWith(Operation.class) .andShould() .beAnnotatedWith(ApiResponse.class);