평소 Junit의 assertEquals로 알고리즘 코드만 test 하다가
토이 프로젝트에서도 Junit을 사용해보기 위해 Spring에서의 Junit Test 방법을 정리 해보자 한다


Test할 Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Controller
public class HomeController {

@GetMapping("/")
public String home(Locale locale, Model model) {
Date date = new Date();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);

String formattedDate = dateFormat.format(date);

model.addAttribute("serverTime", formattedDate );

return "home";
}
}
  • index 페이지로 매핑되는 home 메서드만 존재하는 Controller

standaloneSetup() 메서드를 활용한 Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class HomeControllerTest {

private MockMvc mockMvc;

@Before
public void setUp() {
this.mockMvc = MockMvcBuilders.standaloneSetup(new HomeController()).build();
}

@Test
public void homeTest() throws Exception {

mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(model().attributeExists("serverTime"))
.andExpect(view().name("home"));
}
}

MockMvc 클래스를 통해 HTTP GET, POST 등에 대한 테스트를 할 수 있게 한다

  • perform() : get 방식으로 url을 호출
  • status() : http status에 대해 테스트
  • model() : model에 “serverTime” attribute가 존재하는 지 확인
  • view() : return하는 view의 name이 “home”인지 확인

위 메서드들을 정상적으로 사용하기 위해선 아래 처럼 메서드들을 import 시켜야한다

1
2
3
4
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;

webAppContextSetup() 메서드를 활용한 Test

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
@RunWith(SpringRunner.class)
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"
, "file:src/main/webapp/WEB-INF/spring/root-context.xml"
, "file:src/main/webapp/WEB-INF/spring/spring-security.xml"})
@WebAppConfiguration
public class HomeControllerTest {

@Autowired
private WebApplicationContext context;

private MockMvc mockMvc;

@Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}

@Test
public void homeTest() throws Exception {

mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(model().attributeExists("serverTime"))
.andExpect(view().name("home"));
}
}

@RunWith

  • Junit에 내장된 Runner외에 다른 실행자를 실행시킨다
  • 위 코드에선 SpringRunner 실행자로 Test를 실행하는데 이때 SpringRunner class는 SpringJUnit4ClassRunner class를 상속받고 있다
  • 테스트를 진행할 때 ApplicationContext를 만들고 관리하는데 이 때 싱글톤을 보장한다

@ContextConfiguration

  • 스프링 Bean 설정 파일의 위치를 지정한다
  • 위 코드에서는 Controller는 servlet-context.xml, 그 외 Service, Repository 등은 root-context.xml, 암호화를 위한 bcryptPasswordEncoder bean은 spring-security.xml에 지정되어 있어 3개를 지정했다

@WebAppConfiguration

  • applicationContext의 웹 버전을 작성하는데 사용된다

standalonesetup과 webAppContextSetup의 차이

standalonesetup() 메서드는 테스트할 컨트롤러를 수동으로 초기화해서 주입하고, webAppContextSetup() 메서드는 스프링의 ApplicationContext의 인스턴스로 동작한다

standalonesetup() 메서드는 컨트롤러에 집중하여 테스트할 때 사용되고
webAppContextSetup() 메서드는 스프링의 DI 컨테이너를 이용해 스프링 MVC 동작을 재현해서 테스트한다


번외 java.lang.NoClassDefFoundError: javax/servlet/SessionCookieConfig 에러

1
2
3
4
5
6
7
8
9
10
11
12
13
java.lang.NoClassDefFoundError: javax/servlet/SessionCookieConfig
at org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder.initWebAppContext(StandaloneMockMvcBuilder.java:339)
at org.springframework.test.web.servlet.setup.AbstractMockMvcBuilder.build(AbstractMockMvcBuilder.java:139)
at com.toon.controller.HomeControllerTest.setUp(HomeControllerTest.java:25)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
... 26 more
  • Spring 4.0 이상에서 Junit으로 테스트 시 servlet 3.0 API를 사용하기 때문에 발생
  • pom.xml에서 servlet 버전을 3.0.1 이상으로 올려주면 해결
  • artifactId가 javax.servlet-api 으로 변경 됐으므로 함께 변경해준다.
1
2
3
4
5
6
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>

참고 사이트

댓글 공유

