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. !:VUB4BJUP
    ґଘؔ܎ͷςετ͚ͩ͡Όͳ͍
    "SDI6OJUͷ͜Μͳ࢖͍ํ

    View full-size slide

  2. "SDI6OJU঺հ

    View full-size slide

  3. "SDI6OJUͱ͸
    w +BWBͷ6OJU5FTUϑϨʔϜϫʔΫΛར༻ͯ͠ɺΞʔΩςΫνϟΛνΣοΫ͢Δ
    ͨΊͷϥΠϒϥϦ
    w ҎԼͷྫͷΑ͏ͳνΣοΫ͕ग़དྷΔ
    ύοέʔδɺΫϥεͷґଘؔ܎͕ఆٛ௨Γ͔
    Ϋϥεͷωʔϛϯά͕૝ఆ௨Γ͔
    ಛఆ৚݅ԼͷΫϥεʹΞϊςʔγϣϯ͕෇༩͞Ε͍ͯΔ͔
    IUUQTXXXBSDIVOJUPSH

    View full-size slide

  4. +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Λ෇༩͢Δɻ

    View full-size slide

  5. +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 does not have simple name ending with
    'Controller' in (TestAction.java:0)


    ࣮ߦ͠ϧʔϧʹҧ൓͢Δίʔυ͕ଘࡏ͢Δ৔߹ʹ͸ɺ+6OJUͷςετ͕ࣦഊ͢
    Δɻ

    View full-size slide

  6. ϨΠϠʔؒͷґଘؔ܎Λςετ͢Δ
    $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");
    ϨΠϠʔؒͷґଘؔ܎ͷςετίʔυͷॻ͖ํ

    View full-size slide

  7. "SDI6OJUͷ͜Μͳ࢖͍ํ

    View full-size slide

  8. "SDI6OJUΛ࢖͍ͬͯΔഎܠ
    w ಉ࣌ʹෳ਺νʔϜ͕։ൃΛਐΊ͍ͯͯɺ։ൃํ਑ͷڞ༗͕೉͍͠
    w ϨΨγʔγεςϜ͔ΒετϥϯάϥʔύλʔϯͰҠߦΛਐΊ͍ͯΔ͕ɺϨΨγ
    ʔγεςϜʹ׳Ε͗ͯ͢͠·͍ɺҠߦઌγεςϜʹྑ͘ͳ͍ॻ͖ํΛ࣋ͪࠐΜ
    Ͱ͠·͏ ੹຿Λҙࣝग़དྷ͍ͯͳ͍

    w ݸʑͷܦݧ εΩϧ
    ʹ͕ࠩ͋Δ "1*ͷ։ൃܦݧ͕ͳ͍౳

    View full-size slide

  9. 4USJOH6UJMTͷϥΠϒϥϦΛݶఆ
    w 4USJOH6UJMT͍ͬͺ͍͋ͬͯؒҧ͑Δ
    w "QBDIF$PNNPOT-BOHͷΈΛར༻͍ͨ͠

    View full-size slide

  10. 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Λར༻ͨ͠ΧελϜϧʔϧΛ࡞੒

    View full-size slide

  11. 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 calls method
    in
    (ApacheCommonsLangTest.java:8)

    View full-size slide

  12. 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);


    View full-size slide

  13. $POUSPMMFSͷύεΛࢦఆ
    w ΤϯυϙΠϯτͷύεͱύοέʔδΛ౷Ұ͍ͨ͠
    W99
    W99
    DPNFYBNQMFBSDIVOJUDPOUSPMMFSW
    DPNFYBNQMFBSDIVOJUDPOUSPMMFSW

    View full-size slide

  14. $POUSPMMFSͷύεΛࢦఆ
    w "SDI$POEJUJPOΛར༻ͨ͠ΧελϜϧʔϧΛ࡞੒
    private static ArchCondition havePathStartingWith(String apiVersion) {


    return new ArchCondition("APIͷpath͸ /" + apiVersion + “/ Ͱ࢝ΊΔඞཁ͕͋Γ·͢ɻ") {


    @Override


    public void check(JavaClass item, ConditionEvents events) {


    RequestMapping requestMapping = item.getAnnotationOfType(RequestMapping.class);


    Set 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));


    }


    }


    };


    }

    View full-size slide

  15. $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"));


    View full-size slide

  16. 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);


    View full-size slide

  17. ·ͱΊ
    w "SDI6OJUΛར༻͢Δ͜ͱͰɺΞʔΩςΫνϟͷςετ͕Ͱ͖Δ
    w ϧʔϧʹҧ൓͍ͯ͠Δίʔυ͕͋Ε͹ςετʹࣦഊ͢ΔͨΊɺ৭ʑͳܦݧͷਓ
    ͕҆શʹػೳ։ൃΛਐΊΔ͜ͱ͕ग़དྷΔ

    View full-size slide

  18. ͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠

    View full-size slide

  19. ϓϩμΫτͷ͜ΜͳࠔΓ͝ͱ
    ิ଍ࢿྉ

    View full-size slide

  20. ϓϩμΫτͷঢ়گᶃ
    ։ൃମ੍
    w ػೳ͝ͱͷΞδϟΠϧνʔϜɺ1+5͝ͱͷνʔϜɺอकνʔϜ͕ଘࡏ͢Δɻ
    ΞδϟΠϧνʔϜ"
    ػೳ"ͷ։ൃ
    ΞδϟΠϧνʔϜ#
    ػೳ#ͷ։ൃ
    ΞδϟΠϧνʔϜ$
    ػೳ$ͷ։ൃ
    1+5νʔϜ" 1+5νʔϜ# อकνʔϜ

    View full-size slide

  21. ϓϩμΫτͷঢ়گᶄ
    w ݩʑͷγεςϜʹٕज़ෛ࠴͕ଟ͋ͬͨͨ͘Ίɺ443"1*αʔόͷ৽͍͠γε
    ςϜΛ࡞੒͠ॏཁͳΤϯυϙΠϯτ͔ΒҠߦΛ࣮ࢪɻ
    Ϧόϓϩ
    ϨΨγʔ
    γεςϜ
    443
    αʔό
    "1*αʔό
    Ҡߦ४උ͕ग़དྷͨ
    ΤϯυϙΠϯτ
    Ҡߦग़དྷ͍ͯͳ͍
    ΤϯυϙΠϯτ
    Ϣʔβʔ
    ϨΨγʔγεςϜͷҠߦ

    View full-size slide

  22. ࠔΓ͝ͱ
    w ֤νʔϜͦΕͧΕͰ։ൃ͕ਐΉͨΊɺ։ൃํ਑ͷڞ༗͕೉͍͠
    w ϨΨγʔγεςϜʹ׳Ε͗ͯ͢͠·͍ɺҠߦઌγεςϜʹྑ͘ͳ͍ॻ͖ํΛ࣋
    ͪࠐΜͰ͠·͏ ੹຿Λҙࣝग़དྷ͍ͯͳ͍

    w ݸʑͷܦݧ εΩϧ
    ʹ͕ࠩ͋Δ "1*ͷ։ൃܦݧ͕ͳ͍౳

    View full-size slide