Slide 1

Slide 1 text

Saryong Kang Senior Developer Relations Engineer @ Google Japan Google의 개발문화와 프로세스(1)

Slide 2

Slide 2 text

Disclaimer ● 기본적으로 공개된 내용에 기초하고 있지만, 해석은 어디까지나 개인 의견입니다. ● Don't try this at home: 아무리 성공 사례라고 해도 도입의 이유는, ○ "구글이 한다니까" ‍♂ ○ "나 or 우리 조직에 필요하고 AND 적합하니까" ‍♂

Slide 3

Slide 3 text

I. 서론

Slide 4

Slide 4 text

왜 문화를 알아야 하는가? ● 사내 문화? ○ 참고: Netflix Culture: Freedom & Responsibility ○ “회사가 실제로 어디에 가치를 두고 있는가를 보려면, 누가 보상을 받고, 승진하고, 해고 당하는가 를 보면 된다” ● 큰 그림에서 회사를 볼 수 있게 됨 ○ 회사의 건강함을 판단할 수 있는 중요한 잣대 중 하나 ○ 문화, 비즈니스 모델, 실적

Slide 5

Slide 5 text

개발 프로세스의 세부 사항: 내가 개선 할 수 있는 일 ● Testing ● Code review ● Documentation ● Version control ● Estimate & analysis ● CI / CD ● Dependency management ● Etc.

Slide 6

Slide 6 text

II. Google의 문화와 Scale & Efficiency

Slide 7

Slide 7 text

60,000+ engineers 75,000 changes per day 2 billion lines of code 9 million source files 500 million test cases per day Google Scale

Slide 8

Slide 8 text

Scalability ● Scalable: sublinear scaling with regard to human interaction ● 개발에서뿐만 아니라 모든 정책 결정에 영향을 미치는 요소 ● 만약 팀이 10배가 된다면, 조직의 성과물도 10배로 증가할 수 있도록, 작업을 최대한 최적화 / 자동화 할 수 있는가? ○ No 라면 뭔가 잘못된 것

Slide 9

Slide 9 text

Eg 1. Code Interview ● Time & space complexity - O(1), O(n), O(n log n)

Slide 10

Slide 10 text

Eg 2. Deprecation ● 전통적인 방법: "x월 y일에 기존 컴포넌트를 지울 예정이니 그전에 새로운 컴포넌트로 변경하시오" ○ Mandating doesn't scale. ○ Heroic don't scale! ● Churn Rule(Since 2012): Expertise scale! ○ 변경을 만들어 낸 조직이 변경을 위한 best practice 등을 만들어서 전파. 변경사항이 매우 복잡한 경우, DevOps 조직에서 요청해서 내부 컨설팅 진행

Slide 11

Slide 11 text

성공적인 조직이라면, 반복되는 모든 것은 sub-linear한 자원 (특히 인적 자원)만을 소비해야 한다

Slide 12

Slide 12 text

Eg 3. Configuration management: weekly merge meeting ● Branch 기반 코드 관리(ie. git)의 장점과 문제점

Slide 13

Slide 13 text

Solution: no merge meeting ● Piper: branch를 (거의) 사용하지 않는 single repository(monorepo) system ○ 개발용 브랜치가 오래 open되어 있을 수 없음 ○ 커밋할 브랜치를 선택할 수 없음 ○ 어떤 버전에 의존하는가를 선택할 수 없음 ● 참고 문헌: Advantages of trunk based development (한글 | 영문)

Slide 14

Slide 14 text

그 외에도.. ● 비용 지불 ● 효율적으로 일하는 방식 ○ 아무도 가르쳐주지 않았으나 몸에 베어있는 agility ○ 영웅주의 / 무리해서 일하기 배격

Slide 15

Slide 15 text

III. Google의 Testing

Slide 16

Slide 16 text

Test-centric Culture ● Google은 극도로 테스트 중심의 개발 원칙을 갖고 있음 ○ 여기서는 테스트는 engineer-driven automated testing을 의미함 ○ “QA doesn't scale”

Slide 17

Slide 17 text