문제

https://www.acmicpc.net/problem/11047


코드

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
34
35
36
37
38
39
40
41
42
43
44
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String coinAndMoney = br.readLine();

String[] coinAndMoneyArray = coinAndMoney.split(" ");

int coinAmount = Integer.parseInt(coinAndMoneyArray[0]);
long money = Long.parseLong(coinAndMoneyArray[1]);

long[] coinCategory = getCoincategory(coinAmount, br);

long answer = solution(coinAmount, money, coinCategory);

System.out.println(answer);
}

public static long[] getCoincategory(int coinAmount, BufferedReader br) throws IOException {
long[] result = new long[coinAmount];
for (int i = 0; i < coinAmount; i++) {

result[i] = Integer.parseInt(br.readLine());
}

return result;
}

public static long solution(int n, long money, long[] array) {
long answer = 0;

for (int i = n - 1; i >= 0; i--) {
if (array[i] > money) {
continue;
}

answer += (money / array[i]);
money %= array[i];

if (money == 0) {
break;
}
}

return answer;
}

흐름

  1. 돈의 종류 만큼 돌면서 큰 수 부터 가지고 있는 돈을 나눔
  2. 돈의 가치가 더 큰 경우엔 나눌 수 없으므로 continue
  3. 돈으로 갖고 있는 돈을 나눈 값을 더하고 남은 값은 다시 나눠야 하므로 money에 다시 저장함
  4. momeny가 0이 되면 나눌 돈이 없는 것이므로 사용한 돈 갯수 return

결과

결과


테스트 케이스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
long[] array = new long[] {1,5,10,50,100,500,1000,5000,10000,50000};
assertEquals(6, test.solution(10, 4200, array));
assertEquals(12, test.solution(10, 4790, array));
assertEquals(1, test.solution(10, 50000, array));
assertEquals(2000, test.solution(10, 100000000, array));

array = new long[] {1};
assertEquals(2, test.solution(1, 2, array));
assertEquals(1, test.solution(1, 1, array));
assertEquals(100000000, test.solution(1, 100000000, array));

array = new long[] {1, 5};
assertEquals(1, test.solution(2, 5, array));

array = new long[] {1, 3};
assertEquals(2, test.solution(2, 4, array));

array = new long[] {1, 100};
assertEquals(1, test.solution(2, 100, array));

array = new long[] {5000};
assertEquals(1, test.solution(1, 5000, array));
  • 나머지는 입력 값 처리이고 solution 메서드가 주요 로직이므로 solution 메서드를 테스트
  • 이렇게 테스트 할 경우 입력처리 때문에 에러가 발생 할 수 있으니 꼭 java application으로 함께 테스트 해볼 것

댓글 공유

@SpringBootApplication의 동작 방식

Spring-Boot의 Main Application 코드에는 @SpringBootApplication이라는 Annotation이 존재한다.

1
2
3
4
5
6
@SpringBootApplication
public class Demo1Application {
public static void main(String[] args) {
SpringApplication.run(Demo1Application.class, args);
}
}
  • @SpringBootApplication
1
2
3
4
5
6
7
8
9
10
11
12
13
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

...

}

이 Annotation의 주된 내용은 아래 3가지 이다.

  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • @ComponentScan

@SpringBootConfiguration

  • @SpringBootConfiguration은 기존 @Configuration과 마찬가지로 해당 클래스가 @Bean 메서드를 정의되어 있음을 Spring 컨테이너에 알려주는 역할을 한다.
  • @Configuration 어노테이션의 대안으로 둘의 차이점은 아래와 같이 설명하고 있다.

@SpringBootConfiguration is an alternative to the @Configuration annotation. The main difference is that @SpringBootConfiguration allows configuration to be automatically located. This can be especially useful for unit or integration tests.

@EnableAutoConfiguration

  • @ComponentScan에서 먼저 스캔해서 Bean으로 등록하고 tomcat등 스프링이 정의한 외부 의존성을 갖는 class들을 스캔해서 Bean으로 등록한다.
  • 이 때 정의된 class들은 spring-boot-autoconfigure안에 있는 MATE-INF 폴더에 spring.factories라는 파일안에 정의되어 있다.
  • 이 중에서 spring.boot.enableautoconfiguration을 key로하는 외부 의존성 class들이 존재한다.
  • 여기에 정의된 모든 class를 가져오는 게 아니라 class에 내부에 정의된 어노테이션에 따라 그 조건에 부합하는 class들만 생성된다.
    • @ConditionalOnProperty, @ConditionalOnClass, @ConditionalOnBean 등등
