4developers 2015: przejrzysty i testowalny kod na androidzie? spróbujmy z clean architecture -...

62

Upload: proidea

Post on 15-Jul-2015

130 views

Category:

Software


1 download

TRANSCRIPT

Page 1: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas
Page 2: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

Testowalna aplikacja na Androida?

Spróbujmy z Clean Architecture.

Michał Charmas

Page 3: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

O mnie

• Developer aplikacji mobilnych na Androida

• Trener w firmie Bottega IT Solutions

• Konsultant / Freelancer

Page 4: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

Photo by http://commons.wikimedia.org/wiki/User:Mattbuck / CC BY

Page 5: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

• Kwestie techniczne / optymalizacyjne - raczej łatwe i dobrze opisane

• Jak żyć?

• jak bezboleśnie wprowadzać zmiany w aplikacji?

• jak nie psuć wcześniej działających ficzerów wprowadzonymi zmianami?

• jak testować?

• jak dzielić odpowiedzialność?

• jak osiągnąć mniejszy coupling?

• jakich patternów / biliotek używac żeby to osiągnąć

Page 6: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

Jak żyć?

Page 7: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

ioChed 2014

Page 8: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

–Android Developers Blog

„…the other primary goal is to serve as a practical example of best practices for Android app design

and development.”

Page 9: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

• Gdzie zasada pojedynczej odpowiedzialności?

• Logika domenowa w UI

• Logika UI pomieszana z asynchronicznym pobieraniem danych

• Callbacks everywhere

• Mapowanie kursorów na obiekty biznesowe w UI

• Activity i Fragmenty po 1000+ linii

• Całkowite uzależnienie od frameworka (import android.*)

• Testy?

Page 10: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

A 2

B 4

A 2

B 4

A 2

B 4

2 * 4 = 8

2 + 4 = 6

*

