Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Clean Architecture & TDD

Clean Architecture & TDD

Clean Architecture & TDD @Android Test Night #1
https://testnight.connpass.com/event/63753/

3cca191bf3064fd059ea2c3d6022afbd?s=128

Fumihiko Shiroyama

September 20, 2017
Tweet

Transcript

  1. Clean Architecture & TDD @fushiroyama

  2. About Me • Fumihiko Shiroyama • Android App Developer •

    Unit Test Enthusiast • https://github.com/srym
  3. Clean Architecture • Presentation • Domain • Infrastructure

  4. TDD • Test Driven Development • Test First • Minimum

    Implementation • Refactoring
  5. TDD is great! because... • Focus on I/O • Less

    reworking • Force Unit Testing
  6. Example • Infrastructure • Remote Data Source • GitHub Information

    • Local Unit Test
  7. Interface public interface RemoteGitHubDataSource { Single<List<Repo>> listRepos(@NonNull String user); }

  8. Blank Implementation public class RestGitHubDataSource implements RemoteGitHubDataSource { @Override public

    Single<List<Repo>> listRepos(@NonNull String user) { return Single.error(new RuntimeException()); } }
  9. Blank Implementation public class RestGitHubDataSource implements RemoteGitHubDataSource { @Override public

    Single<List<Repo>> listRepos(@NonNull String user) { return Single.error(new RuntimeException()); } }
  10. Create Test • Mouse over class • Alt + Enter

    • Create Test
  11. Create Test

  12. Create Test

  13. By the way... • Mouse over class • Shift +

    Command + T • Choose Test
  14. Test First public class RestGitHubDataSourceTest { private RestGitHubDataSource dataSource; @Before

    public void setUp() throws Exception { dataSource = new RestGitHubDataSource(); } @Test public void listRepos() throws Exception { // implement here! } }
  15. Test First @Test public void listRepos() throws Exception { List<Repo>

    repos = restGitHubDataSource.listRepos("srym") .test() .await() .assertNoErrors() .assertComplete() .values() .get(0); assertThat(repos).isNotNull(); }
  16. Test First assertThat(repos).isNotNull().isNotEmpty(); Repo repo = repos.get(0); assertThat(repo).isNotNull(); assertThat(repo.getFullName()).isNotBlank(); assertThat(repo.getId()).isGreaterThanOrEqualTo(0);

    assertThat(repo.getOwner()).isNotNull();
  17. Test First assertThat(repos).isNotNull().isNotEmpty(); Repo repo = repos.get(0); assertThat(repo).isNotNull(); assertThat(repo.getFullName()).isNotBlank(); assertThat(repo.getId()).isGreaterThanOrEqualTo(0);

    assertThat(repo.getOwner()).isNotNull(); You can confirm the specs BEFORE you implement
  18. Test Execution (failure) • This of course fails.

  19. Minimum Implementation public class RestGitHubDataSource implements RemoteGitHubDataSource { private final

    GitHubService gitHubService; @Inject public RestGitHubDataSource(GitHubService gitHubService) { this.gitHubService = gitHubService; } @Override public Single<List<Repo>> listRepos(@NonNull String user) { return gitHubService.listRepos(user); } }
  20. Minimum Implementation public class RestGitHubDataSource implements RemoteGitHubDataSource { private final

    GitHubService gitHubService; @Inject public RestGitHubDataSource(GitHubService gitHubService) { this.gitHubService = gitHubService; } @Override public Single<List<Repo>> listRepos(@NonNull String user) { return gitHubService.listRepos(user); } }
  21. Minimum Implementation public class RestGitHubDataSource implements RemoteGitHubDataSource { private final

    GitHubService gitHubService; @Inject public RestGitHubDataSource(GitHubService gitHubService) { this.gitHubService = gitHubService; } @Override public Single<List<Repo>> listRepos(@NonNull String user) { return gitHubService.listRepos(user); } }
  22. But wait... • This is Local Unit Test • Mock

    data? Hmm.
  23. MockWebServer

  24. MockWebServer • Provided by OkHttp • Full HTTP Stack •

    Can test REAL response
  25. MockWebServer private final MockWebServer mockWebServer = new MockWebServer();

  26. MockWebServer Dispatcher dispatcher = new Dispatcher() { @Override public MockResponse

    dispatch(RecordedRequest request) throws InterruptedException { return new MockResponse().setResponseCode(404); } }; mockWebServer.setDispatcher(dispatcher); mockWebServer.start();
  27. MockWebServer @Override public MockResponse dispatch(RecordedRequest request) throws InterruptedException { if

    (request == null || request.getPath() == null) { return new MockResponse().setResponseCode(400); } if (request.getPath().matches("/users/.+/repos")) { return new MockResponse() .setBody(readJsonFromResources("users_repos.json")) .setResponseCode(200); } return new MockResponse().setResponseCode(404); }
  28. MockWebServer @Override public MockResponse dispatch(RecordedRequest request) throws InterruptedException { if

    (request == null || request.getPath() == null) { return new MockResponse().setResponseCode(400); } if (request.getPath().matches("/users/.+/repos")) { return new MockResponse() .setBody(readJsonFromResources("users_repos.json")) .setResponseCode(200); } return new MockResponse().setResponseCode(404); }
  29. MockWebServer @Override public MockResponse dispatch(RecordedRequest request) throws InterruptedException { if

    (request == null || request.getPath() == null) { return new MockResponse().setResponseCode(400); } if (request.getPath().matches("/users/.+/repos")) { return new MockResponse() .setBody(readJsonFromResources("users_repos.json")) .setResponseCode(200); } return new MockResponse().setResponseCode(404); }
  30. MockWebServer @Override public MockResponse dispatch(RecordedRequest request) throws InterruptedException { if

    (request == null || request.getPath() == null) { return new MockResponse().setResponseCode(400); } if (request.getPath().matches("/users/.+/repos")) { return new MockResponse() .setBody(readJsonFromResources("users_repos.json")) .setResponseCode(200); } return new MockResponse().setResponseCode(404); } Talk about this later
  31. MockWebServer Retrofit retrofit = new Retrofit.Builder() .baseUrl(mockWebServer.url("")) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .build();

    GitHubService gitHubService = retrofit.create(GitHubService.class);
  32. MockWebServer Retrofit retrofit = new Retrofit.Builder() .baseUrl(mockWebServer.url("")) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .build();

    GitHubService gitHubService = retrofit.create(GitHubService.class);
  33. Prepare Data • Curl • Postman curl https://api.github.com/users/srym/repos > users_repos.json

  34. Prepare Data • Put it test/resources

  35. Read JSON from file private String readJsonFromResources(@NonNull String fileName) {

    InputStream inputStream = getClass().getClassLoader().getResourceAsStream(fileName); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); StringBuilder stringBuilder = new StringBuilder(); try { String buffer; while ((buffer = bufferedReader.readLine()) != null) { stringBuilder.append(buffer); } } catch (IOException e) { fail(e.getMessage(), e); } return stringBuilder.toString(); }
  36. Fix Test @Before public void setUp() throws Exception { //

    abbr. dataSource = new RestGitHubDataSource(gitHubService); } @Test public void listRepos() throws Exception { List<Repo> repos = restGitHubDataSource.listRepos("srym") .test() .await() .assertNoErrors() .assertComplete() .values() .get(0); assertThat(repos).isNotNull(); // abbr. }
  37. Passed!

  38. Refactoring • TDD is NOT perfect • Repeat Write &

    Test
  39. Cons • Useless when API changes • Takes longer time

  40. Pros • Quality • Relief • Takes shorter time in

    total
  41. TDD rocks!

  42. Thank You

  43. Links • https://github.com/srym/Architecture • https://github.com/square/okhttp/tree/ master/mockwebserver • http://www.irasutoya.com/