1
2
3
4
5
6
public @interface EnableAutoConfiguration {

String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

...
}

spring.factories 위치

spring-boot-autoconfigure

META-INF

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\

...

번외. 내장 Tomcat Class

spring.factories 파일 안에 보면

1
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\

가 있고 이 class의 코드를 보면 아래와 같이 tomcatServletWebServerFactoryCustomizer() 메서드를 통해 tomcat을 실행한다는 것을 알 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {

@Bean
public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {
return new ServletWebServerFactoryCustomizer(serverProperties);
}

@Bean
@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
ServerProperties serverProperties) {
return new TomcatServletWebServerFactoryCustomizer(serverProperties);
}

...

@ComponentScan

  • @ComponentScan은 해당 어노테이션 하위에 있는 객체들 중 @Component가 선언된 클래스들을 찾아 Bean으로 등록하는 역할을 한다.
  • 이 때 꼭 @Component가 아니여도 @Component가 선언되어 있는 어노테이션인 @Service, @Repository, @Controller 등등도 포함된다.
  • @EnableAutoConfiguration이 스캔하기 전에 먼저 @ComponentScan이 진행된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
...
}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
...
}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
...
}

참고 사이트

댓글 공유

문제

https://programmers.co.kr/learn/courses/30/lessons/42839


코드

  • 순열을 이용한 완전탐색으로 해결
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public int solution(String numbers) {
char[] tochar = numbers.toCharArray();

int numbersLength = tochar.length;

Set<Integer> numberSet = new HashSet<Integer>();
for (int i = 1; i <= numbersLength; i++) {
perm(tochar, 0, i, numberSet);
}

int answer = getPrimeCount(numberSet);

return answer;
}

private void perm(char[] array, int depth, int length, Set<Integer> numberSet) {
if (depth == length) {

StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
sb.append(array[i]);
}

int number = Integer.parseInt(sb.toString());
if (number > 1) {
numberSet.add(number);
}

return;
}

for (int i = depth; i < array.length; i++) {
swap(array, i, depth);
perm(array, depth + 1, length, numberSet);
swap(array, i, depth);
}
}

private void swap(char[] arrary, int i, int j) {
char temp = arrary[i];
arrary[i] = arrary[j];
arrary[j] = temp;
}

private int getPrimeCount(Set<Integer> numberSet) {
int result = 0;
for (int i : numberSet) {
boolean isPrime = true;

for (int j = 2; j * j <= i; j++) {
if (i % j == 0) {
isPrime = false;
break;
}
}

if (isPrime) {
++result;
}
}

return result;
}

흐름

  1. [1,2,3] 배열일 경우 3 자리 수 까지 나타날 수 있으므로, numbers의 길이 만큼 반복하면서 순열을 만듬
  2. perm() 메서드를 재귀로 돌면서 깊이 만큼 index를 스위칭하면서 순열을 만듬
    • ex) [1,2,3] 인 경우, 0번째와 0번째를 스왑하여 123이 되고 그 이후엔 0번째와 1번째를 스왑해서 213, 그 이후엔 0과 2를 스왑해서 321 … 이런 식으로 진행된다
  3. 돌다가 깊이랑 이번에 만들 숫자의 길이가 같으면 중복되지 않게 Set에 저장하는데 1 이하인 경우엔 소수가 아니므로 저장하지 않음
  4. 다시 swap() 해서 배열 index를 원상복귀 시킴
  5. 반복해서 모든 순열을 만든 후 해당 Set에 저장된 수들이 소수 인지 판별해서 소수면 카운트 증가시킴

결과

