public class ScoreCollection {
private final List<Scorable> scoreList = new ArrayList<>();
public void add(Scorable item){
scoreList.add(item);
}
public int arithmeticMean(){
int total = scoreList.stream().mapToInt(Scorable::getScore).sum();
return total / scoreList.size();
}
}
class Main{
public static void main(String[] args) {
ScoreCollection collection = new ScoreCollection();
// Test1. 5와 7의 평균 6
collection.add(() -> 5);
collection.add(() -> 7);
System.out.println(collection.arithmeticMean());
// Test2. 10 20 의 평균 15
// 그런데 Test1의 영향을 받아서 원하는 테스트 출력을 얻지 못했다.
// Test1과 Test2의 순서가 달라지면, 또 결과가 달라진다.
collection = new ScoreCollection(); // 초기화해서 원하는 값을 얻을 수 있다.
collection.add(() -> 10);
collection.add(() -> 20);
System.out.println(collection.arithmeticMean());
}
}
위의 임시적인 테스트 방법의 문제점을 해결하기 위한 테스트 프레임워크가 있다.
인텔리제이, 이클립스에도 다 포함되어 있는 프레임워크로, Production 코드와 Test 코드를 분리하여 테스트 할 수 있다.
JUnit 설정방법!
package com.company.p01;
import org.junit.Test;
import static org.junit.Assert.*;
public class ScoreCollectionTest {
@Test
public void arithmeticMeanOfFiveAndSevenResultsInSIx(){
ScoreCollection collection = new ScoreCollection();
collection.add(() -> 5);
collection.add(() -> 7);
int actualResult = collection.arithmeticMean();
assertEquals(6,actualResult);
}
@Test
public void arithmeticMeanOfTenAndTwentyResultsInFifteen(){
ScoreCollection collection = new ScoreCollection();
collection.add(() -> 10);
collection.add(() -> 20);
int actualResult = collection.arithmeticMean();
assertEquals(15,actualResult);
}
}
+@
After (사후) - 중간 과정에서 자원을 할당한 경우에는 이를 해제 특수한 경우에만 사용.package com.company.p02;
public class Account {
private int balance;
public Account(int balance) {
this.balance = balance;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
public void withdraw(int value){
balance -= value;
}
public void deposit(int value){
balance += value;
}
public boolean isMinus(){
return balance < 0;
}
public void throwExcept(){
throw new ArithmeticException();
}
}
import static org.hamcrest.CoreMatchers.*
- EqualTo 이런 메소드들을 사용할 수 있게 import 해줘야한다.@Ignore("This will be tested later")
- 테스트를 무시한 채 다른 테스트를 하는것 테스트를 안할 것을 따로 빼놀 때 사용.fail()
- 익셉션이 발생하지 않아도 통과되서 익셉션이 발생하지 않은 경우에 강제로 fail 시켜버리는 것이다assertThat(actualResult,equalTo(true));
package com.company.p02;
import org.junit.*;
import org.junit.rules.ExpectedException;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
public class AccountTest {
private Account account;
@BeforeClass
public static void classSetUp(){
// static으로 만들어야한다 맨 처음 한번 실행 오래되고
// 딱 한번만 하면 되는 것들 여기다 작성
}
@Before
public void setUpBySetBalanceOneHundred(){
//setUp()메소드라고 부른다.
account = new Account(100); // 1. Arrange
// 각 테스트를 실행할 때마다 매번 다시 실행된다. 초기화가 된다는 의미인듯
}
@Test
public void answerIsMinusWithNegativeBalance(){
// 어렌지와 액트를 구분해줄 필요가 있다 구분하지 않을 때
// 테스트에 어렌지를 다 구현해줘야하는 문제가 생긴다 그래서 따로 만들어 줄 수있다.
// setUp() 메소드를 만든다.
// Account account = new Account(100); // 1. Arrange
account.withdraw(150); // 2. Act
boolean actualResult = account.isMinus();
// boolean에 대한 assertion은 assertTure, assertFalse를 쓰면 좋다.
// assertFalse(actualResult);
// 이건 실패 성공에 대한 것만 나오고 내용이 나오지 않는데
// assertThat()은 틀렸을 때 정보를 볼 수 있음
// assertTure, False가 있다.
assertThat(actualResult,is(equalTo(true))); // 3. Assert
// 인지적으로 일기 좋게 actualResult is equalTo false로 문장처럼 읽히게 해준다.
assertThat(actualResult,not(equalTo(false))); // is <-> not
// not에 경우엔 not일 때 동작을 하게 된다. 이때 테스트가 통과됨.
}
@Test
public void answerIsNotMinusWithPositiveBalance(){
account.withdraw(50);
boolean actualResult = account.isMinus();
assertThat(actualResult,is(not(equalTo(true))));
}
// 가능하면 모든 메소드르 테스트하되 우선순위를 정해서 하는 것이 좋다.
@Test
public void checkPositiveBalanceAfterWithdrawal(){
account.withdraw(80);
int actualResult = account.getBalance();
assertThat(actualResult,is(equalTo(20)));
}
@Test
@Ignore("This will be tested later")
// 테스트를 무시한 채 다른 테스트를 하는것 테스트를 안할 것을 따로 빼놀 때 사용,
// 이것을 남겨두고 커밋하지마세요
// 임시로만 사용해야 한다.
public void checkNegativeBalanceAfterWithdrawal(){
account.withdraw(130);
int actualResult = account.getBalance();
assertThat(actualResult,is(equalTo(20)));
}
// ArithmeticException이 발생하는지 assert하는 테스트
// 간단하다는 장점이 있지만, 인지적으로는 별로 좋지 않음
// 테스트 메소드 내부에 assert가 드러나지 않는다.
@Test(expected = ArithmeticException.class)
// ArithmeticException을 넣어줘서 ArithmeticException인지 확인
public void checkExceptionByAnnotation(){
account.throwExcept();
}
// 인지적으로 더 개선되나, 코드가 매우 복잡해진다.
@Test
public void checkExceptionByTryCatch(){
try{
// account.throwExcept();
fail();
// 익셉션이 발생하지 않아도 통과되서 익셉션이 발생하지 않은 경우에 강제로 fail시켜버리는 것이다
} catch (ArithmeticException e){
assertThat(e.getClass(), equalTo(ArithmeticException.class));
// 아리스메틱 클래스의 클래스 클래스(클래스의 정보를 가지고 있는)를 가져온다.
// 그래서 그것이 맞는지 비교.
// e.getClass() == ArithmeticException.class
}
}
// Rule을 이용하면 메소드 코드에 excepted exception이 드러나서
// 인지적으로 개선 가장 추천함.
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void checkExceptionByRule(){
thrown.expect(ArithmeticException.class);
// ArithmeticException을 기대한다고 써놔서 인지적으로 더 잘 이해가능
// 이렇게 해줬을 때 프레임워크에 가서 이 익셉션이
// 프레임워크에 올라가서 메소드가 동작할 때 익셉션이 발생했을 때 캐치해준다.
account.throwExcept();
}
}
package com.company.p04;
import java.io.IOException;
import java.net.MalformedURLException;
public interface Http {
String get(String targetUrl) throws IOException;
}
package com.company.p04;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
public class HttpImpl implements Http{
@Override
public String get(String targetUrl) throws IOException {
URL url = new URL(targetUrl);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
int responseCode = con.getResponseCode();
BufferedReader in =
new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = in.readLine()) != null){
response.append(inputLine);
}
in.close();
System.out.println("Response code : " + responseCode);
return response.toString();
}
}
package com.company.p04;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class WebParser {
private Http http;
// dependency injection di를하기위해 리팩토링을함
// 테스트 케이스로 인해서 더 테스트하기 좋은 코드로 수정하였다. 이부분이 중요함!!!!
public WebParser(Http http) {
this.http = http;
}
public int countImageFromWebPage(String url) throws IOException {
String text = http.get(url); // DI 적용
// String text = new HttpImpl.get(url); 원래 코드
Pattern pattern = Pattern.compile("(\\w+.(png|jpg|gif))");
Matcher matcher = pattern.matcher(text);
int count = 0;
while (matcher.find()){
count++;
}
System.out.println(text);
return count;
}
}
package com.company.p04;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
public class WebParserTest {
// 이렇게 테스트하면 문제점
// 1. Fast에 맞지 않는다 느리다. 구글에 요청을 보내서 받아오는데
// http 반응 속도가 느려서 그런 것이다. 인터넷 연결이 순간 안될 수도 있다.
// 2. 웹페이지 내용이 변할 수 있다. (기대값이 변할 수 있다.)
// @Test
// public void countImageFromGoogleDotCom() throws IOException {
// WebParser parser = new WebParser();
// int actualResult =parser.countImageFromWebPage("http://google.com");
//
// assertThat(actualResult, equalTo(5));
// }
private WebParser parser;
@Before
public void setUpUsingPageWithThreeImages(){
// DI를 이용해 Http 객체의 stub을 구현하여 넣어준다.
// 실제로 웹파서를 이용할 때 HttpImpl을 만들어서 넣어주면 된다.
parser = new WebParser((targetUrl) -> {
return "<html><meta content=a.png>
<meta content=b.png> <meta content=c.png> </html>";
// 테스트 하고자하는 상황을 더 넣으면 더 의미가 있어짐
});
// 이미지가 3개 나온다는걸 설정을 해줬다. 테스트가 많아질수록 의미가 있어진다.
}
@Test
public void countImageFromThreeImagePageStub() throws IOException {
int actualResult = parser.countImageFromWebPage("http://google.com");
assertThat(actualResult, is(equalTo(3)));
}
}
package com.company.p05;
/**
* I-> 1
* II -> 2
* III -> 3
* IV -> 4...
*/
public class RomanConverter {
private String roman = "";
public void setRoman(String roman) {
this.roman = roman;
}
// 들여쓰기가 2번넘게 들어가면 리팩토링을 하는게 맞다.
public int transform() {
if (roman.equals("")){
throw new ArithmeticException();
}
if (roman.contains("X")){
return 10;
}
int count = 0;
boolean beforeV = roman.contains("V") ? true : false;
for (char c: roman.toCharArray()){
if (c == 'I'){
count += beforeV ? -1 : 1;
}
// 4
if (c == 'V'){
beforeV = false;
count += 5;
}
// 6을 했을때 I가 앞에나오는지 뒤에나오는지 구별을해야하는 상황이 됐다.
}
return count;
}
}
package com.company.p05;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
public class RomanConverterTest {
RomanConverter converter;
@Before
public void setUp() throws Exception {
converter = new RomanConverter();
}
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void exceptionWhenRomanNotSet(){
thrown.expect(ArithmeticException.class);
int actualResult = converter.transform();
}
@Test
public void convertI(){
converter.setRoman("I");
int actualResult = converter.transform();
assertThat(actualResult, is(equalTo(1)));
}
@Test
public void convertX(){
converter.setRoman("X");
int actualResult = converter.transform();
assertThat(actualResult, is(equalTo(10)));
}
@Test
public void convertIII(){
converter.setRoman("III");
int actualResult = converter.transform();
assertThat(actualResult, is(equalTo(3)));
}
@Test
public void convertIV(){
converter.setRoman("IV");
int actualResult = converter.transform();
assertThat(actualResult, is(equalTo(4)));
}
@Test
public void convertVI(){
converter.setRoman("VI");
int actualResult = converter.transform();
assertThat(actualResult, is(equalTo(6)));
}
}