JavaBookStudy / JavaBook

책읽기 스터디
https://javabookstudy.github.io/
Apache License 2.0
19 stars 2 forks source link

[토비의 스프링] 6.5.2_DefaultAdvisorAutoProxyCreator 챕터에서의 테스트 실패 #113

Closed taxol1203 closed 3 years ago

taxol1203 commented 3 years ago

클래스 필터를 사용하여 프록시 자동생성을 시도해 보았습니다.

481 ~ 486p에 따라 코드를 수정하고 테스트를 진행하였는데, UserServiceTest가 실패하였습니다.

코드 상 실수나 접근이 잘 못되었는지 확인 부탁드립니다.

실패한 테스트는 upgradeLevelsupgradeAllOrNothing입니다.

upgradeLevels 실패

우선 483p에서 userServiceImpl의 빈의 아이디를 userService로 바꾸어 주었습니다.

<!-- 이제 다시 userService 아이디를 사용할 수 있다. -->
<bean id = "userService" class="com.taxol.service.UserServiceImpl">
    <property name="userDao" ref="userDao"/>
    <property name="mailSender" ref="mailSender"/>
</bean>

이에따라 기존의 UserServiceImpl는 DI를 할 수 없어 제거하였습니다.

@Autowired
UserServiceImpl userServiceImpl; // 제거하였음

이는 UserServiceTestupgradeLevels 메서드에서 오류가 나는데,
기존의 코드는

@Test
    @DirtiesContext
    public void upgradeLevels() {
        userDao.deleteAll();
        for(User user : users) userDao.add(user);

        // 메일 발송 결과를 테스트할 수 있도록 목 오브젝트를 만들어 userService의 의존 오브젝트로 주입한다.
        MockMailSender mockMailSender = new MockMailSender();
        userServiceImpl.setMailSender(mockMailSender);

        userService.upgradeLevels();

        // 각 사용자별로 업그레이드 후의 예상 레벨을 검증한다.
        checkLevelUpgraded(users.get(0), false);
        checkLevelUpgraded(users.get(1), true);
        checkLevelUpgraded(users.get(2), false);
        checkLevelUpgraded(users.get(3), true);
        checkLevelUpgraded(users.get(4), false);

        // 목 오브젝트에 저장된 메일 수신자 목록을 가져와 업그레이드 대상과 일치하는지 확인 한다.
        List<String> request = mockMailSender.getRequests();
        assertThat(request.size(), is(2));
        assertThat(request.get(0), is(users.get(1).getEmail()));
        assertThat(request.get(1), is(users.get(3).getEmail()));
    }

와 같습니다.

여기서 userServiceImpl.setMailSender(mockMailSender);가 오류가 나, 이 코드를 제거하였더니 결과가

java.long.AssertionError:
   Expectd: is <2>
     but wsas <0>

으로 제대로 동작하질 않습니다.

upgradeAllOrNothing 실패

upgradeAllOrNothing의 코드의

fail("TestUserService Exception expected");

을 통해 실패하였습니다.

아예 this.testUserService.upgradeLevels(); 코드가 실패하였다고 생각합니다.

전체 코드

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- <bean id="connectionMaker" class="com.taxol.chapter1_8.DConnectionMaker" /> -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/tobyspring?useSSL=false"/>
        <property name="username" value="ssafy"/>
        <property name="password" value="ssafy"/>
    </bean>

    <bean id="userDao" class="com.taxol.dao.UserDaoJdbc">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- 이제 userService는 userServiceTx의 오브젝트가 주입 된다. -->
    <!-- <bean id="userService" class ="com.taxol.service.UserServiceTx">
        <property name="transactionManager" ref="transactionManager"></property>
        <property name="userService" ref="userServiceImpl"></property>
    </bean> -->

    <!-- UserService에 대한 트랜잭션 프록시 팩토리 빈 -->
    <bean id="userService" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="userServiceImpl" />
        <property name="interceptorNames">
            <list>
                <value>transactionAdvisor</value>
            </list>
        </property>
    </bean>

    <bean id = "userServiceImpl" class="com.taxol.service.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
        <property name="mailSender" ref="mailSender"/>
    </bean>
    <bean id = "transactionManager"
          class = "org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref = "dataSource"></property>    
    </bean>
    <bean id="mailSender" class = "com.taxol.service.DummyMailSender"/>

    <!-- 어드바이스 -->
    <bean id ="transactionAdvice" class="com.taxol.service.TransactionAdvice">
        <property name="transactionManager" ref="transactionManager"/>
    </bean>

    <!-- 스프링이 제공하는 포인트컷 클래스 사용 -->
    <bean id ="transactionPointcut" class="org.springframework.aop.support.NameMatchMethodPointcut">
        <property name="mappedName" value="upgrade*"/>
    </bean>

    <!-- 어드바이저 -->
    <bean id ="transactionAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="advice" ref="transactionAdvice"/>
        <property name="pointcut" ref="transactionPointcut"/>
    </bean>