번호 속도
테스트 1 통과 (1.06ms, 50.3MB)
테스트 2 통과 (9.99ms, 53MB)
테스트 3 통과 (0.83ms, 52.1MB)
테스트 4 통과 (7.08ms, 52.6MB)
테스트 5 통과 (23.43ms, 56MB)
테스트 6 통과 (0.91ms, 52.6MB)
테스트 7 통과 (1.13ms, 51.9MB)
테스트 8 통과 (18.16ms, 55.9MB)
테스트 9 통과 (0.96ms, 49.9MB)
테스트 10 통과 (10.37ms, 52.9MB)
테스트 11 통과 (2.51ms, 51.5MB)
테스트 12 통과 (2.16ms, 52MB)

테스트 케이스

1
2
3
4
5
6
assertEquals(3, test.solution("17"));
assertEquals(2, test.solution("011"));
assertEquals(1, test.solution("2"));
assertEquals(12, test.solution("7843"));
assertEquals(0, test.solution("9999999"));
assertEquals(1336, test.solution("1276543"));

참고 사이트

댓글 공유

문제

https://www.acmicpc.net/problem/1152


코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static long solution(String args) {
char[] tochar = args.trim().toCharArray();

long answer = 0;
if (tochar.length == 0) {
return answer;
}

for (char ch : tochar) {
if (ch == ' ') {
++answer;
}
}

return answer + 1;
}

흐름

  1. 문자열 앞 뒤로 공백이 존재 할 수 있으므로 trim()
  2. 공백만 있는 문자열인 경우 0 return
  3. char array로 변환된 문자열을 돌면서 공백인 경우 count 증가
  4. 공백을 기준으로 단어가 만들어졌으니 공백 개수 + 1 return
    • ex) hello, wolrd! = 공백 1개, 문자 2개

결과

결과


테스트 케이스

1
2
3
4
5
assertEquals(6, test.solution("The Curious Case of Benjamin Button"));
assertEquals(3, test.solution(" Mazatneunde Wae Teullyeoyo"));
assertEquals(2, test.solution("Teullinika Teullyeotzi "));
assertEquals(7, test.solution(" a b c d e f g "));
assertEquals(0, test.solution(" "));

전체 코드

https://github.com/jungguji/algorithm_training/blob/master/src/main/java/algorithm/baekjoon/string/%EB%8B%A8%EC%96%B4%EC%9D%98_%EA%B0%9C%EC%88%98.java

댓글 공유

스프링의 정수는 엔터프라이즈 서비스 기능을 POJO에 제공하는 것

Professional SPring FrameworkRod Johnson 외 4명
  • 스프링의 주요 기술인 Ioc/DI, AOP와 PSA(Portable Service Abstraction)는 애플리케이션을 POJO로 개발할 수 있게 해주는 가능 기술이라고 불린다

POJO란

  • Plain Old Java Object의 약자
  • 직역하면 평범한 오래된 자바 객체다

POJO의 조건

  1. 특정 규약에 종속되지 않는다.
    • 객체지향 설계의 자유로운 적용이 가능한 오브젝트여야만 POJO라고 불릴 수 있다.
  2. 특정 환경에 종속되지 않는다
    • 환경에 독립적이어야 한다.
    • 특히 비즈니스 로직을 담고 있는 POJO 클래스는 웹이라는 환경정보나 웹 기술을 담고 있는 클래스나 인터페이스를 사용해서는 안된다.
      • 설령 나중에 웹 컨트롤러와 연결돼서 사용될 것이 뻔하도 할지라도
    • 비즈니스 로직을 담은 코드에 HttpServletRequest나 HttpSession, 캐시와 관련된 API가 등장하거나 웹 프레임워ㅏ크의 클래스를 직접 이용하는 부분이 있다면 그것은 진정한 POJO라고 볼 수 없다.

진정한 POJO란 객체지향적인 원리에 충실하면서, 환경과 기술에 종속되지 않고 필요에 따라 재활용될 수 있는 방식으로 설계된 오브젝트를 말한다

POJO의 장점

  1. 특정한 기술과 환겨엥 종속디지 않는 오브젝트는 그만큼 깔끔한 코드가 될 수 있다.
  2. POJO로 개발된 코드는 자동화된 테스트에 매우 유리하다.
  3. 객체지향적인 설계를 자유롭게 적용할 수 있다.

POJO 프레임워크

  • POJO 프로그래밍이 가능하도록 기술적인 기반을 제공하는 프레임워크
  • 스프링 프레임워크와 하이버네이트 등

