mercoledì 25 agosto 2010
Se avete seguito i passi del post precedente
e provate a lanciare il test dovrebbe aprirsi una finestra che si chiude quasi istantaneamente.
Proviamo ora a simulare il click ad un pulsante. Per prima cosa aggiungiamo il controllo alla main window ricordandoci
di dargli un nome (nel nostro caso buttonControl).
Lo xaml dovrebbe essere qualcosa di analogo:
<Window x:Class="WPFAndNUnit.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Canvas>
<Button Name="buttonControl" >A button</Button>
</Canvas>
</Window>
Ora possiamo usare il metodo FindName per trovare il nostro button e quindi modifichiamo il test come segue:
[Test]
public void ShowTheMainWindow()
{
var window = new MainWindow();
window.Show();
var button = (Button) window.FindName("buttonControl");
Assert.That(button, Is.Not.Null);
}
Se eseguiamo il test dovrebbe passare altrimenti ricontrollate i passi precedenti.
Per simulare il click al pulsante usiamo il metodo RaiseEvent come segue:
button.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
Se proviamo ad aggiungere un comportamento all'evento click del pulsante verifichiamo che venga eseguito correttamente.
In sinstesi la tecnica consiste in: dare un nome ai controlli con cui vogliamo interagire nel test e usare il metodo RaiseEvent
per simulare le azioni dell'utente.
giovedì 19 agosto 2010
Gli acceptance test servono a verificare che una user story
sia stata realizzata correttamente, quindi sono test definiti insieme al cliente o addirittura scritti dal cliente.
L'applicazione che stiamo scrivendo utilizza wpf, quindi la nostra necessità è quella di poter aprire finestre,
cliccare sui pulsanti, ecc. all'interno di un test automatico.
Esistono approcci alternativi a quello che propongo in questo post, come ad esempio le
TestApi, le quali utilizzano le
automation api mentre per l'input del mouse e della tastiera
utilizzano l'API SendInput.
Le TestApi fanno molto di più di quello che a noi serve e quindi ho cercato un approccio più adatto al nostro caso.
Per prima cosa creiamo un progetto di Test che referenzia l'assembly con la finestra sotto test e scriviamo il seguente codice:
[TestFixture]
public class MainWindowTest
{
[Test]
public void ShowTheMainWindow()
{
var window = new MainWindow();
window.Show();
}
}
Se proviamo ad eseguirlo ottienamo il seguente errore:
System.InvalidOperationException : The calling thread must be STA, because many UI components require this.
Il motivo dell'errore è che il default apartment state è MTA, normalmente lo si imposta come attributo del metodo Main, purtroppo in questo caso il main si trova nel test runner.
Per risolvere il problema dobbiamo aggiungere al progetto l'Application Configuration File (App.config) ed inserire la
seguente configurazione:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="NUnit">
<section name="TestRunner" type="System.Configuration.NameValueSectionHandler"/>
</sectionGroup>
</configSections>
<NUnit>
<TestRunner>
<add key="ApartmentState" value="STA" />
</TestRunner>
</NUnit>
</configuration>
Se proviamo a lanciare il test ora passa senza problemi; come test runner uso quello di Resharper per cui non so se con
altri test runner la soluzione proposta funzioni correttamente.
Vedremo una tecnica per mandare gli eventi ai controlli nel prossimo post.
lunedì 16 agosto 2010
Consiglio la lettura del blog You are not so smart. Ogni post parla di un errore che comunemente la mente umana commette (The Misconception) per poi approfondire l'argomento (The Truth).
Anche la pagina About segue lo stesso formato:
The Misconception: You are a rational, logical being who sees the world as it really is.
The Truth: You are as deluded as the rest of us, but that’s OK, it keeps you sane...
sabato 24 luglio 2010
Il primo linguaggio ad oggetti è stato Smalltalk aveva alcune caratteristiche che quasi nessun linguaggio moderno possiede ancora.
Un'importante differenza è che la classe è un oggetto. Infatti per creare una nuova classe non ho un meccanismo speciale come in C#, ma semplicemento chiamo un metodo di un oggetto.
Esempio supponiamo di voler creare la classe Dog:
Object subclass: #Dog
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'PBE'
In questo caso viene mandato il messaggio subclass (analogo alla chiamata di un metodo) all'oggetto Object. Analogamente se voglio creare un oggetto di tipo Dog manderò il messaggio new all'oggetto classe Dog.
Anche in Ruby le classi sono oggetti. Quindi il linguaggio C# è un linguaggio Class Oriented proprio per questa limitazione.
Il video Ruby for .NET developers spiega bene queste differenze prendendo come linguaggio di riferimento Ruby.
venerdì 23 luglio 2010
Partirò da una citazione dell'articolo A Laboratory For Teaching Object-Oriented Thinking:
One of the distinguishing features of object design is that no object is an island. All objects stand in relationship to others, on whom they rely for services and control. The last dimension we use in characterizing object designs is the collaborators of an object. We name as collaborators objects which will send or be sent messages in the course of satisfying responsibilities. Collaboration is not necessarily a symmetric relation.
La parte iniziale è un concetto fondamentale: gli oggetti vanno considerati in relazione degli oggetti con cui collaborano e non come entità singole.
Purtroppo l'ambiente di sviluppo non aiuta in questo senso perchè visualizzando il sorgente di una classe per volta rendo difficile la visualizzazione degli oggetti con cui collabora.
Cercherò di chiarire questo concetto con una metafora: è come se volessi insegnare la geografia senza una cartina. Ovviamente posso scrivere che l'Italia confina con la Francia, L'Austria, ecc., ma senza una cartina non potrò mai dire di conoscere la geografia.
La domanda a questo punto sorge spontanea: negli oggetti qual è la cartina?
Come nella geografia ne esistono di diversi tipi. Quella che rende meglio l'idea di un oggetto che collabora con gli altri oggetti è il communication diagram introdotto in UML 2. Il communication diagram fa parte degli interaction diagram tra cui è ben più famoso il sequence diagram. Per approfondire l'argomento consiglio la lettura di questo pdf, tratto dal libro di Larman Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and Iterative Development.
Ne riporto un esempio:
L'idea che sta alla base del communication diagram è simile a quella che ebbe originariamente Alan Kay uno dei padri della programmazione ad ogggeti. In sintesi gli oggetti comunicano scambiandosi messaggi, nel caso di c# il messaggio è una chiamata ad un metodo.
Nel diagramma in figura il Controller collabora con il ListinoPrezzi, il Counter e il Display scambiandosi i messaggi GetPrice, IncrementBy, ecc. Come potete notare la collaborazione tra gli oggetti è evidente in questa schematizzazione.
Consiglio vivamente di usare i communication diagram per il design della propria architettura perchè aiuta a ragionare ad oggetti e non in modo procedurale.
giovedì 22 luglio 2010
Leggere sul monitor del PC a lungo mi stanca la vista allora ho deciso di acquistare un e-book reader con tecnologia e-ink. Ho deciso di scartare l'ipotesi IPad perchè ha lo schermo retroillumanto e quindi avrei lo stesso problema di lettura che sul video del PC.
La mia scelta è caduta sul Kindle DX graphite per la dimensione dello schermo poco più piccola di 10 pollici. In questo modo si riesce a leggere agevolmente un PDF senza bisogno di zoom.
Un'altro aspetto che mi ha fatto propendere per il Kindle è stato la connessione 3G gratuita con la quale si può accedere a wikipedia in inglese ed al sito di Amazon su cui si possono acquistare gli e-book.
Lo schermo è perfetto per la lettura, l'ho usato per diverse ore e non ho notato differenze rispetto alla carta stampata. Se il formato lo consente si possono ingrandire i caratteri a piacimento, mentre per i PDF lo zoom avviene per l'intera pagina e poi bisogna scollare.
Con mia sopresa non supporta il formato epub comunque con un software tipo calibre è possibile convertire in diversi formati tra cui anche quello del kindle (AZW). L'ho provato con The Early History of Smalltalk un libro in formato html e il risultato è stato buono.
Per copiare i libri sul lettore ci sono due possibilità: tramite USB oppure inviandolo al proprio indirizzo @kindle, nel secondo caso il servizio non è gratuito per l'Italia e si appoggia alla rete WhisperNet. Si hanno a disposizione circa 4 gb per i propri e-book.
Durante la lettura selezionando una parola viene visualizzato il suo significato con il dizionario Oxford in inglese.
Il modello graphite ha la funzionalità auto rotation, ma l'ho dovuta disabilitare perchè troppo sensibile e a volte si attivava senza che avessi girato lo schermo magari solo inclinandolo leggermente.
Se lo acquistate ricordatevi di prendere la presa europea, perchè all'interno della confezione viene fornito un cavo microusb per caricare la batteria direttamente dal pc e un adattatore americano. Infine non viene fornita nessuna custodia per cui conviene acquistarla sempre sul sito di amazon io ho preso la Belkin Neoprene. Il tutto mi è costato all'incirca 400 euro compreso di tasse di importazione e spese di spedizione.
In sintesi sono soddisfatto dell'acquisto era quello che volevo e a parte i difetti che ho descritto i pregi li superano di gran lunga.
martedì 13 luglio 2010
Per stimare o sviluppare una user story a volte è necessario documentarsi su aspetti tecnici che non padroneggiamo. Nel mondo agile questa pratica viene chiamata spike
Uno degli aspetti che si sottovalutano degli spike è che con il tempo costituiscono una knowledge base molto utile per lo sviluppatore. Un altro aspetto importante è che non vengono fatti esperimenti sul codice di produzione.
Personalmente gli spike li committo in un repository chiamato Spikes cercando di dare dei nomi significativi alle varie solution in modo da poterli ritrovare facilmente in seguito es: HowToCenterTextVerticallyWithWpf.sln. Inoltre cerco di scrivere, se possibile, tutto il codice in un unico metodo per ritrovarlo facilmente. Lo scopo dello spike non è codice object oriented o con un'architettura malleabile, ma semplicemente di rispondere ad una domanda tecnica ben precisa.
Gli svantaggi nello scrivere gli spike nel codice di produzione sono: difficoltà nel ritrovarli in futuro; qualità del codice compromessa in quanto non sapendo esattamente cosa fare si rischia di scrivere molto più codice del necessario.
Un altro utilizzo molto utile dello spike è quello di cercare diverse soluzioni per lo stesso problema in modo da scegliere la più semplice. In questo caso trovo utile decidere prima un timebox fisso (tipo mezza giornata) per non rischiare di perdere troppo tempo. A volte il timebox è meno importante perchè non si conosce alcuna soluzione al problema e quindi è necessario andare avanti finchè non si trova oppure si rinuncia alla feature.
lunedì 14 giugno 2010
Nel mio precedente post ho rivolto la domanda su come testare la classe Alarm senza modificarla. Matteo Baglini ha provato a rispondere alle domande esprimendo qualche perplessità proprio sull'ultima. In effetti rispondere è meno banale di quello che sembra.
Riporto nuovamente la classe Alarm originale:
public class Alarm
{
private const double LowPressureTreshold = 17;
private const double HighPressureTreshold = 21;
Sensor _sensor = new Sensor();
bool _alarmOn = false;
private long _alarmCount = 0;
public void Check()
{
double psiPressureValue = _sensor.PopNextPressurePsiValue();
if (psiPressureValue < LowPressureTreshold || HighPressureTreshold < psiPressureValue)
{
_alarmOn = true;
_alarmCount += 1;
}
}
public bool AlarmOn
{
get { return _alarmOn; }
}
}
Qual è il problema di testare questa classe senza modificarla?
La dipendenza dalla classe Sensor. In quanto viene fatta la new su cui non ho il controllo.
La soluzione che propongo è liberamente ispirata dal libro Working Effectively with Legacy Code di Michael Feathers che consiglio vivamente se avete a che fare con codice legacy.
Creo un nuovo progetto a cui aggiungo il file sorgente che devo testare, in questo caso il file Alarm.cs. La solution avrà una struttura tipo questa:
Occhio che affincè il barbatrucco funzioni il progetto di test deve stare in una cartella di livello superiore al progetto testato. Infatti vedete che dal progetto Alarm.Tests vedo la cartella AlarmApplication. In questo modo il file Alarm.cs appartiene contemporaneamente ai due progetti non è una copia.
Ora otterrò un errore di compilazione perchè in questo progetto la classe Sensor non esiste.
Aggiungo quindi una nuova classe Sensor nel progetto di test la cui implementazione è completamente sotto il mio controllo:
class Sensor
{
public static double NextValue { get; set;}
public double PopNextPressurePsiValue()
{
return NextValue;
}
}
Scrivere il test della classe Alarm ora è banale:
[Test]
public void ShouldSetAlarmOn()
{
Sensor.NextValue = 18;
var alarm = new Alarm();
alarm.Check();
Assert.That(alarm.AlarmOn, Is.True);
}
}
Questa è una tecnica molto utile, da usare con cautela nel caso in cui si debba fare un refactoring a codice legacy non testato e ci si voglia assicurare di non creare regressioni al comportamento attuale.
martedì 8 giugno 2010
Il post di Luka Micro-esercizio di TDD mi fornisce l'occasione di parlare di un errore di design che vedo in molte codebase e che anche io soprattutto all'inizio commettevo.
Partiamo dalla classe Alarm dell'esempio:
public class Alarm
{
private const double LowPressureTreshold = 17;
private const double HighPressureTreshold = 21;
Sensor _sensor = new Sensor();
bool _alarmOn = false;
private long _alarmCount = 0;
public void Check()
{
double psiPressureValue = _sensor.PopNextPressurePsiValue();
if (psiPressureValue < LowPressureTreshold || HighPressureTreshold < psiPressureValue)
{
_alarmOn = true;
_alarmCount += 1;
}
}
public bool AlarmOn
{
get { return _alarmOn; }
}
}
Il problema è la property AlarmOn. Questa property ha senso solo per i test; per usare la classe Alarm dovrò fare altri if sul valore della property. Infine la classe Alarm non modella un comportamento, ma solo uno stato. Mi spiego meglio: cosa deve fare la nostra applicazione se scatta un allarme? Non credo che debba settare una variabile boolean in memoria a true. Magari deve visualizzare su schermo un led rosso e scrivere in un log il momento in cui l'allarme è scattato: deve compiere un comportamento.
Come potremmo modificare il design per renderlo più object oriented? Usando gli oggetti ;-)
Per prima cosa estraiamo le due istruzioni all'interno dell'if in un altra classe:
public class AlarmCounter : IAlarmReport
{
private bool _alarmOn;
private int _alarmCount;
public void Report()
{
_alarmOn = true;
_alarmCount += 1;
}
}
public interface IAlarmReport
{
void Report();
}
Modifichiamo la clase Alarm in modo che usi la nuova interfaccia:
public class Alarm
{
private const double LowPressureTreshold = 17;
private const double HighPressureTreshold = 21;
private readonly IAlarmReport m_alarmReport;
Sensor _sensor = new Sensor();
public Alarm() : this(new AlarmCounter()) {}
public Alarm(IAlarmReport alarmReport)
{
m_alarmReport = alarmReport;
}
public void Check()
{
double psiPressureValue = _sensor.PopNextPressurePsiValue();
if (psiPressureValue < LowPressureTreshold || HighPressureTreshold < psiPressureValue)
{
m_alarmReport.Report();
}
}
}
Alcune considerazioni importanti: noterete il doppio costruttore; quello senza parametri è stato aggiunto in modo che i chiamanti non si accorgano della modifica, mentre il secondo serve per poter testare la classe Alarm.
Abbiamo reso l'architettura molto più malleabile ed abbiamo anche accontentato il nostro amico OCP. Infatti qualunque cosa debba fare la nostra applicazione quando scatta un allarme per la classe Alarm è indifferente e non verrà modificata.
Lo stesso disaccoppiamento si sarebbe potuto ottenere con il sensore che lascio come esercizio al lettore.
Per finire alcune domande per riflettere su questo codice:
- Se volessi eseguire più di un'azione quando scatta l'allarme, ma senza modificare la classe Alarm come potrei fare?
- Quanti test devo scrivere per testare la classe Alarm? Esiste un'altra implementazione che renda necessario scrivere meno test?
- Come potrei modificare l'implementazione per rendermi indipendente dal tipo di sensore?
- Se avessi voluto testare la prima implementazione di Alarm senza modificare la classe Alarm come avrei potuto fare?
giovedì 27 maggio 2010
Affrontare l'integrazione il prima possibile. Una feature è consegnata solo quando gira in produzione. Le feature in code complete non sono consegnate. Su questo punto ho sentito molte rimostranze diciamo che il concetto è di anticipare tutto ciò che si puo' anticipare in modo da non avere sorprese alla fine.
Cercare di capire qual è l'obiettivo da raggiungere e partire da quello, lasciando perdere tutto il resto. Ridurre lo scope il più possibile.
Imparare a dire di no è difficile e costa fatica, ma nel lungo termine dà grandi risultati.
Limitare l'uso di framework il più possibile, soprattutto in vicinanza delle consegne, aumentano i rischi in maniera esponenziale.
Decidere le exit strategy prima di iniziare il progetto per evitare di essere guidati dal panico e dalla paura. Un progetto puo' fallire ed a volte costa meno interromperlo che intestardirsi e terminarlo a tutti i costi.
Un software dura alcuni anni, un mese in più di sviluppo nel ciclo di vita di un software non è la fine del mondo.
Se siete persone emotive prima di parlare uscite dall'ufficio fate due passi e ritornate dopodichè dite la vostra.
E' più facile ottenere il perdono che il permesso.
Altre idee che per voi hanno funzionato?