Test에서 Fixture란?

정의

테스트를 수행하는 데 필요한 정보나 오브젝트

설명

일반적으로 픽스처는 여러 테스트에서 반복적으로 사용되기 때문에 @Before메소드를 이용해 생성해두면 편리하다.

예시

1
2
3
4
5
6
7
8
9
10
11
12
13
public class UserDaoTest {
private UserDao dao; //Fixture
private User user1; //Fixture
private User user2; //Fixture

@Before
public void setUp() {
...
this.user1 = new User("rookie", "루키맨", "spring1장");
this.user2 = new User("rokie", "로키맨", "spring2장");
}
...
}

Redis 설치

Redis 서버

Redis 설치 및 실행

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[~]$ wget http://download.redis.io/redis-stable.tar.gz
[~]$ tar xvzf redis-stable.tar.gz
[~]$ cd redis-stable/src
[~]$ vim Makefile

PREFIX?=~/apps/redis-server <- 이부분 수정

[~ src]$ mkdir ~/apps/redis-server
[~ src]$ make PREFIX=~/apps/
[~ src]$ make test
[~ src]$ make install
[~ src]$ cd ~/apps/redis-server
[~ redis-server]$ mkdir conf
[~ redis-server]$ cp ~/redis-stable/redis.conf ./conf/
[~ redis-server]$ vim conf/redis.conf

bind x.x.x.x <- 이 부분을 현재 서버 IP로 설정
daemonize yes <- 이 부분 no를 yes로 수정

[~ redis-server]$ ./bin/redis-server ./conf/redis.conf
[~ redis-server]$ ps -ef | grep redis

- 0000 1 0 00:00 ? 00:00:00 ./bin/redis-server conf/redis.conf x.x.x.x:6379
- 0000 00000 0 00:00 pts/0 00:00:00 grep redis

실행 스크립트

1
2
3
4
5
6
#!/bin/bash
REDIS_DIR=~/apps/redis-server

#### Main #####
# apps/redis-server/bin/redis-server apps/redis-server/conf/redis.conf
$REDIS_DIR/bin/redis-server $REDIS_DIR/conf/redis.conf

Spring

pom.xml

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>biz.paluch.redis</groupId>
<artifactId>lettuce</artifactId>
<version>4.5.0.Final</version>
</dependency>

application.properties

1
2
3
4
5
spring.redis.lettuce.pool.max-active=10
spring.redis.lettuce.pool.max-idle=10
spring.redis.lettuce.pool.min-idle=2
spring.redis.port=6379
spring.redis.host=x.x.x.x

RedisConfiquration.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration    
public class RedisConfiguration {

@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory("x.x.x.x", 6379);
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());

return redisTemplate;
}
}

RedisServiceTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisServiceTest {

@Autowired
RedisTemplate<String, Object> redisTemplate;

@Test
public void test() {
ValueOperations<String, Object> vop = redisTemplate.opsForValue();

vop.set("test1", "햇님");
assertEquals("햇님",vop.get("test1"));
}
}

싱글톤 주의하기

문제

아래의 코드를 우선 읽어보자.

해결 전 심각한 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Controller
public class NoReadRecipientMailboxController {

@Autowired
private NoReadRecipientMailboxMapper recipientMailboxMapper;
private String user_id_session;
private String searchContent;
private List<RecipientEmail> recipientEmail;
private List<RecipientEmail> recipientStarEmail;
private List<RecipientEmail> recipientSearchEmail;

@GetMapping("/noreadreceivemailbox")
public String mailbox(Model model, HttpServletRequest req) {
user_id_session = (String)req.getSession().getAttribute("user_id");
recipientEmail = recipientMailboxMapper.selectList(user_id_session);
model.addAttribute("noreadrecipientEmail", recipientEmail);

recipientStarEmail = recipientMailboxMapper.selectStarList(user_id_session);
model.addAttribute("noreadrecipientStarEmail", recipientStarEmail);

recipientSearchEmail = recipientMailboxMapper.searchMailTitle(searchContent, user_id_session);
model.addAttribute("noreadrecipientSearchEmail", recipientSearchEmail);
this.searchContent = "";

return "mailbox/noreadreceivemailbox";
}
@PutMapping("/noreadreceivemailbox/searchMail/title/{searchContent}")
public ResponseEntity<?> searchMailTitle (@PathVariable String searchContent) {
this.searchContent = "%" + searchContent + "%";

return new ResponseEntity<>(true, HttpStatus.OK);
}
}

문제점 발견

코드를 잘 읽어보면 처음에는 어떤 문제가 있는지 잘 모르는 경우가 많다. 하지만 이 코드는 굉장한 문제점을 가지고 있고, 이제부터 이 문제점을 해결해나가고자 한다.

1
private List<RecipientEmail> recipientEmail;

이러한 방식으로 메소드 외부에 인스턴스 변수를 생성했을 때 스프링은 최초 동작 시에만 새롭게 생성하고, 이후에는 해당 변수를 그대로 반환한다. 그리고 싱글톤은 Thread-Safety하지 않다!
여러 사용자가 동시에 이 서비스를 이용하는 경우에 다른 사용자의 값을 반환받을 경우도 생기는 것이다. 이렇게 되면 다른 사용자의 메일을 볼 수 있을지도 모른다. 아니 실제로 그런 일이 발생한다.

그러므로 아래처럼 메소드 내부에 변수를 선언하는 것이 중요하다.

해결 후 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Controller
public class NoReadRecipientMailboxController {

@Autowired
private NoReadRecipientMailboxMapper recipientMailboxMapper;

@GetMapping("/noreadreceivemailbox")
public String mailbox(Model model, HttpServletRequest req, String searchContent, String view) {
String user_id_session = (String)req.getSession().getAttribute("user_id");

if(searchContent != null) {
String Content = "%" + searchContent + "%";
List<RecipientEmail> recipientSearchEmail = recipientMailboxMapper.searchMailTitle(Content, user_id_session);
model.addAttribute("noreadrecipientSearchEmail", recipientSearchEmail);
}
else if(view != null && view.equals("star")){
List<RecipientEmail> recipientStarEmail = recipientMailboxMapper.selectStarList(user_id_session);
model.addAttribute("noreadrecipientStarEmail", recipientStarEmail);
}
else {
List<RecipientEmail> recipientEmail = recipientMailboxMapper.selectList(user_id_session);
model.addAttribute("noreadrecipientEmail", recipientEmail);
}

return "mailbox/noreadreceivemailbox";
}
}