그 전에, Google의 개발 프로세스는.. 1. Issue Tracker에 작업 등록 2. 코드 작성 (test 코드 포함) 3. Pull Request 신청 4. 자동 검사 (test coverage 미달, 코드 스타일, 잠재적 취약성 등), 테스트 실행 5. Code Review 6. Merge!

Slide 18

Slide 18 text

이런 embrassing한 상황에 처하기도 하지만.. ● Setting this image as 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]++; } }

Slide 19

Slide 19 text

버그 발생시 프로세스는.. 1. Issue Tracker에 버그 보고 2. 해당 Pulll Request를 통째로 roll back 3. 보고된 버그를 재현할 수 있는 테스트 코드 작성 (물론 테스트 케이스는 fail) 4. 테스트 코드를 pass 할 수 있는 코드 작성 5. Pull Request 신청 (이하 동일)

Slide 20

Slide 20 text

왜 테스트 코드를 짜는가? ● 효율적으로 버그를 잡아내기 위해 ● 코드를 쉽게 변경할 수 있게 하기 위해

Slide 21

Slide 21 text

테스트로 얻을 수 있는 이점 ● 코딩 생산성 증대: 확신을 갖고 시스템을 수정할 수 있음 ● 문서로서의 테스트 코드. 테스트 케이스만 보면 특정 시스템의 기능과 의도, 올바른 사용법을 단번에 파악 가능 ● 협업을 촉진: code owner가 아니더라도 코드를 수정할 수 있음 ● Simpler reviews ● modular한 설계에 도움: single responsibility

Slide 22

Slide 22 text

실패 사례: Google Web Server in 2005 ● 거의 테스트 코드가 없었음 ○ 특정 시점에서는 무려 80% 이상의 PR이 버그로 인해 롤백되었음 -_-; ○ 개발자들이 기능을 수정할 때 잘 동작하리라는 확신이 없어짐 ● 이 문제를 분석한 결과, 자동화 테스트만이 해결책이라는 결론 ○ 코드 변경시에는 반드시 테스트를 함께 구현하도록 정책 변경 ○ 1년 뒤 → 긴급 패치 숫자가 절반으로 감소 ○ 현재 → GWS 단일 시스템에서만 수만 개의 테스트 케이스

Slide 23

Slide 23 text

Google의 테스트 정책 ● 높은 Test Coverage*: 여기서 테스트 커버리지는 오직 small-sized test로만 측정함 ● Beyoncé Rule: “If you liked it, then you shoulda put a test on it.” * 프로덕트 별로 차이가 있음

Slide 24

Slide 24 text

만약 작은 팀 혹은 혼자 코딩하고 있다면? * ● PR을 날리기 전에 이슈(aka 태스크, 스토리) 하나 당 적어도 두 개의 E2E 테스트 (정상/실패), 그리고 그것이 2x 정도의 소규모 테스트 작성 ● 이슈를 중심으로 high level -> low level로 내려가면서 테스트 작성 ● 버그 발생 시, 재현을 위한 테스트부터 작성 후 처리하기 * 개인적인 의견입니다

Slide 25

Slide 25 text

IV. Google의 테스트 코드 작성 원칙

Slide 26

Slide 26 text

Unchanging Test ● Brittle test: 테스트 유지 보수 과정에서 만나는 가장 큰 문제 ● 테스트 불변의 원칙: 한 번 짠 코드는 테스트는 아래의 이유로 변경되지 않는다 ○ Pure refactorings ○ New features ○ Bug fixes ● 단, behavior changes의 경우에만 테스트를 수정

Slide 27

Slide 27 text

Idiom 1. Test via Public APIs public void processTransaction(Transaction transaction) { if (isValid(transaction)) { saveToDatabase(transaction); } } private boolean isValid(Transaction t) { return t.getAmount() < t.getSender().getBalance(); } private void saveToDatabase(Transaction t) { String s = t.getSender() + "," + t.getRecipient() + "," + t.getAmount(); database.put(t.getId(), s); }

Slide 28

Slide 28 text

잘못된 예 @Test public void emptyAccountShouldNotBeValid() { assertThat(processor.isValid(newTransaction().setSender(EMPTY_ACCOUNT))) .isFalse(); } @Test public void shouldSaveSerializedData() { processor.saveToDatabase(newTransaction() .setId(123) .setSender("me") .setRecipient("you") .setAmount(100)); assertThat(database.get(123)).isEqualTo("me,you,100"); }

Slide 29

Slide 29 text

올바른 예 @Test public void shouldTransferFunds() { processor.setAccountBalance("me", 150); processor.setAccountBalance("you", 20); processor.processTransaction(newTransaction() .setSender("me") .setRecipient("you") .setAmount(100)); assertThat(processor.getAccountBalance("me")).isEqualTo(50); assertThat(processor.getAccountBalance("you")).isEqualTo(120); }

Slide 30

Slide 30 text

올바른 예 @Test public void shouldNotPerformInvalidTransactions() { processor.setAccountBalance("me", 50); processor.setAccountBalance("you", 20); processor.processTransaction(newTransaction() .setSender("me") .setRecipient("you") .setAmount(100)); assertThat(processor.getAccountBalance("me")).isEqualTo(50); assertThat(processor.getAccountBalance("you")).isEqualTo(20); }

Slide 31

Slide 31 text

Idiom 2. Test State, Not Interactions 잘못된 예 @Test public void shouldWriteToDatabase() { accounts.createUser("foobar"); verify(database).put("foobar"); }

Slide 32

Slide 32 text

좋은 예 @Test public void shouldCreateUsers() { accounts.createUser("foobar"); assertThat(accounts.getUser("foobar")).isNotNull(); }

Slide 33

Slide 33 text

Idiom 3. Make Your Tests Complete and Concise 잘못된 예 @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? }

Slide 34

Slide 34 text

Idiom 3. Make Your Tests Complete and Concise ● 원칙: 테스트 케이스의 본문은, ○ 테스트를 이해하기 위한 모든 정보를 담고 있어야 함 ○ 테스트와 상관없거나 이해를 방해하는 정보는 최대한 감춰야 함 @Test public void shouldPerformAddition() { Calculator calculator = newCalculator(); int result = calculator.calculate(newCalculation(2, Operation.PLUS, 3)); assertThat(result).isEqualTo(5); }

Slide 35

Slide 35 text

Idiom 4. Test Behaviors, Not Methods 테스트 대상 코드 public void displayTransactionResults(User user, Transaction transaction) { ui.showMessage("You bought a " + transaction.getItemName()); if (user.getBalance() < LOW_BALANCE_THRESHOLD) { ui.showMessage("Warning: your balance is low!"); } }

Slide 36

Slide 36 text

잘못된 예: 메소드 중심 테스트 @Test public void testDisplayTransactionResults() { 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"); }

Slide 37

Slide 37 text

좋은 예: 행위 중심 테스트 @Test public void displayTransactionResults_showsItemName() { 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"); }

Slide 38

Slide 38 text

Idiom 5. Structure tests to emphasize behaviors (좋은 예) @Test 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)); }

Slide 39

Slide 39 text

또 다른 좋은 예 @Test public void shouldTimeOutConnections() { // 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();

Slide 40

Slide 40 text

Idiom 6. Write Clear Failure Messages ● 나쁜 예 ○ Test failed: account is closed ● 좋은 예 ○ Expected an account in state CLOSED, but got account: {name: "my-account", state: "OPEN"}

Slide 41

Slide 41 text

Idiom 7. Don’t Put Logic in Tests 나쁜 예 @Test public void shouldNavigateToAlbumsPage() { String baseUrl = "http://photos.google.com/"; Navigator nav = new Navigator(baseUrl); nav.goToAlbumPage(); assertThat(nav.getCurrentUrl()).isEqualTo(baseUrl + "/albums"); }

Slide 42

Slide 42 text

좋은 예 @Test public void shouldNavigateToPhotosPage() { Navigator nav = new Navigator("http://photos.google.com/"); nav.goToPhotosPage(); assertThat(nav.getCurrentUrl())) .isEqualTo("http://photos.google.com//albums"); // Oops! }

Slide 43

Slide 43 text

Idiom 8. DAMP, Not DRY ● DAMP: Descriptive And Meaningful Phrases ● DRY: Don't Repeat Yourself

Slide 44

Slide 44 text

나쁜 예 @Test public void shouldAllowMultipleUsers() { List users = createUsers(false, false); Forum forum = createForumAndRegisterUsers(users); validateForumAndUsers(forum, users); } @Test public void shouldNotAllowBannedUsers() { List users = createUsers(true); Forum forum = createForumAndRegisterUsers(users); validateForumAndUsers(forum, users); } // Lots more tests... private static List createUsers(boolean... banned) { // ... } // ...

Slide 45

Slide 45 text

좋은 예 @Test public void shouldAllowMultipleUsers() { User user1 = 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(); }

Slide 46

Slide 46 text

좋은 예 2 @Test public void shouldNotRegisterBannedUsers() { User user = newUser().setState(State.BANNED).build(); Forum forum = new Forum(); try { forum.register(user); } catch(BannedUserException ignored) {} assertThat(forum.hasRegisteredUser(user)).isFalse(); }

Slide 47

Slide 47 text

Idiom 9. No Shared Value 나쁜 예 private static final Account ACCOUNT_1 = Account.newBuilder() .setState(AccountState.OPEN).setBalance(50).build(); private static final Account ACCOUNT_2 = Account.newBuilder() .setState(AccountState.CLOSED).setBalance(0).build(); private static final Item ITEM = Item.newBuilder() .setName("Cheeseburger").setPrice(100).build(); // Hundreds of lines of other tests... @Test public void canBuyItem_returnsFalseForClosedAccounts() { assertThat(store.canBuyItem(ITEM, ACCOUNT_1)).isFalse(); } // ...

Slide 48

Slide 48 text

좋은 예 private static Contact.Builder newContact() { return Contact.newBuilder() .setFirstName("Grace") .setLastName("Hopper") .setPhoneNumber("555-123-4567"); } @Test public void fullNameShouldCombineFirstAndLastNames() { Contact contact = newContact() .setFirstName("Ada") .setLastName("Lovelace") .build(); assertThat(contact.getFullName()).isEqualTo("Ada Lovelace"); }

Slide 49

Slide 49 text

Idiom 10. Shared Setup 나쁜 예 private NameService nameService; private UserStore userStore; @Before public void setUp() { nameService = new NameService(); nameService.set("user1", "Donald Knuth"); userStore = new UserStore(nameService); } // [... hundreds of lines of tests ...] @Test public void shouldReturnNameFromService() { UserDetails user = userStore.get("user1"); assertThat(user.getName()).isEqualTo("Donald Knuth"); }

Slide 50

Slide 50 text

좋은 예 private NameService nameService; private UserStore userStore; @Before public void setUp() { nameService = new NameService(); nameService.set("user1", "Donald Knuth"); userStore = new UserStore(nameService); } @Test public void shouldReturnNameFromService() { nameService.set("user1", "Margaret Hamilton"); UserDetails user = userStore.get("user1"); assertThat(user.getName()).isEqualTo("Margaret Hamilton"); }

Slide 51

Slide 51 text

좋은 helper의 예 private void assertUserHasAccessToAccount(User user, Account account) { for (long userId : account.getUsersWithAccess()) { if (user.getId() == userId) { return; } } fail(user.getName() + " cannot access " + account.getName()); }

Slide 52

Slide 52 text

외부 의존은 어떻게 처리할 것인가? ● TDD by Example 입문 이후 최대 난관 ● 원칙은 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(); }

Slide 53

Slide 53 text

여기까지 모든 테스팅 관련 내용은 여기에서.. ● Google Testing Blog ○ https://testing.googleblog.com/ ● TotT(Testing on the Toilet) ○ https://testing.googleblog.com/search/label/TotT

Slide 54

Slide 54 text

Thank You https://speakerdeck.com/saryong