-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[5주차] (김동하, 이승철, 이세원) #2
Comments
토비의 스프링 2장테스트
테스트 검증의 자동화.아래 코드로 작성했을 때, 우리는 화면에 표시된 정보들이 다 맞는지 눈으로 확인을 해봐야만 맞는지를 테스트할 수 있었다. 수동 검증
System.out.println(user2.getName());
System.out.println(user2.getPassword());
System.out.println(user2.getUd() + " 조회 성공); 하지만 아래 코드처럼 자동으로 검증하도록 바꾸면 우리는 테스트 성공인지 실패인지만 확인해 보면 된다. 자동 검증
if(!user.getName().equals(user2.getName())) {
System.out.println("테스트 실패 (name)");
}
else if(!user.getPassword().equals(user2.getPassword())) {
System.out.println("테스트 실패 (password)");
}
else{
System.out.println("조회 테스트 성공");
} JUnit framework
위에서 잠깐 보았던 if(!user.getName().equals(user2.getName())) { . . . } 이 코드를 JUnit이 제공해주는 assertThat이라는 스태틱 메소드를 이용하여 다음과 같이 변경할 수 있다. assertThat(user2.getName(), is(user.getName()));
JUnit Test 순서 지정 (책 이외의 내용)
기본적으로 Junit은 테스트 메소드의 순서가 랜덤이지만, 지난 스터디때 배운 기억이 있어서 정리를 해보았다.
MethodName 코드
@TestMethodOrder(MethodOrderer.MethodName.class)
public class MethodNameOrderTest {
@Test
void bear() {
System.out.println("bear");
}
@Test
void ant() {
System.out.println("ant");
}
@Test
void cat() {
System.out.println("cat");
}
@Test
void dog() {
System.out.println("dog");
}
}
@order 코드
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class JUnit5Test {
@Test
@Order(3)
void bear() {
System.out.println("bear");
}
@Test
@Order(4)
void ant() {
System.out.println("ant");
}
@Test
@Order(2)
void cat() {
System.out.println("cat");
}
@Test
@Order(1)
void dog() {
System.out.println("dog");
}
}
우선, 2번을 살펴보겠다. @Test(expected=EmptyResultDataAccessException.class) 이와 같이 테스트 어노테이션을 작성해 준다면, 보통 테스트와는 반대로 정상적으로 테스트 메소드를 마치면 실패하고, epected에서 지정한 예외가 던져지면 테스트가 성공한다. 즉, 예외가 반드시 발생해야 하는 경우를 테스트하고 싶을때 유용하게 쓸 수 있다. 테스트가 이끄는 개발이 책에서 나온 과정은 테스트를 먼저 만들고, 테스트가 실패하는 것을 보고 실제 코드에 손을 대기 시작한다.
테스트 코드 개선
Junit의 테스트 수행 방식
책에서 나온대로 간단히 정리해보면 이와같은 방식으로 테스트가 수행이 된다. 어플리케이션 컨텍스트 관리@Runwith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="/applicationContext.xml)
public class testClass{
@Autowired
private ApplicationContext context;
. . .
}
DI와 테스트테스트를 잘 활용하려면 자동으로 실행 가능하며 빠르게 동작 하도록 테스트 코드를 만들어야 한다.
@DirtiesContext // 테스트 메소드에서 어플리케이션 컨텍스트의 구성이나 상태를 변경한다는 것을 테스트 컨텍스트 프레임워크에 알려준다.
public class UserTest {
@Autowired
UserDao dao;
@Before
public void beforeTest() {
. . .
DataSource dataSource = new SingleConnectionDataSource(
"jdbc:mysql:/localhost/testdb". "spring", "book", true);
//테스트에서 UserDao가 사용할 DataSource 오브젝트를 직접 생성한다
dao.setDataSource(dataSource);
}
} 이 방법의 장점은 XML설정파일을 수정하지 않고도 테스트 코드를 통해 오브젝트 관계를 재구성 할수 있다. 따라서 위 코드에서는 @DirtiesContext 어노테이션을 추가해서 해당 클래스의 테스트에서 어플리케이션 컨텍스트의 상태를 변경한다는 것을 알려줬다. 이 어노테이션이 붙은 클래스에는 어플리케이션 컨텍스트 공유를 허용하지 않는다. 하지만 이와같은 방법으로도 조금 찜찜하다.
public class UserTest {
UserDao dao;
@Before
public void beforeTest() {
. . .
UserDao dao = new UserDao()
DataSource dataSource = new SingleConnectionDataSource(
"jdbc:mysql:/localhost/testdb". "spring", "book", true);
dao.setDataSource(dataSource);
}
} 이렇게 하면 어플리케이션 컨텍스트가 만들어지는 번거로움이 없으니 그만큼 테스트시간도 절약할 수 있지만, 매번 새로운 테스트 오브젝트를 만들기 때문에 UserDao가 매번 새롭게 만들어진다는 단점이 있다. 여기서 중요한점은 DI를 위해 컨테이너가 반드시 필요한 것은 아니다. 학습 테스트개발자가 자신이 만든 코드가 아닌 다른사람이 만든 코드와 기능에 대해 작성한 테스트를 학습 테스트라고 한다.
학습테스트의 예로 다음과같은 테스트를 책에서 소개하고 있다.
버그 테스트버그테스트란 코드에 오류가 있을 때 그 오류를 가장 잘 드러내줄 수 있는 테스트이다.
|
3장 템플릿
class TemplateClass {
void test1() {
System.out.println("a");
System.out.println("b1");
System.out.println("c");
}
void test2() {
System.out.println("a");
System.out.println("b2");
System.out.println("c");
}
}
public class Main {
public static void main(String[] args) {
TemplateClass templateClass = new TemplateClass();
templateClass.test1();
templateClass.test2();
}
}
class TemplateClass {
void a() {
System.out.println("a");
}
void c() {
System.out.println("c");
}
void test1() {
a();
System.out.println("b1");
c();
}
void test2() {
a();
System.out.println("b2");
c();
}
}
public class Main {
public static void main(String[] args) {
TemplateClass templateClass = new TemplateClass();
templateClass.test1();
templateClass.test2();
}
}
class TemplateClass {
void a() {
System.out.println("a");
}
void c() {
System.out.println("c");
}
void test(B b) {
a();
b.b();
c();
}
}
interface B {
void b();
}
class B1 implements B {
@Override
public void b() {
System.out.println("b1");
}
}
class B2 implements B {
@Override
public void b() {
System.out.println("b2");
}
}
public class Main {
public static void main(String[] args) {
TemplateClass templateClass = new TemplateClass();
B1 b1 = new B1();
B2 b2 = new B2();
templateClass.test(b1);
templateClass.test(b2);
}
}
class TemplateClass {
void a() {
System.out.println("a");
}
void c() {
System.out.println("c");
}
void test(B b) {
a();
b.b();
c();
}
}
interface B {
void b();
}
public class Main {
public static void main(String[] args) {
TemplateClass templateClass = new TemplateClass();
templateClass.test(() -> System.out.println("b1")); // 람다 사용
templateClass.test(new B() {
@Override
public void b() {
System.out.println("b2");
}
});
}
}
정리
고정된 작업흐름을 가지고 여기저기 자주 반복되는 코드가 있다면, 중복되는 코드를 분리할 방법을 생각 |
1장 오브젝트와 의존관계모든 것은 어떠한 목적을 위해 발명된다. 나의 생각 => 스프링은 미래의 변화에 대비할 수 있는 코드를 짜도록 도와준다. 1장 목표 : 스프링이 관심을 갖는 object의 설계, 구현, 동작원리에 초점.과정 설명 -> 리팩토링 및 테스트 코드 작성 User.class public class User {
String id;
String name;
String password;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
} UserDao.class public class UserDao {
public void add(User user) throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/springbook", "spring", "book");
PreparedStatement pstmt = conn.prepareStatement(
"insert into users(id, name, password) value(?,?,?)");
pstmt.setString(1, user.getId());
pstmt.setString(2, user.getName());
pstmt.setString(3, user.getPassword());
pstmt.executeUpdate();
pstmt.close();
conn.close();
}
public User get(String id) throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/springbook", "spring", "book");
PreparedStatement pstmt = conn.prepareStatement(
"select * from users where id = ?");
pstmt.setString(1, id);
ResultSet rs = pstmt.executeQuery();
rs.next();
User user = new User();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
rs.close();
pstmt.close();
conn.close();
return user;
}
} 대체로 변화는 집중된 한가지 관심. 한가지 관심이 한 군데에 집중되게관심사의 분리 : ex) DB 커넥션
private Connection getConnection() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/springbook","spring","book");
return conn;
} 상속을 통한 확장 public abstract Connection getConnection(); 상속의 단점상하위 클래스의 관계가 생각보다 밀접. => 슈퍼 클래스가 변동이 있으면, 서브 클래스는 영향을 받게 된다. 클래스의 분리public class UserDao {
private final SimpleConnectionMaker simpleConnectionMaker; // 관계 존재
public UserDao() {
simpleConnectionMaker = new SimpleConnectionMaker();
}
public void add(User user) throws ClassNotFoundException, SQLException {
Connection conn = simpleConnectionMaker.getConnection(); 하지만, Connection 변경 시, UserDao 변경 사항이 발생함. 인터페이스의 도입public interface ConnectionMaker {
Connection getConnection() throws ClassNotFoundException, SQLException;
} public class UserDao {
private final ConnectionMaker connectionMaker;
public UserDao() {
connectionMaker = new SimpleConnectionMaker(); // 아직도 의존
} UserDao는 인터페이스만 의존한다. 다형성을 활용한 것. 관계설정 책임의 분리public class UserDao {
private final ConnectionMaker connectionMaker;
public UserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
} 외부에서의 주입 원칙과 패턴OCP : 개방 폐쇠 원칙클래스나 모듈은 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다. ex) Connection 변경이 이루어진 상황이다. 외부에서 주입하는 관계 설정 클래스가 따로 존재하기 때문에 가능한 것. 높은 응집도와 낮은 결합도높은 응집도connection 기능을 하나의 클래스로 응집도를 높이자. 낮은 결합도느슨하게 연결된 형태를 유지하는 것이 바람직하다. 서로 독립적이고 알 필요도 없게 만들어 주는 것. 제어의 역전(IOC)public class DaoFactory {
public UserDao userDao() {
ConnectionMaker connectionMaker = new SimpleConnectionMaker();
UserDao userDao = new UserDao(connectionMaker);
return userDao;
}
} DaoFactory 클래스를 만든다. DaotFactory에게 제어권이 생긴 것이다. 설계도로서의 팩토리오브젝트 내에서 구조를 결정하는 작업(하나의 관심사)를 분리한 것. 제어의 역전프로그램의 제어 흐름 구조가 뒤바끼는 것 프레임워크는 제어의 역전 개념이 적용된 대표적인 기술###스프링의 Ioc Bean스프링이 IOC 방식으로 관리하는 오브젝트 BeanFactory등록 생성 조회 관리. 스프링의 Ioc 를 담당하는 핵심 컨테이너. 애플리케이션 컨텍스트beanFactory를 확장. 스프링이 제공하는 애플리케이션 지원 기능을 모두 포함한다. 설정정보/설정 메타정보IOC를 적용하기 위해 사용하는 메타정보 : Configuration(청사진) @bean 어노테이션으로 스프링이 관리하는 bean임을 명시 싱글톤 레지스트리와 오브젝트 스코프싱글톤인스턴스를 1개만 생성하여 공유해서 사용 빈 객체는 싱글톤으로 관리가 된다. 순수 java로의 싱글톤 패턴의 한계
스프링을 통한 싱글톤순수 자바로의 싱글톤 패턴과 달리, 스프링이 지지하는 객체지향적인 설계방식과 디자인 패턴을 적용하는데 아무런 제약이 없다 싱글톤 주의점
DI스프링은 Ioc 컨테이너로 object를 관리한다. |
The text was updated successfully, but these errors were encountered: