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/

Fumihiko Shiroyama

September 20, 2017
Tweet

More Decks by Fumihiko Shiroyama

Other Decks in Technology

Transcript

  1. Clean Architecture &
    TDD
    @fushiroyama

    View full-size slide

  2. About Me
    • Fumihiko Shiroyama
    • Android App Developer
    • Unit Test Enthusiast
    • https://github.com/srym

    View full-size slide

  3. Clean Architecture
    • Presentation
    • Domain
    • Infrastructure

    View full-size slide

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

    View full-size slide

  5. TDD is great! because...
    • Focus on I/O
    • Less reworking
    • Force Unit Testing

    View full-size slide

  6. Example
    • Infrastructure
    • Remote Data Source
    • GitHub Information
    • Local Unit Test

    View full-size slide

  7. Interface
    public interface RemoteGitHubDataSource {
    Single> listRepos(@NonNull String user);
    }

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  10. Create Test
    • Mouse over class
    • Alt + Enter
    • Create Test

    View full-size slide

  11. By the way...
    • Mouse over class
    • Shift + Command + T
    • Choose Test

    View full-size slide

  12. 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!
    }
    }

    View full-size slide

  13. Test First
    @Test
    public void listRepos() throws Exception {
    List repos = restGitHubDataSource.listRepos("srym")
    .test()
    .await()
    .assertNoErrors()
    .assertComplete()
    .values()
    .get(0);
    assertThat(repos).isNotNull();
    }

    View full-size slide

  14. 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();

    View full-size slide

  15. 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

    View full-size slide

  16. Test Execution (failure)
    • This of course fails.

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  20. But wait...
    • This is Local Unit Test
    • Mock data? Hmm.

    View full-size slide

  21. MockWebServer

    View full-size slide

  22. MockWebServer
    • Provided by OkHttp
    • Full HTTP Stack
    • Can test REAL response

    View full-size slide

  23. MockWebServer
    private final MockWebServer mockWebServer
    = new MockWebServer();

    View full-size slide

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

    View full-size slide

  25. 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);
    }

    View full-size slide

  26. 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);
    }

    View full-size slide

  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);
    }

    View full-size slide

  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);
    }
    Talk about this later

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  31. Prepare Data
    • Curl
    • Postman
    curl https://api.github.com/users/srym/repos >
    users_repos.json

    View full-size slide

  32. Prepare Data
    • Put it test/resources

    View full-size slide

  33. 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();
    }

    View full-size slide

  34. Fix Test
    @Before
    public void setUp() throws Exception {
    // abbr.
    dataSource = new RestGitHubDataSource(gitHubService);
    }
    @Test
    public void listRepos() throws Exception {
    List repos = restGitHubDataSource.listRepos("srym")
    .test()
    .await()
    .assertNoErrors()
    .assertComplete()
    .values()
    .get(0);
    assertThat(repos).isNotNull();
    // abbr.
    }

    View full-size slide

  35. Refactoring
    • TDD is NOT perfect
    • Repeat Write & Test

    View full-size slide

  36. Cons
    • Useless when API changes
    • Takes longer time

    View full-size slide

  37. Pros
    • Quality
    • Relief
    • Takes shorter time in total

    View full-size slide

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

    View full-size slide