POJO를 가능하게 하는 스프링의 기술

  1. Ioc/DI
  2. AOP
  3. PSA

제어의 역전(Ioc) / 의존관계 주입(DI)

  • 왜 두개의 오브젝트를 분리해서 만들고, 인터페이스를 두고 느슨하게 연결한 뒤, 실제 사용할 대상은 DI를 통해 외부에서 지정하는 걸까?
  • 이렇게 DI 방식으로 하는 것이 직접 사용할 오브젝트를 new 키워드로 생성해서 사용하는 강한 결합을 쓰는 방법보다 나은 점은 무엇일까?

= 유연한 확장이 가능하게 하기 위해

  • A->B라는 의존관계가 있을 떄 확장은 B가 자유롭게 변경될 수 있음을 의미
  • 이는 B가 변경되어도 A는 아무 영향이 없고 그대로 유지 가능
    B 관점에선 유연한 확장이고, A 관점에서는 변경 없이 재사용이 가능함
  • 개방 폐쇄 원칙(OCP)라는 객체지향 설계 원칙으로 설명할 수 있음

애스펙트 지향 프로그래밍(AOP)

  • 객체지향 기술만으로는 애플리케이션의 요구조건과 기술적인 난해함을 모두 해결하는데 한계가 있음
  • 이를 도와주는 보조적인 프로그래밍 기술이 AOP
  • 스프링의 AOP는 스프링이 POJO 프로그래밍을 지원하려는 그 핵심 목적을 위해 중요한 역할을 하고 있음

포터블 서비스 추상화(PSA)

  • POJO로 개발된 코드는 특정 환경이나 구현 방식에 종속적이지 않아야 함
  • 환경과 세부 기술의 변화에 관계없이 일관된 방식으로 기술에 접근 할 수 있게 해주는게 PSA
  • 단순히 구체적인 기술에 종속되지 않게 하기 위해서 말고 테스트가 어렵게 만들어진 API나 설정을 통해 주요 기느을 외부에서 제어하게 만들고 싶을 때도 이용할 수 있음

댓글 공유

문제

문제에 오류가 있는 것으로 판단됌

https://programmers.co.kr/learn/courses/30/lessons/42860


코드

아래 코드는 통과되지 못했음

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
private static final int A_ASCII = 65;

public int solution(String name) {
char[] toChar = name.toCharArray();

int answer = getUpDownCount(toChar);

int[] serialIndex = getLongestSerialIndex(toChar);

int leftRightCursur = getLeftRightCount(serialIndex[0], serialIndex[1], toChar.length);

answer += leftRightCursur;

return answer;
}

private int getUpDownCount(char[] names) {
int count = 0;
for (char c : names) {
int upDownCurour = c - A_ASCII;
if (upDownCurour > 12) {
upDownCurour = 26 - upDownCurour;
}

count += upDownCurour;
}

return count;
}

private int[] getLongestSerialIndex(char[] array) {
List<Serial> list = getSerialList(array);

Serial serial = getMaxCountSerial(list);

int[] result = new int[2];
result[0] = serial.index;
result[1] = serial.index + serial.count;

return result;
}

private List<Serial> getSerialList(char[] array) {
int count = 0;
List<Serial> list = new ArrayList<Serial>();

for (int i = 0; i < array.length; i++) {
if (array[i] != 'A') {
count = 0;
continue;
}

if (count == 0) {
list.add(new Serial(i, 1));
} else {
list.get(list.size()-1).count = (count+1);
}

count++;
}

return list;
}

private Serial getMaxCountSerial(List<Serial> list) {
int max = 0;
Serial serial = new Serial();

for (Serial s : list) {
if (max <= s.count) {
max = s.count;
serial = s;
}
}

return serial;
}

private int getLeftRightCount(int serialIndex, int serialCount, int nameLength) {
int leftRightCursur = 0;

if (serialIndex == 0 && serialCount == 0) {
leftRightCursur = nameLength - 1;
} else {
int left = serialIndex == 0 ? 0 : serialIndex - 1;
int right = nameLength - serialCount;

leftRightCursur = left > right ? (right * 2) + left : (left * 2) + right;
leftRightCursur = leftRightCursur > nameLength - 1 ? nameLength - 1 : leftRightCursur;
}

return leftRightCursur;
}