</beans>

UserServiceTest

package com.taxol.proxy;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;

import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.List;

import javax.sql.DataSource;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.MailSender;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.PlatformTransactionManager;

import com.taxol.dao.UserDao;
import com.taxol.domain.Level;
import com.taxol.domain.User;
import com.taxol.service.DummyMailSender;
import com.taxol.service.MockMailSender;
import com.taxol.service.TransactionHandler;
import com.taxol.service.UserService;
import com.taxol.service.UserServiceImpl;
import com.taxol.service.UserServiceTx;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "/applicationcontext.xml")
public class UserServiceTest {
    private static final int MIN_LOGCOUNT_FOR_SIVER = 50;
    private static final int MIN_RECOMMEND_FOR_GOLD = 30;

    @Autowired
    UserService userService;
    @Autowired
    UserService testUserService;

    @Autowired
    UserDao userDao;

    List<User> users; // 테스트 픽스처

    @Autowired
    private DataSource dataSource;
    @Autowired
    private PlatformTransactionManager transactionManager;
    @Autowired
    MailSender mailSender;
    @Before
    public void setUp() throws Exception {
        users = Arrays.asList( // 배열을 리스트로 만들어주는 편리한 메소드, 배열을 가변인자로 넣어주면 더욱 편리하다.
            new User("bumjin",   "박범진", "p1", Level.BASIC,  MIN_LOGCOUNT_FOR_SIVER-1, 0, "taxol1203@naver.com"),
            new User("joytouch", "강명성", "p2", Level.BASIC,  MIN_LOGCOUNT_FOR_SIVER, 0, "taxol1203@gmail.com"),
            new User("erwins",   "신승한", "p3", Level.SILVER, 60, MIN_RECOMMEND_FOR_GOLD-1, "taxol1203@daum.com"),
            new User("madnitel", "이상호", "p4", Level.SILVER, 60, MIN_RECOMMEND_FOR_GOLD, "taxol1203@kakao.com"),
            new User("green",    "오민규", "p5", Level.GOLD,  100, Integer.MAX_VALUE, "taxol1203@hanmail.net")
        );
    }

    // 5-17 userService 빈의 주입을 확인하는 테스트
    @Test
    public void bean() {
        assertNotNull(this.userService);
    }

    // 5-30 개선한 레벨 업그레이드 테스트
    @Test
    @DirtiesContext
    public void upgradeLevels() {
        userDao.deleteAll();
        for(User user : users) userDao.add(user);

        // 메일 발송 결과를 테스트할 수 있도록 목 오브젝트를 만들어 userService의 의존 오브젝트로 주입한다.
        MockMailSender mockMailSender = new MockMailSender();
        //userServiceImpl.setMailSender(mockMailSender);

        userService.upgradeLevels();

        // 각 사용자별로 업그레이드 후의 예상 레벨을 검증한다.
        checkLevelUpgraded(users.get(0), false);
        checkLevelUpgraded(users.get(1), true);
        checkLevelUpgraded(users.get(2), false);
        checkLevelUpgraded(users.get(3), true);
        checkLevelUpgraded(users.get(4), false);

        // 목 오브젝트에 저장된 메일 수신자 목록을 가져와 업그레이드 대상과 일치하는지 확인 한다.
        List<String> request = mockMailSender.getRequests();
        assertThat(request.size(), is(2));
        assertThat(request.get(0), is(users.get(1).getEmail()));
        assertThat(request.get(1), is(users.get(3).getEmail()));
    }

