developing application for windows phone 7 in tdd

Post on 10-May-2015

1.395 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

DESCRIPTION

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.

TRANSCRIPT

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: michele@orangecode.itTwitter: @piccoloaiutanteWeb: www.orangecode.itBlog: www.orangecode.it/blogGitHub: https://github.com/piccoloaiutante

Community: WEBdeBS

That’s all folksThat’s all folks

top related