class Serial {

private int index;
private int count;

public Serial() {}

public Serial(int index, int count) {
this.index = index;
this.count = count;
}
}

흐름

위 코드는 통과되지 못했음

  1. 문자들을 A로 뺀 후 거꾸로 도는게 빠른지 정방향으로 도는게 빠른지 확인해서 upDownCurour 변수에 저장
  2. 문자열에서 A가 제일 길게 연속되는 부분을 찾기 위해 inner class를 하나 만들어서 A가 연속될 때 마다 A가 시작된 index와 연속된 길이를 저장
  3. A가 제일 길게 저장된 시작 index와 끝 index를 return
  4. 문자열 내에 A가 없으면 정방향으로 돌리고
  5. A가 있으면 return된 index들을 가지고 왼쪽과 오른쪽에서 몇 칸씩 이동해야 하는 지 구함
  6. left와 right를 더하고 가장 짧게 가야하므로 둘 중에 짧은 방향을 한번 더 더함
  7. 더 한 값이 문자열의 길이보다 길면 정방향으로 가는게 더 짧으므로 비교해서 확인
  8. 그렇게 구한 값을 더해서 return

위 코드는 통과되지 못했음


테스트 케이스

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
assertEquals(4, test.solution("AAABAA"));
assertEquals(56, test.solution("JEROEN"));
assertEquals(23, test.solution("JAN"));
assertEquals(48, test.solution("CANAAAAANAN"));
assertEquals(8, test.solution("BBBAAB"));
assertEquals(56, test.solution("AAAAACANAAAAANANAAAAAA"));
assertEquals(3, test.solution("BBAAAAA"));
assertEquals(41, test.solution("JJAJAAAAAAAAAJ"));
assertEquals(21, test.solution("AJAJAA"));
assertEquals(7, test.solution("BBAABAA"));
assertEquals(6, test.solution("BBABA"));
assertEquals(10, test.solution("BBAABBB"));
assertEquals(7, test.solution("BBAABAAAA"));
assertEquals(10, test.solution("BBAABAAAAB"));
assertEquals(6, test.solution("ABAAAAAAABA"));
assertEquals(2, test.solution("AAB"));
assertEquals(11, test.solution("AABAAAAAAABBB"));
assertEquals(5, test.solution("ZZZ"));
assertEquals(10, test.solution("BBBBAAAAAB"));
assertEquals(12, test.solution("BBBBAAAABA"));
assertEquals(10, test.solution("ABABAAAAAAABA"));
assertEquals(18, test.solution("BBBBBBBABB"));
assertEquals(5, test.solution("AZAAAZ"));
assertEquals(3, test.solution("AC"));
assertEquals(3, test.solution("BBAAAAA"));
assertEquals(17, test.solution("ABAAABBBBBBB"));

통과된 코드

  • https://by-dev.tistory.com/9
    • 위 주소의 코드는 “AAABAA”인 경우 2를 return
    • 단순히 B까지 이동하는 것만해도 3이 걸리는데 통과 되었다는게 이해가 가지 않음

댓글 공유

스프링의 정의

자바 엔터프라이즈 개발을 편하게 해주는 오플ㄴ소스 경량급 애플리케이션 프레임워크

이일민토비의 스프링

애플리케이션 프레임워크

  • 특정 계층이나, 기술, 업무 분야에 국한되지 않고 애플케이션의 전 영역을 포괄하는 범용적인 프레임워크
  • 애플리케이션 개발의 전 과정을 빠르고 편리하며 효율적으로 진행하는데 목표를 두는 프레임워크
  • 단지 여러 계층의 다향한 기술을 한데 모아뒀기 때문에 그렇게 불리는 게 아님

스프링의 일차적인 존재 목적은 핵심 기술에 담긴 프로그래밍 모델을 일관되게 적용해서 엔터프라이즈 애플리케이션 전 계층과 전 여영ㄱ에 전략과 기능을 제공해줌으로써 애플리케이션을 편리하게 개발하게 해주는 애플리케이션 프레임워크로 사용되는 것