    // DB에서 사용자 정보를 가져와 레벨을 확인하는 코드가 중복되므로 헬퍼 메소드로 분리했다.
    private void checkLevelUpgraded(User user, boolean upgraded) {
        User userUpdate = userDao.get(user.getId());
        if(upgraded) {
            assertEquals(userUpdate.getLevel(), user.getLevel().nextLevel()); // 업그레이드가 일어났는지 확인
        }
        else { 
            assertEquals(userUpdate.getLevel(), user.getLevel()); // 업그레이드가 일어나지 않았는지 확인
        }
    }

    // 5-21 add() 메소드의 테스트
    @Test
    public void add() {
        userDao.deleteAll();

        User userWithLevel = users.get(4); // GOLD 레벨
        User userWithoutLevel = users.get(0);
        userWithoutLevel.setLevel(null); // 레벨이 비어 있는 사용자. 로직에 따라 등록 중에 BASIC 레벨도 설정돼야 한다.

        userService.add(userWithLevel);
        userService.add(userWithoutLevel);

        // DB에 저장된 결과를 가져와 확인한다.
        User userWithLevelRead = userDao.get(userWithLevel.getId());
        User userWithoutLevelRead = userDao.get(userWithoutLevel.getId());

        assertEquals(userWithLevelRead.getLevel(), userWithLevel.getLevel());
        assertEquals(userWithoutLevelRead.getLevel(), Level.BASIC);
    }

    @Test
    public void upgradeAllOrNothing() throws Exception {
        userDao.deleteAll();
        for (User user : users) {
            userDao.add(user);
        }

        try {
            this.testUserService.upgradeLevels();
            fail("TestUserService Exception expected");
        } catch (TestUserServiceException e) {
            System.out.println("test failed");
        }

        checkLevelUpgraded(users.get(0), false);
        checkLevelUpgraded(users.get(1), false);
        checkLevelUpgraded(users.get(2), false);
        checkLevelUpgraded(users.get(3), false);
        checkLevelUpgraded(users.get(4), false);
    }

    static class TestUserServiceImpl  extends UserServiceImpl {
        private String id = "madnite1";  // 테스트 픽스처의 값을 이제 가져올 수 없기에 고정

        protected void upgradeLevel(User user) { 
            if (user.getId().equals(this.id)) {
                throw new TestUserServiceException();
            }
            super.upgradeLevel(user);
        }
    }
    static class TestUserServiceException extends RuntimeException {
    }
}

NameMatchClassMethodPointcut

package com.taxol.proxy;

import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.NameMatchMethodPointcut;
import org.springframework.util.PatternMatchUtils;

public class NameMatchClassMethodPointcut extends NameMatchMethodPointcut {
    // 모든 클래스를 다 허용하던 디폴트 클래스 필터를 프로퍼티로 받은 클래스 이름을 이용해서 필터를 만들어 덮어씌운다.
    public void setMappedClassName(String mappedClassName){
        this.setClassFilter(new SimpleClassFilter(mappedClassName));
    }

    static class SimpleClassFilter implements ClassFilter{
        String mappedName;

        private SimpleClassFilter(String mappedName) {
            this.mappedName = mappedName;
        }

        @Override
        public boolean matches(Class<?> clazz) {
            // 와일드카드(*)가 들어간 문자열 비교를 지원하는 스프링의 유틸리티 메소드다.
            // *name, name*, *name* 세 가지 방식을 모두 지원한다.
            return PatternMatchUtils.simpleMatch(mappedName, clazz.getSimpleName());
        }
    }
}
kjsu0209 commented 3 years ago

upgradeLevels 실패

MailSender의 send 메서드가 실행되지 않아서 request가 0으로 나온 것 같아 보입니다. setMailSender()코드를 삭제해서 UserService의 upgradeLevels에서 sendUpgradeEmail이 호출 되었으나, mailSender가 주입되지 않아 send 자체가 실행이 안 된게 아닐까 싶습니다..
그러면 assertion에러 전에 NullPointerException이 나올 텐데 이상하네요 :thinking:

upgradeAllOrNothing 실패

upgradeLevel(User user) 메서드가 어디에 호출되나요? 올려주신 코드에서는 호출되는 부분이 안 보여요.