Developing application for Windows Phone 7 in TDD

1.692 visualizações

Publicada em

A real example of how to develop an application for Windows Phone 7 with Test Driven Development approach. In this presentation you'll see also hoew to implements the Model-View-ViewModel (MVVM) pattern.

Publicada em: Tecnologia
  • Seja o primeiro a comentar

Developing application for Windows Phone 7 in TDD

  1. 1. Sviluppare unapp per WP7 in TDD? Si può fare!
  2. 2. Who am I?• Developer freelance: – C# Asp.net Mvc, Wpf, Wp7 – Python Django – Blog: orangecode.it/blog• WEBdeBS founder
  3. 3. What we’re going to see• TDD• MVVM• Code..• Code…• …some more code!
  4. 4. The show case• Basic application• Download user songs list from SoundCloud.com• Display the list• View song’s detail
  5. 5. Screen Shot
  6. 6. Let’s do it with code behind
  7. 7. Trackpublic class Track{ public int Id{get; set; } public string Kind { get; set; } public string Title { get; set; } public string Description { get; set; } public string Artwork_url { get; set; } public string Created_at { get; set; }}
  8. 8. Xaml<phone:PhoneApplicationPage > <Grid x:Name="ContentPanel”> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal"> <TextBox x:Name="searchedText" /> <Button Content="Search" Click="Search" /> </StackPanel>
  9. 9. Xaml <ItemsControl Grid.Row="1” x:Name="songsList"> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <Image Source="{Binding Image}" /> <TextBlock Text="{Binding Title}"/> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </Grid></phone:PhoneApplicationPage>
  10. 10. Xaml<StackPanel Orientation="Horizontal"> <TextBox x:Name="searchedText" /> <Button Content="Search" Click="Search" /></StackPanel>
  11. 11. Xaml<ItemsControl x:Name="songsList"> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <Image Source="{Binding Image}”/> <TextBlock Text="{Binding Title}"/> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate></ItemsControl>
  12. 12. Code behind MainViewpublic partial class MainView :PhoneApplicationPage { public MainView() { InitializeComponent(); } private void Search(object sender, EventArgs e) { RestClient client = new RestClient { BaseUrl "http://api.soundcloud.com/users/"+searchedText.Text+"/tracks?client_id=eaf7649b0de68f902a4607c0b730e226" }; var request = new RestRequest { RequestFormat = DataFormat.Json }; client.ExecuteAsync<List<Track>>(request, response => { songsList.ItemsSource = response.Data; }); } private void ShowDetail(object sender, GestureEventArgs e) { NavigationService.Navigate(new Uri("/View/DetailView.xaml?id="+ ((Track)((StackPanel)sender).DataContext).Id, UriKind.Relative)); } }
  13. 13. Code behind MainViewprivate void Search(object sender, EventArgs e){ RestClient client = new RestClient { BaseUrl "http://api.soundcloud.com/ users/"+searchedText.Text+"/tracks? client_id=eaf7649b0de68f902a4607c0b730e226" }; var request = new RestRequest { RequestFormat = DataFormat.Json };
  14. 14. Code behind MainView client.ExecuteAsync<List<Track>>(request, response => { songsList.ItemsSource = response.Data; });}
  15. 15. Code behind MainViewprivate void ShowDetail(object sender, GestureEventArgs e){ NavigationService.Navigate( new Uri( "/View/DetailView.xaml?id="+ ((Track)((StackPanel)sender).DataContext).Id, UriKind.Relative ) );}
  16. 16. Xaml DetailView<phone:PhoneApplicationPage x:Class="OrangeCode.SoundCloud.View.MainView” > <Grid x:Name="LayoutRoot" Background="Transparent"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <StackPanel> <Image x:Name="image" Width="200" Height="200” /> <TextBlock x:Name="creationDate" FontSize="22” /> <TextBlock x:Name="title" FontSize="28” /> <TextBlock x:Name="description” TextWrapping=Wrap/> </StackPanel> </Grid> </Grid></phone:PhoneApplicationPage>
  17. 17. Xaml DetailView<Grid x:Name="ContentPanel”> <StackPanel> <Image x:Name="image” /> <TextBlock x:Name="creationDate” /> <TextBlock x:Name="title” /> <TextBlock x:Name="description” /> </StackPanel></Grid>
  18. 18. Code behind DetailViewpublic partial class DetailView : PhoneApplicationPage { public DetailView() { InitializeComponent(); } protected override void OnNavigatedTo(NavigationEventArgs e) { string id = NavigationContext.QueryString["id"]; RestClient client = new RestClient { BaseUrl = "http://api.soundcloud.com/tracks?client_id=eaf7649b0de68f902a4607c0b730e226&ids=" + id }; var request = new RestRequest { RequestFormat = DataFormat.Json }; client.ExecuteAsync<List<Track>>(request, response => { image.Source = new BitmapImage(new Uri(response.Data[0].Image)); creationDate.Text = response.Data[0].CreationDate; description.Text = response.Data[0].Description; title.Text = response.Data[0].Title; }); } }
  19. 19. Code behind DetailViewprotected override void OnNavigatedTo(NavigationEventArgs e){ string id = NavigationContext.QueryString["id"]; RestClient client = new RestClient { BaseUrl = "http://api.soundcloud.com/tracks? client_id=eaf7649b0de68f902a4607c0b730e226&ids=" + id }; var request = new RestRequest { RequestFormat = DataFormat.Json };
  20. 20. Code behind DetailView client.ExecuteAsync<List<Track>>(request, response => { image.Source = new BitmapImage( new Uri(response.Data[0].Image) ); creationDate.Text = response.Data[0].CreationDate; description.Text = response.Data[0].Description; title.Text = response.Data[0].Title; });}
  21. 21. Problem with code behind• Code coupled with UI – Xaml + Code Behind -> one class• Not testable
  22. 22. MVVM approach• Architectural Pattern• Derived from Presentation Model pattern (Fowler)• Clear separation between UI and Logic UI ViewModel Collections, DelegateCommand, Properties
  23. 23. MVVM approach• Structure our code: – ViewModel (c#): Logic – View (Xaml): Presentation – No more code behind• Now the ViewModel is testable
  24. 24. Test Driven Development• As easy as complex• Life Cycle: – Write test (red) – Write logic to pass the test (green) – Refactor code (refactor) – Again..
  25. 25. Test Driven Development• It’s about code design, not test• Test suite are good side effect of tdd• It require a lot of discipline and practice
  26. 26. Testing Tools• Nunit for Windows Phone 7• No official mocking framework for Windows Phone 7, but I found out that Moq 3.1 for silverlight works!
  27. 27. TDD• Download searched user songs list from SoundCloud.com
  28. 28. TDD• There is a list of track that i’ve to show
  29. 29. TDDnamespace OrangeCode.SoundCloudFixture{ public class MainViewModelFixture { }}namespace OrangeCode.SoundCloud{ public class MainViewModel { }}
  30. 30. TDD -RedWrite test: [Test] public void Constructor_Should_Initialize_TrackList() { MainViewModel viewModel = new MainViewModel(); Assert.IsNotNull(viewModel.Tracks); }
  31. 31. TDDYou are not allowed to write anyproduction code unless it is to make afailing unit test pass. Uncle Bob Martin
  32. 32. TDD - Redpublic class MainViewModel{ public IList<Track> Tracks { get ; set ; }}
  33. 33. TDD
  34. 34. TDDYou are not allowed to write any moreproduction code than is sufficient topass the one failing unit test. Uncle Bob Martin
  35. 35. TDD - Greenpublic class MainViewModel{ public IList<Track> Tracks { get; set; } public MainViewModel() { Tracks = new List<Track>(); }}
  36. 36. TDD
  37. 37. TDD –Refactorpublic class MainViewModel{ private IList<Track> _tracks; public IList<Track> Tracks { get { return _tracks; } } public MainViewModel() { _tracks = new List<Track>(); }}
  38. 38. TDD - Refactor
  39. 39. TDD• Download searched user songs list from SoundCloud.com
  40. 40. Architecture MainViewModel Ilist<Track>SearchUserTrack(string ) Search Rest Call Service
  41. 41. Architecture public interface ISearchService Main { ViewModel IList<Track> SearchUserTrack(string user); } public class SearchService : ISearchService {ISearchService public IList<Track> SearchUserTrack(string user){ } } Search Rest Call Service
  42. 42. TDD[Test]public void Search_Should_RetrieveSearchedUserTrack (){ Mock<ISearchService> service = new Mock<ISearchService>(); MainViewModel viewModel = new MainViewModel(service.Object); viewModel.SearchedText = "michelecapra"; viewModel.Search.Execute(); service.Verify(p=>p.SearchUserTrack("michelecapra"));}
  43. 43. TDD[Test]public void Search_Should_RetrieveSearchedUserTrack (){ Mock<ISearchService> service = new Mock<ISearchService>(); MainViewModel viewModel = new MainViewModel(service.Object); viewModel.SearchedText = "michelecapra"; viewModel.Search.Execute(); service.Verify(p=>p.SearchUserTrack("michelecapra"));}
  44. 44. TDD - Mock• Simulated objects that mimic the behavior of real objects in controlled ways• Mock objects have the same interface as the real objects they mimic, allowing a client object to remain unaware of whether it is using a real object or a mock object.
  45. 45. TDD[Test]public void Search_Should_RetrieveSearchedUserTrack (){ Mock<ISearchService> service = new Mock<ISearchService>(); MainViewModel viewModel = new MainViewModel(service.Object); viewModel.SearchedText = "michelecapra"; viewModel.Search.Execute(); service.Verify(p=>p.SearchUserTrack("michelecapra"));}
  46. 46. TDD- ICommand• The ICommand interface enables the abstraction of a parameterized method call through its Execute method.• Typically objects implement this interface to enable method calls on the objects through the use of XAML bindings.
  47. 47. TDD- DelegateCommand• ICommand whose delegates can be attached for Execute(T)• Execute(T) is the method to be called when the command is invoked.<Button Command=“”></Button>
  48. 48. TDD[Test]public void Search_Should_RetrieveSearchedUserTrack (){ Mock<ISearchService> service = new Mock<ISearchService>(); MainViewModel viewModel = new MainViewModel(service.Object); viewModel.SearchedText = "michelecapra"; viewModel.Search.Execute(); service.Verify(p=>p.SearchUserTrack("michelecapra"));}
  49. 49. TDD - RedAdd properties in order to compilepublic string SearchedText { get; set; }public DelegateCommand Search { get; set; }
  50. 50. TDD - Red
  51. 51. TDD - Greenpublic string SearchedText { get; set; }public DelegateCommand Search { get; set; }public MainViewModel(ISearchService searchService){ _searchService = searchService; _tracks = new List<Track>(); Search = new DelegateCommand(OnSearch);}private void OnSearch(){ _searchService.SearchUserTrack(SearchedText);}
  52. 52. TDD - Green
  53. 53. TDD - Refactorpublic string SearchedText { get; set; }public DelegateCommand Search { get; private set; }public MainViewModel(ISearchService searchService){ _searchService = searchService; _tracks = new List<Track>(); Search = new DelegateCommand(OnSearch);}private void OnSearch(){ _searchService.SearchUserTrack(SearchedText);}
  54. 54. TDD - Refactor
  55. 55. TDD• Display the list
  56. 56. TDD[Test]public void Search_Should_UpdateTrackList(){ _searchService.Setup(p =>p.SearchUserTrack("michelecapra")).Returns(new List<Track>{newTrack()}); _viewModel.SearchedText = "michelecapra"; _viewModel.Search.Execute(); Assert.AreEqual(_viewModel.Tracks.Count, 1);}
  57. 57. TDD[Test]public void Search_Should_UpdateTrackList(){ _searchService.Setup(p =>p.SearchUserTrack("michelecapra")).Returns(new List<Track>{newTrack()}); _viewModel.SearchedText = "michelecapra"; _viewModel.Search.Execute(); Assert.AreEqual(_viewModel.Tracks.Count, 1);}
  58. 58. TDD[Test]public void Search_Should_UpdateTrackList(){ _searchService.Setup(p =>p.SearchUserTrack("michelecapra")).Returns(new List<Track>{newTrack()}); _viewModel.SearchedText = "michelecapra"; _viewModel.Search.Execute(); Assert.AreEqual(_viewModel.Tracks.Count, 1);}
  59. 59. TDD - Redpublic string SearchedText { get; set; }public DelegateCommand Search { get; private set; }public MainViewModel(ISearchService searchService){ _searchService = searchService; _tracks = new List<Track>(); Search = new DelegateCommand(OnSearch);}private void OnSearch(){ _searchService.SearchUserTrack(SearchedText);}
  60. 60. TDD - Red
  61. 61. TDD - Greenprivate void OnSearch(){ _tracks= _searchService.SearchUserTrack(SearchedText);}
  62. 62. TDD - Green
  63. 63. TDD• View song’s detail
  64. 64. TDD• We need to introduce the NavigationService• But how to decouple it from our ViewModel?
  65. 65. TDD – Navigation Servicepublic interface INavigationService THX to{ void NavigateTo(Uri pageUri); Laurent Bugnion}public class NavigationService : INavigationService{ private static PhoneApplicationFrame _mainFrame; public event NavigatingCancelEventHandler Navigating; public void NavigateTo(Uri pageUri) { … }}
  66. 66. TDD [Test] public void ShowDetail_Should_NavigateToDetailView() { var navigationService = new Mock<INavigationService>(); var searchService = new Mock<ISearchService>(); var viewModel = new MainViewModel(searchService.Object,navigationService.Object); viewModel.ShowDetail.Execute(new Track{Id=345}); navigationService.Verify(p => p.NavigateTo(new Uri("/View/DetailView.xaml?id=345", UriKind.Relative)));}
  67. 67. TDD [Test] public void ShowDetail_Should_NavigateToDetailView() { var navigationService = new Mock<INavigationService>(); var searchService = new Mock<ISearchService>(); var viewModel = new MainViewModel(searchService.Object,navigationService.Object); viewModel.ShowDetail.Execute(new Track{Id=345}); navigationService.Verify(p => p.NavigateTo(newUri("/View/DetailView.xaml?id=345", UriKind.Relative)));}
  68. 68. TDD [Test] public void ShowDetail_Should_NavigateToDetailView() { var navigationService = new Mock<INavigationService>(); var searchService = new Mock<ISearchService>(); var viewModel = new MainViewModel(searchService.Object,navigationService.Object); viewModel.ShowDetail.Execute(new Track{Id=345}); navigationService.Verify(p => p.NavigateTo(newUri("/View/DetailView.xaml?id=345", UriKind.Relative)));}
  69. 69. TDD [Test] public void ShowDetail_Should_NavigateToDetailView() { var navigationService = new Mock<INavigationService>(); var searchService = new Mock<ISearchService>(); var viewModel = new MainViewModel(searchService.Object,navigationService.Object); viewModel.ShowDetail.Execute(new Track{Id=345}); navigationService.Verify(p => p.NavigateTo(newUri("/View/DetailView.xaml?id=345", UriKind.Relative)));}
  70. 70. TDD - Redpublic class MainViewModel{ private IList<Track> _tracks; public IList<Track> Tracks { get { return _tracks; } } public string SearchedText { get; set; } public DelegateCommand Search { get; private set; } public DelegateCommand<Track> ShowDetail{get; set; }
  71. 71. TDD - Redpublic MainViewModel(ISearchService searchService,INavigationService navigationService) { _searchService = searchService; _navigationService = navigationService; _tracks = new List<Track>(); Search = new DelegateCommand(OnSearch); ShowDetail= new DelegateCommand<Track>(OnShowDetail); }
  72. 72. TDD - Redpublic MainViewModel(ISearchService searchService,INavigationService navigationService) { _searchService = searchService; _navigationService = navigationService; _tracks = new List<Track>(); Search = new DelegateCommand(OnSearch); ShowDetail= new DelegateCommand<Track>(OnShowDetail); }
  73. 73. TDD - Red
  74. 74. TDD - Greenprivate readonly INavigationService _navigationService;…public DelegateCommand<Track> ShowDetail{get; set; }… public MainViewModel(ISearchService searchService,INavigationService navigationService) { _searchService = searchService; _navigationService = navigationService; _tracks = new List<Track>(); Search = new DelegateCommand(OnSearch); ShowDetail= new DelegateCommand<Track>(OnShowDetail); }
  75. 75. TDD - Greenprivate void OnSearch(){ _tracks= _searchService.SearchUserTrack(SearchedText);}private void OnShowDetail(Track obj){ _navigationService.NavigateTo(new Uri("/View/DetailView.xaml?id="+obj.Id, UriKind.Relative));}
  76. 76. TDD - Green
  77. 77. TDD - Refactorprivate readonly INavigationService _navigationService;public DelegateCommand<Track> ShowDetail{get; private set; }public MainViewModel(ISearchService searchService,INavigationService navigationService) { _searchService = searchService; _navigationService = navigationService; _tracks = new List<Track>(); Search = new DelegateCommand(OnSearch); ShowDetail= new DelegateCommand<Track>(OnShowDetail); }
  78. 78. TDD - Refactorprivate void OnSearch(){ _tracks= _searchService.SearchUserTrack(SearchedText);}private void OnShowDetail(Track track){ _navigationService.NavigateTo(new Uri("/View/DetailView.xaml?id=”+track.Id, UriKind.Relative));}
  79. 79. TDD - Refactor
  80. 80. TDD – Test suite refactor [Test] public void ShowDetail_Should_NavigateToDetailView() { var navigationService = new Mock<INavigationService>(); var searchService = new Mock<ISearchService>(); var viewModel = new MainViewModel(searchService.Object,navigationService.Object); viewModel.ShowDetail.Execute(new Track{Id=345}); navigationService.Verify(p => p.NavigateTo(newUri("/View/DetailView.xaml?id=345", UriKind.Relative)));}
  81. 81. TDD – Test suite refactor [Test] public void ShowDetail_Should_NavigateToDetailView() { var navigationService = new Mock<INavigationService>(); var searchService = new Mock<ISearchService>(); var viewModel = new MainViewModel(searchService.Object,navigationService.Object); viewModel.ShowDetail.Execute(new Track{Id=345}); navigationService.Verify(p => p.NavigateTo(newUri("/View/DetailView.xaml?id=345", UriKind.Relative)));}
  82. 82. TDD – Test suite refactorpublic class MainViewModelFixture{ private Mock<INavigationService> _navigationService; private Mock<ISearchService> _searchService; private MainViewModel _viewModel; [SetUp] public void Setup() { _navigationService = new Mock<INavigationService>(); _searchService = new Mock<ISearchService>(); _viewModel = new MainViewModel(_searchService.Object,_navigationService.Object); }
  83. 83. TDD – Test suite refactorpublic class MainViewModelFixture{ private Mock<INavigationService> _navigationService; private Mock<ISearchService> _searchService; private MainViewModel _viewModel; [SetUp] public void Setup() { _navigationService = new Mock<INavigationService>(); _searchService = new Mock<ISearchService>(); _viewModel = new MainViewModel(_searchService.Object,_navigationService.Object); }
  84. 84. ViewModel and UI• FrameworkElement.DataContext• “A directly embedded object that serves as data context for any bindings within the parent element”• We’ll put here our ViewModel!<phone:PhoneApplicationPage DataContext=“”/>
  85. 85. ViewModel and UIpublic partial class MainView :PhoneApplicationPage{ public MainView() { InitializeComponent(); DataContext = new MainViewModel(new SearchService(),new NavigationService()); }}
  86. 86. MainView<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal"> <TextBox Text="{Binding Text,Mode=TwoWay}” /> <Button Content="Search" Command="{BindingSearch}" HorizontalAlignment="Right" > </StackPanel>
  87. 87. MainView<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal"> <TextBox Text="{Binding Text,Mode=TwoWay}” /> <Button Content="Search" Command="{BindingSearch}" HorizontalAlignment="Right" > </StackPanel>
  88. 88. MainView<ItemsControl Grid.Row="1" ItemsSource="{Binding Tracks}” > <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" Tap="ShowDetail"> <Image Source="{Binding Image}" /> <TextBlock Text="{Binding Title}"/> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate></ItemsControl>
  89. 89. MainView<ItemsControl Grid.Row="1" ItemsSource="{Binding Tracks}” > <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" Tap="ShowDetail"> <Image Source="{Binding Image}" /> <TextBlock Text="{Binding Title}"/> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate></ItemsControl>
  90. 90. INotifyPropertyChanged• INotifyPropertyChanged interface is used to notify clients, typically binding clients, that a property value has changed.
  91. 91. INotifyPropertyChangedpublic class MainViewModel : INotifyPropertyChanged{ public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChanged(String name) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(name)); } }
  92. 92. INotifyPropertyChanged private void ExecuteSearch() { _tracks=_service.SearchUserTrack(SearchedUser); NotifyPropertyChanged(”Tracks"); }}
  93. 93. RecapWhat we have seen:-TDD (unit test, mock)-MVVM (commanding, binding,INotifyPropertyChanged, DataContext)
  94. 94. Node.JS
  95. 95. Be in contactMail: michele@orangecode.itTwitter: @piccoloaiutanteWeb: www.orangecode.itBlog: www.orangecode.it/blogGitHub: https://github.com/piccoloaiutanteCommunity: WEBdeBS
  96. 96. That’s all folks

×