Culture: Freedom & Responsibility ◦ “회사가 실제로 어디에 가치를 두고 있는가를 보려면, 누가 보상을 받고, 승진하고, 해고 당하는가 를 보면 된다” • 큰 그림에서 회사를 볼 수 있게 됨 ◦ 회사의 건강함을 판단할 수 있는 중요한 잣대 중 하나 ◦ 문화, 비즈니스 모델, 실적
지울 예정이니 그전에 새로운 컴포넌트로 변경하시오" ◦ Mandating doesn't scale. ◦ Heroic don't scale! • Churn Rule(Since 2012): Expertise scale! ◦ 변경을 만들어 낸 조직이 변경을 위한 best practice 등을 만들어서 전파. 변경사항이 매우 복잡한 경우, DevOps 조직에서 요청해서 내부 컨설팅 진행
single repository(monorepo) system ◦ 개발용 브랜치가 오래 open되어 있을 수 없음 ◦ 커밋할 브랜치를 선택할 수 없음 ◦ 어떤 버전에 의존하는가를 선택할 수 없음 • 참고 문헌: Advantages of trunk based development (한글 | 영문)
wallpaper could soft-brick your phone // TODO: Fine tune the performance here, tracking on [b/123615079] int[] histogram = new int[256]; for (int row = 0; row < height; row++) { for (int col = 0; col < width; col++) { int pixel = grayscale.getPixel(col, row); int y = Color.red(pixel) + Color.green(pixel) + Color.blue(pixel); histogram[y]++; } }
갖고 시스템을 수정할 수 있음 • 문서로서의 테스트 코드. 테스트 케이스만 보면 특정 시스템의 기능과 의도, 올바른 사용법을 단번에 파악 가능 • 협업을 촉진: code owner가 아니더라도 코드를 수정할 수 있음 • Simpler reviews • modular한 설계에 도움: single responsibility
코드가 없었음 ◦ 특정 시점에서는 무려 80% 이상의 PR이 버그로 인해 롤백되었음 -_-; ◦ 개발자들이 기능을 수정할 때 잘 동작하리라는 확신이 없어짐 • 이 문제를 분석한 결과, 자동화 테스트만이 해결책이라는 결론 ◦ 코드 변경시에는 반드시 테스트를 함께 구현하도록 정책 변경 ◦ 1년 뒤 → 긴급 패치 숫자가 절반으로 감소 ◦ 현재 → GWS 단일 시스템에서만 수만 개의 테스트 케이스
날리기 전에 이슈(aka 태스크, 스토리) 하나 당 적어도 두 개의 E2E 테스트 (정상/실패), 그리고 그것이 2x 정도의 소규모 테스트 작성 • 이슈를 중심으로 high level -> low level로 내려가면서 테스트 작성 • 버그 발생 시, 재현을 위한 테스트부터 작성 후 처리하기 * 개인적인 의견입니다
@Test public void shouldPerformAddition() { Calculator calculator = new Calculator(new RoundingStrategy(), "unused", ENABLE_COSINE_FEATURE, 0.01, calculusEngine, false); int result = calculator.calculate(newTestCalculation()); assertThat(result).isEqualTo(5); // Where did this number come from? }
테스트 케이스의 본문은, ◦ 테스트를 이해하기 위한 모든 정보를 담고 있어야 함 ◦ 테스트와 상관없거나 이해를 방해하는 정보는 최대한 감춰야 함 @Test public void shouldPerformAddition() { Calculator calculator = newCalculator(); int result = calculator.calculate(newCalculation(2, Operation.PLUS, 3)); assertThat(result).isEqualTo(5); }
transactionProcessor.displayTransactionResults( newUserWithBalance( LOW_BALANCE_THRESHOLD.plus(dollars(2))), new Transaction("Some Item", dollars(3))); assertThat(ui.getText()).contains("You bought a Some Item"); assertThat(ui.getText()).contains("your balance is low"); }
transactionProcessor.displayTransactionResults( new User(), new Transaction("Some Item")); assertThat(ui.getText()).contains("You bought a Some Item"); } @Test public void displayTransactionResults_showsLowBalanceWarning() { transactionProcessor.displayTransactionResults( newUserWithBalance( LOW_BALANCE_THRESHOLD.plus(dollars(2))), new Transaction("Some Item", dollars(3))); assertThat(ui.getText()).contains("your balance is low"); }
public void transferFundsShouldMoveMoneyBetweenAccounts() { // Given two accounts with initial balances of $150 and $20 Account account1 = newAccountWithBalance(usd(150)); Account account2 = newAccountWithBalance(usd(20)); // When transferring $100 from the first to the second account bank.transferFunds(account1, account2, usd(100)); // Then the new account balances should reflect the transfer assertThat(account1.getBalance()).isEqualTo(usd(50)); assertThat(account2.getBalance()).isEqualTo(usd(120)); }
Given two users User user1 = newUser(); User user2 = newUser(); // And an empty connection pool with a 10-minute timeout Pool pool = newPool(Duration.minutes(10)); // When connecting both users to the pool pool.connect(user1); pool.connect(user2); // Then the pool should have two connections assertThat(pool.getConnections()).hasSize(2); // When waiting for 20 minutes clock.advance(Duration.minutes(20)); // Then the pool should have no connections assertThat(pool.getConnections()).isEmpty(); // And each user should be disconnected assertThat(user1.isConnected()).isFalse(); assertThat(user2.isConnected()).isFalse();
newUser().setState(State.NORMAL).build(); User user2 = newUser().setState(State.NORMAL).build(); Forum forum = new Forum(); forum.register(user1); forum.register(user2); assertThat(forum.hasRegisteredUser(user1)).isTrue(); assertThat(forum.hasRegisteredUser(user2)).isTrue(); }
이후 최대 난관 • 원칙은 helpfulness! - 테스트가 실패했다면 실제로 시스템에 문제가 있는 것임 and vice versa • Real code > Fake > Mock/Spy/Stub ◦ Real implementation - prefer realism over isolation ◦ Fake - fakes should be tested ◦ Mock - avoid overspecification @DoNotMock("Use SimpleQuery.create() instead of mocking.") public abstract class Query { public abstract String getQueryValue(); }