clean architecture & tdd · 2017. 9. 20. · • android app developer • unit test enthusiast...
TRANSCRIPT
Clean Architecture & TDD@fushiroyama
About Me
• Fumihiko Shiroyama
• Android App Developer
• Unit Test Enthusiast
• https://github.com/srym
Clean Architecture
• Presentation
• Domain
• Infrastructure
TDD
• Test Driven Development
• Test First
• Minimum Implementation
• Refactoring
TDD is great! because...
• Focus on I/O
• Less reworking
• Force Unit Testing
Example
• Infrastructure
• Remote Data Source
• GitHub Information
• Local Unit Test
Interface
public interface RemoteGitHubDataSource { Single<List<Repo>> listRepos(@NonNull String user);}
Blank Implementation
public class RestGitHubDataSource implements RemoteGitHubDataSource { @Override public Single<List<Repo>> listRepos(@NonNull String user) { return Single.error(new RuntimeException()); }}
Blank Implementation
public class RestGitHubDataSource implements RemoteGitHubDataSource { @Override public Single<List<Repo>> listRepos(@NonNull String user) { return Single.error(new RuntimeException()); }}
Create Test
• Mouse over class
• Alt + Enter
• Create Test
Create Test
Create Test
By the way...
• Mouse over class
• Shift + Command + T
• Choose Test
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! }}
Test First
@Testpublic void listRepos() throws Exception { List<Repo> repos = restGitHubDataSource.listRepos("srym") .test() .await() .assertNoErrors() .assertComplete() .values() .get(0); assertThat(repos).isNotNull();}
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();
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
Test Execution (failure)
• This of course fails.
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); }}
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); }}
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); }}
But wait...
• This is Local Unit Test
• Mock data? Hmm.
MockWebServer
MockWebServer
• Provided by OkHttp
• Full HTTP Stack
• Can test REAL response
MockWebServer
private final MockWebServer mockWebServer = new MockWebServer();
MockWebServer
Dispatcher dispatcher = new Dispatcher() { @Override public MockResponse dispatch(RecordedRequest request) throws InterruptedException { return new MockResponse().setResponseCode(404); }};mockWebServer.setDispatcher(dispatcher);mockWebServer.start();
MockWebServer
@Overridepublic 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);}
MockWebServer
@Overridepublic 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);}
MockWebServer
@Overridepublic 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);}
MockWebServer
@Overridepublic 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
MockWebServer
Retrofit retrofit = new Retrofit.Builder() .baseUrl(mockWebServer.url("")) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .build();GitHubService gitHubService = retrofit.create(GitHubService.class);
MockWebServer
Retrofit retrofit = new Retrofit.Builder() .baseUrl(mockWebServer.url("")) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .build();GitHubService gitHubService = retrofit.create(GitHubService.class);
Prepare Data
• Curl
• Postman
curl https://api.github.com/users/srym/repos > users_repos.json
Prepare Data
• Put it test/resources
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();}
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. }
Passed!
Refactoring
• TDD is NOT perfect
• Repeat Write & Test
Cons
• Useless when API changes
• Takes longer time
Pros
• Quality
• Relief
• Takes shorter time in total
TDD rocks!
Thank You
Links
• https://github.com/srym/Architecture
• https://github.com/square/okhttp/tree/master/mockwebserver
• http://www.irasutoya.com/