경량급

  • 자체가 가렵다거나 작은 규모의 코드로 이뤄졌다는 뜻이 아님
  • 불필요하게 무겁지 않다는 의미
  • EJB에 비해 단순한 개발툴과 기본적인 개발환경으로도 엔터프라이즈 개발에서 필요로 하는 주요한 기능을 갖춘 애프리케이션을 개발하기 충분

자바 엔터프라이즈 개발을 편하게

  • 개발자가 복잡하고 실수하기 쉬운 로우레벨 기술에 많은 신경을 쓰지 않게함
  • 애플리케이션의 핵심인 사용자의 요구사항, 즉 비즈니스 로직을 빠르고 효과적으로 구현하는 것
  • 스프링이 제공하는 기술이 아니라 자신이 작성하는 애플리케이션의 로직에 더 많은 관심과 시간을 투자하게 함
  • 초기에 기본 설정과 적용 기술만 선택하고 준비해두면 이후에 개발자가 신경 쓸 일이 없음

결국 스프링의 목적은 스프링을 할용해서 엔터프라이즈 애플리케이션 개발을 편하게 하는 것
그렇다면 스프링은 어떻게 복잡한 엔터프라이즈 개발을 편하게 해주는가?


복잡함의 근본적인 이유

  1. 기술적인 제약조건과 요구사항이 늘어남
  2. 애플리케이션이 구현해야 할 핵심기능인 비즈니스 로직의 복잡함이 증가함
  • 자바 엔터프라이즈 시스템 개발이 어려운 가증 큰 이유는 근본적인 비즈니스 로직과 엔터프라이즈 기술이라는 두 가지 복잡함이 한데 얽혀 있기 때문

해결책

  • 기술의 적용 사실이 코드에 직접 반영되지 않는 비침투적 기술 사용

  • 어딘가에는 기술의 적요에 따라 필요한 작업을 해줘야하겠지만, 애플리케이션 코드 여기저기에 불쑥 등장하거나, 코드의 설꼐와 구현 방식을 제한하지 않는다는 게 비침투적인 기술의 특징

  • 반대로 침투적인 기술은 어떤 기술을 적용했을 떄 그 기술과 관련된 코드나 규약 등이 코드에 등장하는 경우

  • 즉 스프링의 기본적인 전량근 비즈니스 로직을 담은 애플리케이션 코드와 엔터프라이즈 기술을 처리하는 코드를 분리시키는 것

기술적 복잡함을 상대하는 전략

서비스 추상화

  • 일관성 없는 기술과 서버환경의 변화에 대한 스프링의 공략 방법은 서비스 추상화
  • 추상화를 통해 로우레벨의 기술 구현 부분과 기술을 사용하는 인터페이스를 분리하고, 환경과 세부기술에 독립적인 접근 인터페이스를 제공하는 것이 가장 좋은 해결책

AOP

  • 비즈니스 로직 전후로 경계가 설정돼야하는 트랜잭션
  • 비즈니스 로직에 대한 보안 적용
  • 계층 사이에 주고받는 데이터와 예외의 일괄 변환이나 로징이나 감사 기능 등
  • 이런 기술과 비즈니스 로직의 혼재로 발생하는 복잡함을 해결하기 위한 스프링의 접근 방법이 AOP

비즈니스와 애플리케이션 로직의 복잡함을 상대하는 전략

객체지향과 DI

  • 기술적인 복잡함을 효과적으로 다루게 해주는 기법은 모두 DI를 바탕으로 함
  • 서비스 추상화, 템플릿/콜백, AOP 등
  • 그리고 DI는 객체지향 프로그래밍 기법일 뿐
  • DI를 적용해서 오브젝트를 분리하고, 인터페이스를 동비하고, DI로 관계를 연결
  • 결국 DI는 좋은 오브젝트 설계의 결과물이기도 하지만, 반대로 DI를 적용하다보면 객체지향 설계의 원칙을 잘 따르고 그 장점을 살린 설계가 나올 수 있음

결국 스프링의 기술과 전략은 객체지향이라는 자바 언어가 가진 강력한 도구를 극대화해서 사용할 수 있도록 돕는 것 이고 스프링은 단지 거들 뿐이다.

댓글 공유

문제

https://programmers.co.kr/learn/courses/30/lessons/42885