* J.B. Rainsberger - Integrated Tests Are A Scam (https://vimeo.com/80533536)

Page 11: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas
Page 12: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas
Page 13: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas
Page 14: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

Application

Networking

Persistance

Android SDK Models / Domain

Android Application

Application

Android SDKNetworking

Persistance

Models / Domain

import  android.* „szczegół techniczny”

Page 15: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

Źródło: http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html

Page 16: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

• Niezależna od frameworka.

• Niezależna od interfejsu użytkownika.

• Niezależna od bazy danych.

• Testowalna - w oderwaniu od frameworka / bazy danych / serwera.

Page 17: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas
Page 18: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

public class Product { private final long id; private final String name; private boolean isBought; public Product(long id, String name, boolean isBought){ this.id = id; this.name = name; this.isBought = isBought; } public void markBought() { this.isBought = true; } public void markNotBought() { this.isBought = false; }

//getters}

@Test public void testShouldBeBoughtWhenMarkedAsBought() throws Exception { Product product = new Product(0, "sample name", false); product.markBought(); assertEquals(true, product.isBought()); }

Page 19: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

public class ProductList implements Iterable<Product> { public Product addProduct(String productName) { } public int removeBoughtProducts() { } public Product getProduct(long id) { } public int size() { } @Override public Iterator<Product> iterator() { } }

Page 20: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

–Robert C. Martin

„A good architecture emphasizes the use-cases and decouples them from peripheral concerns.”

Page 21: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

• UseCases:

• AddProduct

• ListProducts

• ChangeProductBoughtStatus

• RemoveAllBoughtProducts

public  interface  UseCase<Result,  Argument>  {      Result  execute(Argument  arg)  throws  Exception;  }  

public  interface  UseCaseArgumentless<Result>  {      Result  execute()  throws  Exception;  }  

Page 22: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

public  class  AddProductUseCaseTest  {      private  AddProductUseCase  useCase;  

   @Before  public  void  setUp()  throws  Exception  {          useCase  =  new  AddProductUseCase();      }  

   @Test  public  void  testShouldAddProduct()  throws  Exception  {          useCase.execute("Sample  product");  

       //TODO:  verify  saved      }  }  

Page 23: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

public interface ProductsDataSource { ProductList getProductList(); void saveProductList(ProductList products); }

public class AddProductUseCaseTest { private static final String PRODUCT_NAME = "Sample product"; @Mock ProductsDataSource productsDataSourceMock; @Mock ProductList mockProducts; private AddProductUseCase useCase; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); useCase = new AddProductUseCase(productsDataSourceMock); } @Test public void testShouldAddProduct() throws Exception { when(productsDataSourceMock.getProductList()).thenReturn(mockProducts); useCase.execute(PRODUCT_NAME); verify(mockProducts, times(1)).addProduct(PRODUCT_NAME); verify(productsDataSourceMock).saveProductList(mockProducts); } }

Page 24: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

public class AddProductUseCase implements UseCase<Product, String> { private final ProductsDataSource productsDataSource; @Inject public AddProductUseCase(ProductsDataSource productsDataSource) { this.productsDataSource = productsDataSource; } @Override public Product execute(final String productName) { if (productName == null || productName.trim().isEmpty()) { throw new ValidationException("Product name cannot be empty"); } ProductList productList = productsDataSource.getProductList(); Product product = productList.addProduct(productName); productsDataSource.saveProductList(productList); return product; } }

Page 25: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

• całkowicie niezależne od frameworka

• pure Java

• może zostać wyciągnięte do oddzielnego modułu - czysto javowego

Page 26: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

Persistence / Data

Page 27: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

• Domena nie wie o sposobie utrwalania danych.

• Sposób utrwalania danych nie powinien operować na obiektach domeny (wyciekająca abstrakcja od środka).

• Wyznaczenie granicy.

• Trzymanie się „zasady zależności”.

Page 28: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

Entities

UseCases

DataSource

DataSourceImpl

StoreRESTStore

InMemoryCacheStoreSharedPreferencesStore

[Product] [ProductList]

[Product -> ProducDBEntity] [ProducDBEntity]

Page 29: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

public  class  ProductEntity  {      private  final  long  id;      private  final  String  name;      private  final  boolean  isBought;  

   public  ProductEntity(long  id,  String  name,  boolean  isBought)  {          this.id  =  id;          this.name  =  name;          this.isBought  =  isBought;      }  

   public  long  getId()  {          return  id;      }  

   public  String  getName()  {          return  name;      }  

   public  boolean  isBought()  {          return  isBought;      }  }  

Page 30: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

• ProductDataSourceImpl - implementacja oderwana od zewnętrznych bibliotek

• Zależy tylko od ProductEntityStore i Mappera (Product -> ProductEntity)

• Łatwe do zmockowania aby przetestować w izolacji.

public interface ProductEntityStore { List<ProductEntity> getAllProduct(); void storeAllProducts(List<ProductEntity> entities); }

Page 31: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

http://upload.wikimedia.org/wikipedia/commons/1/10/20090529_Great_Wall_8185.jpg

Page 32: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

public  class  SharedPreferencesProductEntityStore  implements  ProductEntityStore  {    private  final  SharedPreferences  sharedPreferences;    private  final  EntityJsonMapper  entityJsonMapper;

   //…      @Inject    public  SharedPreferencesProductEntityStore(SharedPreferences  sharedPreferences,            EntityJsonMapper  entityJsonMapper)  {        this.sharedPreferences  =  sharedPreferences;        this.entityJsonMapper  =  entityJsonMapper;    }    @Override  public  List<ProductEntity>  getAllProduct()  {        try  {            return  entityJsonMapper.fromJson(                      sharedPreferences.getString(SP_PRODUCT_ENTITIES,                        „[]”              ));        }  catch  (JSONException  e)  {            handleJSONError(e);            return  Collections.emptyList();        }    }  

//…  }  

Page 33: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

public  class  SharedPreferencesProductEntityStoreTest  extends  AndroidTestCase  {    private  SharedPreferencesProductEntityStore  store;    @Override  public  void  setUp()  throws  Exception  {        super.setUp();        store  =  new  SharedPreferencesProductEntityStore(                getAndClearSharedPreferences(),                new  SharedPreferencesProductEntityStore.EntityJsonMapper()        );    }    public  void  testShouldStoreEmptyList()  throws  Exception  {        store.storeAllProducts(Collections.<ProductEntity>emptyList());        assertEquals(store.getAllProduct().size(),  0);    }}

Page 34: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

Entities

UseCases

DataSource

DataSourceImpl

StoreRESTStore

InMemoryCacheStoreSharedPreferencesStore

[Product] [ProductList]

[Product -> ProducDBEntity] [ProducDBEntity]

Page 35: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

UI

Page 36: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

Źródło: http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html

Page 37: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

Model View Presenter

Passive View

Presenter

Model

user events

updates model

view updates

state changes

Page 38: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas
Page 39: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas
Page 40: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

LOLCycle

Page 41: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

public interface Presenter<T extends UI> { void attachUI(T ui); }

Page 42: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

UI1

P1 P1

UI1 UI2

P2

Page 43: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

UI1

P1

UI2UI1

Page 44: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

public interface Presenter<T extends UI> { void attachUI(T ui); void detachUI(); }

Page 45: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

UI1

P1

UI2

P1

AsyncOperation

P1

Page 46: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

public interface UICommand<T> { void execute(T ui); }

private Queue<UICommand<T>> commandQueue = new LinkedList<>();

UI1

P1

UI2

P1

AsyncOperation

P1

Page 47: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

@Singletonpublic class ProductListPresenter extends BasePresenter<ProductListPresenter.ProductListUI> { @Override protected void onFirstUIAttachment() { updateProductList(true); } public void productStatusChanged(long productId, boolean isBought) { } public void onAddNewProduct() { } public void onRemoveBoughtProducts() { } private void updateProductList(boolean withProgress) { if (withProgress) { execute(new ShowProgressCommand(), true); } asyncUseCase.wrap(listProductsUseCase).subscribe( new Action1<ProductList>() { @Override public void call(ProductList products) { List<ProductViewModel> viewModels = mapper.toViewModel(products); executeRepeat(new PresentContentCommand(viewModels)); } } ); } }

Page 48: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

@Singletonpublic class AddProductPresenter extends BasePresenter<AddProductPresenter.AddProductUI> { private final AddProductUseCase addProductUseCase; private final AsyncUseCase asyncUseCase; @Inject public AddProductPresenter(AddProductUseCase addProductUseCase, AsyncUseCase asyncUseCase) { this.addProductUseCase = addProductUseCase; this.asyncUseCase = asyncUseCase; } public void onProductNameComplete(final String productName) { asyncUseCase.wrap(addProductUseCase, productName) .subscribe( new Action1<Product>() { @Override public void call(Product product) { executeRepeat(new NavigateBackCommand()); } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { if (throwable instanceof ValidationException) { executeOnce(new ShowValidationErrorCommand()); } // TODO: handle unknown error } }); } }

Page 49: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

private static class ShowValidationErrorCommand implements UICommand<AddProductUI> { @Override public void execute(AddProductUI ui) { ui.showValidationError(); } } private static class NavigateBackCommand implements UICommand<AddProductUI> { @Override public void execute(AddProductUI ui) { ui.navigateBack(); } } public interface AddProductUI extends UI { void navigateBack(); void showValidationError(); }

Page 50: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

Pink Floyd - The Wall Cover

Page 51: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

public class AddProductFragment extends PresenterFragment<AddProductUI> implements AddProductUI { @Inject AddProductPresenter presenter; private EditText productNameView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setupPresenter(presenter, this); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_add_product, container, false); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); productNameView = (EditText) view.findViewById(R.id.product_name_view); view.findViewById(R.id.product_add_button) .setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { presenter.onProductNameComplete(productNameView.getText().toString()); } }); } @Override public void navigateBack() { ((ProductListFragmentActivity) getActivity()).onProductAdded(); } @Override public void showValidationError() { Toast.makeText(getActivity(), "Product name cannot be empty.", Toast.LENGTH_SHORT).show(); } }

Page 52: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

Entities

UseCases

Presenters

UI ProductListUI

AddProductUI

[Product]

[Product -> ProductViewModel] [ProductViewModel]

Page 53: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

Zapisywanie stanu• Input użytkownika jest stanem - nie model danych.

• Stan == Parcelable

• UI Zapisuje swój własny stan - EditText, ListView z automatu.

• Powinniśmy trzymać stan w Presenterach?

Page 54: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

DI

Page 55: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

Kontener DI

• ma konstruktor @Inject - wiem jak go znaleźć - tworzę i zwracam

• nie wiem skąd go wziąć? - sięgam do modułu

Moduły Litania metod dostarczających zależności.

Dagger

daj obiekt

ObjectGraph

@Singleton

Page 56: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

AppObjectGraph • AndroidModule • DataModule • DomainModule Application Scope

PresenterActivityOG • PresenterModule • Preseneter - @Singleton onRetainCustomNonConfigurationInstance

ActivityOG • ActivityModule Activity Scope

Page 57: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

Wady / Zalety?• Overhead dla małych aplikacji - na pewno wiadomo, że nie urośnie?

• Duża ilość wywołań metod.

• Mniej optymalne rozwiązanie pamięciowo - tworzy i zwalnia się dużo obiektów, fragmentacja pamięci.

• Samo podejście i ,,zasada zależności” słuszne - umożliwiają testowanie.

Page 58: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

–Robert C. Martin

„A good architecture allows major decisions to be deffered.”

Page 59: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

https://github.com/mcharmas/shoppinglist-clean-architecture-example

Page 60: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html

http://blog.8thlight.com/uncle-bob/2011/09/30/Screaming-Architecture.html

https://vimeo.com/43612849

https://vimeo.com/80533536

http://tpierrain.blogspot.com/2013/08/a-zoom-on-hexagonalcleanonion.html

Page 61: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

Na czym polega ,,zasada zależności" w Clean Architecture?

Page 62: 4Developers 2015: Przejrzysty i testowalny kod na Androidzie? Spróbujmy z Clean Architecture - Michał Charmas

Q&A?