Developing application for Windows Phone 7 in TDD
-
Upload
michele-capra -
Category
Technology
-
view
1.393 -
download
1
description
Transcript of Developing application for Windows Phone 7 in TDD
Sviluppare un'app per WP7 Sviluppare un'app per WP7 in TDD? Si può fare!in TDD? Si può fare!
Who am I?Who am I?• Developer freelance:
– C# Asp.net Mvc, Wpf, Wp7– Python Django– Blog: orangecode.it/blog
• WEBdeBS founder
What we’re going to seeWhat we’re going to see
• TDD• MVVM• Code..• Code…• …some more code!
The show caseThe show case
• Basic application• Download user songs list from
SoundCloud.com• Display the list • View song’s detail
Screen ShotScreen Shot
Let’s do it with code behindLet’s do it with code behind
TrackTrack
public 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; }}
XamlXaml<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>
XamlXaml <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>
XamlXaml
<StackPanel Orientation="Horizontal"> <TextBox x:Name="searchedText" /> <Button Content="Search" Click="Search" /></StackPanel>
XamlXaml
<ItemsControl x:Name="songsList"><ItemsControl.ItemTemplate>
<DataTemplate> <StackPanel Orientation="Horizontal"> <Image Source="{Binding Image}”/>
<TextBlock Text="{Binding Title}"/></StackPanel>
</DataTemplate> </ItemsControl.ItemTemplate></ItemsControl>
Code behind MainViewCode 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)); } }
Code behind MainViewCode 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 };
Code behind MainViewCode behind MainView
client.ExecuteAsync<List<Track>>(request, response => { songsList.ItemsSource = response.Data; }); }
Code behind MainViewCode behind MainView private void ShowDetail(object sender, GestureEventArgs e){ NavigationService.Navigate(
new Uri("/View/DetailView.xaml?id="+
((Track)((StackPanel)sender).DataContext).Id, UriKind.Relative
));
}
Xaml DetailViewXaml 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>
Xaml DetailViewXaml DetailView
<Grid x:Name="ContentPanel”> <StackPanel> <Image x:Name="image” />
<TextBlock x:Name="creationDate” /><TextBlock x:Name="title” /><TextBlock x:Name="description” />
</StackPanel> </Grid>
Code behind DetailViewCode behind DetailView public 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;
}); } }
Code behind DetailViewCode 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 };
Code behind DetailViewCode 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;
}); }
Problem with code behindProblem with code behind
• Code coupled with UI– Xaml + Code Behind -> one class
• Not testable
MVVM approachMVVM approach
• Architectural Pattern• Derived from Presentation Model pattern
(Fowler) • Clear separation between UI and Logic
UI ViewModel
Collections, DelegateCommand, Properties
MVVM approachMVVM approach
• Structure our code:– ViewModel (c#): Logic– View (Xaml): Presentation– No more code behind
• Now the ViewModel is testable
Test Driven DevelopmentTest Driven Development
• As easy as complex
• Life Cycle:– Write test (red)– Write logic to pass the test (green)– Refactor code (refactor)– Again..
Test Driven DevelopmentTest 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
Testing ToolsTesting 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!
TDDTDD
• Download searched user songs list from SoundCloud.com
TDDTDD
• There is a list of track that i’ve to show
TDDTDDnamespace OrangeCode.SoundCloudFixture{ public class MainViewModelFixture { }}
namespace OrangeCode.SoundCloud{ public class MainViewModel { }}
TDD -RedTDD -Red
Write test: [Test]
public void Constructor_Should_Initialize_TrackList() {
MainViewModel viewModel = new MainViewModel(); Assert.IsNotNull(viewModel.Tracks);
}
TDDTDD
You are not allowed to write any production code unless it is to make a failing unit test pass.
Uncle Bob Martin
TDD - RedTDD - Red public class MainViewModel { public IList<Track> Tracks { get ; set ; } }
TDDTDD
TDDTDD
You are not allowed to write any more production code than is sufficient to pass the one failing unit test.
Uncle Bob Martin
TDD - GreenTDD - Green public class MainViewModel { public IList<Track> Tracks { get; set; }
public MainViewModel() { Tracks = new List<Track>(); } }
TDDTDD
TDD –RefactorTDD –Refactor public class MainViewModel{
private IList<Track> _tracks;
public IList<Track> Tracks { get { return _tracks; } }
public MainViewModel() { _tracks = new List<Track>(); } }
TDD - RefactorTDD - Refactor
TDDTDD
• Download searched user songs list from SoundCloud.com
ArchitectureArchitecture
MainViewModel
Ilist<Track>SearchUserTrack(string)
SearchService
Rest Call
ArchitectureArchitecture
MainViewModel
SearchService
Rest Call
public interface ISearchService{ IList<Track> SearchUserTrack(string user);}
public class SearchService : ISearchService{ public IList<Track> SearchUserTrack(string user){ }}
ISearchService
TDDTDD[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"));}
TDDTDD[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"));}
TDD - MockTDD - 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.
TDDTDD[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"));}
TDD- ICommandTDD- 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.
TDD- DelegateCommandTDD- 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>
TDDTDD[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"));}
TDD - RedTDD - RedAdd properties in order to compile
public string SearchedText { get; set; }
public DelegateCommand Search { get; set; }
TDD - RedTDD - Red
TDD - GreenTDD - 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);}
TDD - GreenTDD - Green
TDD - RefactorTDD - 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);}
TDD - RefactorTDD - Refactor
TDDTDD
• Display the list
TDDTDD[Test]public void Search_Should_UpdateTrackList(){ _searchService.Setup(p => p.SearchUserTrack("michelecapra")).Returns(new List<Track>{new Track()});
_viewModel.SearchedText = "michelecapra"; _viewModel.Search.Execute();
Assert.AreEqual(_viewModel.Tracks.Count, 1);}
TDDTDD[Test]public void Search_Should_UpdateTrackList(){ _searchService.Setup(p => p.SearchUserTrack("michelecapra")).Returns(new List<Track>{new Track()});
_viewModel.SearchedText = "michelecapra"; _viewModel.Search.Execute();
Assert.AreEqual(_viewModel.Tracks.Count, 1);}
TDDTDD[Test]public void Search_Should_UpdateTrackList(){ _searchService.Setup(p => p.SearchUserTrack("michelecapra")).Returns(new List<Track>{new Track()});
_viewModel.SearchedText = "michelecapra"; _viewModel.Search.Execute();
Assert.AreEqual(_viewModel.Tracks.Count, 1);}
TDD - RedTDD - 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);}
TDD - RedTDD - Red
TDD - GreenTDD - Greenprivate void OnSearch(){ _tracks= _searchService.SearchUserTrack(SearchedText);}
TDD - GreenTDD - Green
TDDTDD
• View song’s detail
TDDTDD
• We need to introduce the NavigationService
• But how to decouple it from our ViewModel?
TDD – Navigation ServiceTDD – Navigation Servicepublic interface INavigationService{ void NavigateTo(Uri pageUri);}
public class NavigationService : INavigationService{ private static PhoneApplicationFrame _mainFrame;
public event NavigatingCancelEventHandler Navigating;
public void NavigateTo(Uri pageUri) { … }}
THX to
Laurent Bugnion
TDDTDD [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))); }
TDDTDD [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))); }
TDDTDD [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))); }
TDDTDD [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))); }
TDD - RedTDD - Red public 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; }
TDD - RedTDD - Redpublic MainViewModel(ISearchService searchService, INavigationService navigationService) { _searchService = searchService; _navigationService = navigationService; _tracks = new List<Track>(); Search = new DelegateCommand(OnSearch); ShowDetail= new DelegateCommand<Track>(OnShowDetail); }
TDD - RedTDD - Redpublic MainViewModel(ISearchService searchService, INavigationService navigationService) { _searchService = searchService; _navigationService = navigationService; _tracks = new List<Track>(); Search = new DelegateCommand(OnSearch); ShowDetail= new DelegateCommand<Track>(OnShowDetail); }
TDD - RedTDD - Red
TDD - GreenTDD - 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); }
TDD - GreenTDD - Greenprivate void OnSearch(){ _tracks= _searchService.SearchUserTrack(SearchedText);}
private void OnShowDetail(Track obj){ _navigationService.NavigateTo(new Uri("/View/DetailView.xaml?id="+obj.Id, UriKind.Relative));}
TDD - GreenTDD - Green
TDD - RefactorTDD - 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); }
TDD - RefactorTDD - Refactorprivate void OnSearch(){ _tracks= _searchService.SearchUserTrack(SearchedText);} private void OnShowDetail(Track track){ _navigationService.NavigateTo(new Uri("/View/DetailView.xaml?id=”+track.Id, UriKind.Relative));}
TDD - RefactorTDD - Refactor
TDD – Test suite refactorTDD – 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(new Uri("/View/DetailView.xaml?id=345", UriKind.Relative))); }
TDD – Test suite refactorTDD – 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(new Uri("/View/DetailView.xaml?id=345", UriKind.Relative))); }
public 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); }
TDD – Test suite refactorTDD – Test suite refactor
public 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); }
TDD – Test suite refactorTDD – Test suite refactor
ViewModel and UIViewModel 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=“”/>
ViewModel and UIViewModel and UIpublic partial class MainView :PhoneApplicationPage{ public MainView() { InitializeComponent(); DataContext = new MainViewModel(new SearchService(), new NavigationService()); }}
MainViewMainView<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="{Binding Search}"
HorizontalAlignment="Right" ></StackPanel>
MainViewMainView<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="{Binding Search}"
HorizontalAlignment="Right" ></StackPanel>
MainViewMainView<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>
MainViewMainView<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>
INotifyPropertyChangedINotifyPropertyChanged
• INotifyPropertyChanged interface is used to notify clients, typically binding clients, that a property value has changed.
INotifyPropertyChangedINotifyPropertyChanged public class MainViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChanged(String name) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(name)); } }
INotifyPropertyChangedINotifyPropertyChanged private void ExecuteSearch() { _tracks=_service.SearchUserTrack(SearchedUser); NotifyPropertyChanged(”Tracks"); }}
RecapRecap
What we have seen:-TDD (unit test, mock)-MVVM (commanding, binding, INotifyPropertyChanged, DataContext)
Node.JSNode.JS
Be in contactBe in contact
Mail: [email protected]: @piccoloaiutanteWeb: www.orangecode.itBlog: www.orangecode.it/blogGitHub: https://github.com/piccoloaiutante
Community: WEBdeBS
That’s all folksThat’s all folks