코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public int solution(int[] people, int limit) {
Arrays.sort(people);

int left = 0;
int right = people.length - 1;

int answer = 0;
while (left <= right) {
int sum = people[left] + people[right];

if (sum <= limit) {
++left;
}

++answer;
--right;
}

return answer;
}

흐름

  1. 배열을 정렬

  2. 배열 내에서 제일 작은 값이랑 제일 큰 값을 더해서 limit이 넘어가는 지 확인

  3. 넘어가면 제일 작은 값을 증가

  4. 안 넘어가면 제일 큰 값 혼자 빠져야하므로 answer을 증가시키고 right를 감소 시켜서 인덱스를 한칸 땡김

  5. right가 left 보다 작아질 때 까지 반복
    ex) [50,50,70,80] 인 경우 left = 0, right = 4 인데 위 코드르 반복하면 right가 점점 줄어들어 left와 만날 때 끝


결과

정확성 테스트

번호 속도
테스트 1 통과 (1.92ms, 52.7MB)
테스트 2 통과 (1.56ms, 51.4MB)
테스트 3 통과 (1.66ms, 51MB)
테스트 4 통과 (2.02ms, 50.7MB)
테스트 5 통과 (1.42ms, 53MB)
테스트 6 통과 (1.37ms, 50.9MB)
테스트 7 통과 (1.46ms, 53.1MB)
테스트 8 통과 (0.95ms, 52.6MB)
테스트 9 통과 (1.15ms, 52.5MB)
테스트 10 통과 (2.01ms, 52.4MB)
테스트 11 통과 (1.68ms, 52.7MB)
테스트 12 통과 (1.78ms, 52.7MB)
테스트 13 통과 (2.07ms, 52.5MB)
테스트 14 통과 (1.46ms, 50.5MB)
테스트 15 통과 (1.22ms, 50.1MB)

효율성 테스트

번호 속도
테스트 1 통과 (11.43ms, 56.1MB)
테스트 2 통과 (11.63ms, 55.7MB)
테스트 3 통과 (11.88ms, 53.7MB)
테스트 4 통과 (8.84ms, 56.3MB)
테스트 5 통과 (11.08ms, 55.7MB)

테스트 케이스

1
2
3
4
5
6
assertEquals(3, test.solution(new int[] {70, 50, 80, 50}, 100));
assertEquals(3, test.solution(new int[] {70, 80, 50}, 100));
assertEquals(2, test.solution(new int[] {40, 40, 80}, 160));
assertEquals(2, test.solution(new int[] {20, 50, 50, 80}, 100));
assertEquals(5, test.solution(new int[] {40,50,60,70,80,90}, 100));
assertEquals(2, test.solution(new int[] {40,40,40}, 100));

댓글 공유

문제

https://programmers.co.kr/learn/courses/30/lessons/42883?language=java


코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public String solution(String number, int k) {
char[] toCharArray = number.toCharArray();

StringBuilder answer = new StringBuilder();

int idx = 0;
for(int i = 0; i < number.length() - k; i++) {
int max = 0;

for(int j = idx; j <= k + i; j++) {
int ch = toCharArray[j] - '0';
if(max < ch) {
max = ch;
idx = j + 1;
}
}

answer.append(max);
}

return answer.toString();
}

흐름

  1. 문자열에서 k 만큼 빼야하니 당연히 문자열 length - k 만큼 반복
  2. 가장 큰 수의 인덱스를 구해서 그 인덱스부터 한 칸씩 밀려야 하니 k + i 한 값 까지 반복
  3. 가장 큰 수의 인덱스부터 반복해야하니 범위 내에서 가장 큰 수를 구해서 그 수의 인덱스를 저장하고 여기서 구한 인덱스를 2번에서 사용
  4. 가장 큰 수를 저장하고 반복이 끝나면 리턴

테스트 케이스

1
2
3
4
5
6
7
assertEquals("23", test.solution("123", 2));
assertEquals("34", test.solution("1234", 2));
assertEquals("94", test.solution("1924", 2));
assertEquals("3234", test.solution("1231234", 3));
assertEquals("775841", test.solution("4177252841", 4));
assertEquals("9", test.solution("9999999999", 9));
assertEquals("93231357719", test.solution("9312131357719", 5));

참고 사이트

댓글 공유

Junggu Ji

author.bio


author.job