13장. 까다로운 테스트

이 장에서는 스레드와 영속성을 테스트 하는 방법을 다룬다.

설계 다시 하기스텁과 목을 사용하여 의존성 끊기 에 기반을 둔다.

  1. 멀티스레드 코드 테스트
public void findMatchingProfiles(
            Criteria criteria, MatchListener listener) {
        ExecutorService executor =
                Executors.newFixedThreadPool(DEFAULT_POOL_SIZE);

        List<MatchSet> matchSets = profiles.values().stream()
                .map(profile -> profile.getMatchSet(criteria))
                .collect(Collectors.toList());

        for (MatchSet set: matchSets) {
            Runnable runnable = () -> {
                if (set.matches())
                    listener.foundMatch(profiles.get(set.getProfileId()), set);
            };
            executor.execute(runnable);
        }
        executor.shutdown();
    }

애플리케이션을 빠르게 반응하도록 만들고 싶어서

메서드를 각각 별도의 스레드 맥락에서 매칭을 계산하도록 설계했다.

설명하자면, findMatchingProfiles() 메서드는 각 프로파일에 대해서 MatchSet 인스턴스의 리스트를 모은다. 각 MatchSet 에 대해 메서드는 별도의 스레드를 생성해 MatchSet 객체의 matches() 값이 true이면 프로파일과 그에 맞는 MatchSet 객체를 MatchListener 로 보낸다.

public void findMatchingProfiles(
         Criteria criteria, MatchListener listener) {
      ExecutorService executor = 
            Executors.newFixedThreadPool(DEFAULT_POOL_SIZE);
      for (MatchSet set: collectMatchSets(criteria)) {
         Runnable runnable = () -> {
            if (set.matches())
               listener.foundMatch(profiles.get(set.getProfileId()), set);
         };
         executor.execute(runnable);
      }
      executor.shutdown();
   }

   List<MatchSet> collectMatchSets(Criteria criteria) {
      List<MatchSet> matchSets = profiles.values().stream()
            .map(profile -> profile.getMatchSet(criteria))
            .collect(Collectors.toList());
      return matchSets;
   }

하지만 이렇게 애플리케이션 로직과 스레드 로직을 둘 다 사용하기에

첫 번째 과제는 둘을 분리시킨다.

위 처럼 분리하여 collectMatchSets() 처럼 작은 로직을 위한 테스트 코드를 작성한다.

또 다시 메서드의 스레드 로직을 아래와 같이 재 설계한다.

public void findMatchingProfiles(
         Criteria criteria, MatchListener listener) {
      ExecutorService executor = 
            Executors.newFixedThreadPool(DEFAULT_POOL_SIZE);

      for (MatchSet set: collectMatchSets(criteria)) {
         Runnable runnable = () -> process(listener, set);
         executor.execute(runnable);
      }
      executor.shutdown();
   }

   void process(MatchListener listener, MatchSet set) {
      if (set.matches())
         listener.foundMatch(profiles.get(set.getProfileId()), set);
   }

위와 같이 프로파일 정보를 listener로 넘기는 애플리케이션 로직도 추출한다.

→ 그 이후 MatchListener 인터페이스 Mocking하여 테스트 진행한다.