2014-06-05 15 views
5

Also habe ich in einer Organisation gearbeitet, die Entwickler ziemlich unter Druck setzt, Unit-Tests zu schreiben und zu warten. Obwohl ich in der Vergangenheit nicht viel getan habe, mag ich die Idee und glaube, dass jedes seriöse Projekt ein gewisses Maß an Komponententests haben sollte, besonders für Self-Container-Klassenbibliotheken, die sich für solche Tests eignen.Verschleiern Einheitentests unnötigerweise den Code oder gibt es einen besseren Weg?

Allerdings habe ich auch festgestellt, dass der einst sehr einfache, lesbare Code zu einer Ungeheuerlichkeit von Fabriken und Interfaces wurde. Am einfachsten Fall ein Service-Wrapper:

Keine Einheit testet

class ShazamService 
{ 
    private string url; 

    public ShazamService(string url) { this.url = url; } 

    string IdentifySong(byte [] mp3Data) 
    { 
     return HttpHelper.Upload(url, mp3Data).Response; 
    } 
} 

class Program 
{ 
    public static int Main(string [] args) 
    { 
     var svc = new ShazamService("http://www.shazam.com"); 
     Console.Writeline(svc.IdentifySong(args[0].ToByteArray()); 
    } 
} 

Einheit testbare Version

public interface IShazamService 
{ 
    public string IdentifySong(byte [] mp3Data); 
} 

public class ShazamClassFactory 
{ 
    private string url; 

    public ShazamClassFactory(string url) { this.url = url; } 

    public IShazamService GetInstance(bool test) 
    { 
     if (test) 
     { 
     return new ShazamServiceTest(this.url); 
     } 
     else 
     { 
     return new ShazamService(this.url); 
     } 
} 

class ShazamService 
{ 
    private string url; 

    public ShazamService(string url) { this.url = url; } 

    string IdentifySong(byte [] mp3Data) 
    { 
     return HttpHelper.Upload(url, mp3Data).Response; 
    } 
} 

class Program 
{ 
    public static int Main(string [] args) 
    { 
     var factory = new ShazamClassFactory("http://www.shazam.com"); 
     var svc = factory.GetInstance(false); 
     Console.Writeline(svc.IdentifySong(args[0].ToByteArray()); 
    } 
} 

ist nicht nur der Code signifikant länger in dem zweiten ein, aber (für mich) ist es weniger klar - von Main kenne ich nicht einmal den Typ des Rückgabewerts von CreateInstance, wenn ich mir ein Implementierungsdetail ansehen muss, damit ich nicht so einfach F12 durch die Logik gehen kann. Auch, was 1 Datei für den Service gewesen wäre, wird jetzt 4 (Fabrik, Schnittstelle, 2 Implementierungen), mit Header, Dokumentation usw. Schließlich, wenn ich entscheide, dass ich den Konstruktor von string url zu string url, SongGenre genre ändern möchte, muss ich jetzt überprüfen aus, aktualisieren und checken 4 separate Dateien ein und aktualisieren Konstruktoren, Datenmember, Dokumentation usw. für jeden.

Ist diese Methode zur Förderung von Unit-Tests die Norm? Gibt es weniger aufdringliche Optionen? Und, ist es das wert? Meiner Ansicht nach erhöht sich durch die Verkomplizierung des Codes die Entwicklungszeit, und Fehler werden wahrscheinlicher, indem sie sich mit unechten Objekten testen, die den Code, den Sie verwenden, nur sorta-irgendwie testen.

+12

Dies hat nichts mit Unit-Tests zu tun, nicht sicher, wo Sie herkommen mit dieser Fabrik und das ist schrecklich wenn es drin ist. Das ist nur schlechtes Design, es hat nichts mit Unit-Tests zu tun. –

+2

Das Codieren zu Interfaces und nicht zu konkreten Klassen ermöglicht flexibleren Code, unabhängig vom Komponententest. Um die hässliche Fabrik mit einem Bool-Parameter zu vermeiden, möchten Sie vielleicht IoC betrachten, so dass Sie all dies in einer Konfigurationsdatei einstellen können, anstatt Ihren Code zu überladen. – DeanOC

+0

Kurze Antwort: Komponententests sollten nicht dazu führen, dass der Code stärker ausgelutscht wird. Finden Sie heraus, was Ihr Test braucht, um zu verhöhnen, und machen Sie es möglich, das zu übergeben. In diesem Fall, frex, anstatt "HttpHelper.Upload" hart zu codieren, könnten Sie einen "Upload" -Delegaten übergeben. Wenn Sie das nicht ständig tun möchten, können Sie zwei Konstruktoren haben, einen (zum Testen), der den Delegaten übernimmt und einen, der ihn fest codiert. Ich schlage vor, eine andere Frage zu stellen, "wie mache ich das testbar?" –

Antwort

8

Der Code ist unklar, weil er schlecht geschrieben ist.

Die Abhängigkeitsinjektion erfolgt durch Einfügen der gewünschten Klasse in einen Setter oder Konstruktor, nicht durch Festcodieren der verschiedenen Optionen und Verwenden einer GetInstance(bool)-Methode, um Ihre Testaktion zu erhalten.

Stattdessen sollte es so aussehen:

public class ShazamClassFactory 
{ 
    private string url; 
    private IShazamService _shazamService; 

    public ShazamClassFactory(string url) { this.url = url; } 

    public void SetShazamService(IShazamService service) { 
     _shazamService = service; 
    } 

    public string GetSong(){ 
     return _shazamService.IdentifySong(url.ToByteArray()); 
    } 
} 

Jetzt können Sie es wie folgt verwenden können:

var factory = new ShazamClassFactory("http://www.shazam.com"); 
factory.SetShazamService(new ShazamTestService()); 
var song = factory.GetSong(); 
+1

Dies ist eine meist gute Antwort, aber ich verstehe nicht, warum Sie immer noch eine Factory-Klasse haben (oder eine, die mindestens wie eine Factory-Klasse benannt ist), oder warum Sie Setter über Konstruktor Injektion –

+1

@BenAaronson gehen : Ich habe gerade den 'ShazamclassFactory' Namen übernommen; es ist keine tatsächliche Fabrik (keine Fabrikmethoden verfügbar). Ich nahm Setter anstelle von Konstruktor, weil der Konstruktor bereits einen Parameter hatte und ich wollte es klar zu halten (beide sind völlig in Ordnung natürlich, obwohl Konstruktor ist auch meine Präferenz). –

1

Ich denke, was Sie suchen ist ein abstract factory. Indem Sie eine Schnittstelle bereitstellen, die die Factory selbst abstrahiert, können Sie eine Factory passieren, die ein Testobjekt erstellt, oder eine Factory, die reale Objekte erstellt und Ihren Code nicht instrumentieren muss.

2

Das Problem, das ich hier sehe, ist, dass es nicht sofort klar ist, was Sie versuchen zu testen.

Wenn Sie Code schreiben, dass ein ShazamService verwendet dann können Sie entweder eine konkrete Umsetzung oder eine Testimplementierung, je nachdem, ob es sich um ein Gerät zu testen oder nicht passieren.

Die Verwendung einer Fabrik sollte verwendet werden, wenn Sie steuern müssen, wenn ein Objekt erstellt wird, und sollte nicht (IMO) das Standardmuster sein, wenn Abhängigkeiten übergeben werden.

Für Ihre Instanz könnte eine bessere Option sein.

Service Interface

public interface IShazamService 
{ 
    string IdentifySong(byte [] mp3Data); 
} 

Live-Schnittstelle

public class LiveShazamService : IShazamService 
{ 
    private readonly string _url; 

    public LiveShazamService(string url) 
    { 
     _url = url; 
    } 

    public string IdentifySong(byte [] mp3Data) 
    { 
     return HttpHelper.Upload(url, mp3Data).Response; 
    } 
} 

Test-Interface (wahrscheinlich in Ihrem Testprojekt lebt)

public class MockShazamService : IShazamService 
{ 
    private readonly string _testData; 

    public LiveShazamService(string testData) 
    { 
     _testData = testData; 
    } 

    public string IdentifySong(byte [] mp3Data) 
    { 
     return _testData; 
    } 
} 

Testcode

[Test] 
public void ShouldParseTitleOfSong() 
{ 
    // arrange 
    var shazamService = new MockShazamService(
     "<html><title>Bon Jovi - Shock to the Heart</title></html>"); 

    var parser = new ShazamMp3Parser(shazamService); 

    // act 
    // this is just dummy input, 
    // we're not testing input in this specific test 
    var result = parser.Parse(new byte[0]); 

    // assert 
    Assert.AreEqual("Bon Jovi - Shock to the Heart", result.Title); 
} 

Produktionscode

public class ShazamMp3Parser 
{ 
    private readonly IShazamService _shazamService; 

    public ShazamMp3Parser(IShazamService shazamService) 
    { 
     _shazamService = shazamService; 
    } 

    public ShazamParserResult Parse(byte[] mp3Data) 
    { 
     var rawText = _shazamService.IdentifySong(mp3Data); 

     // bla bla bla (up to the viewer to implement properly) 
     var title = rawText.SubString(24, 50); 

     return new ShazamParserResult { Title = title }; 
    } 
} 

Nutzung von Produktionscode

public static int Main(string [] args) 
{ 
    var service = new LiveShazamService("http://www.shazam.com"); 

    var parser = new ShazamMp3Parser(service); 

    var mp3Data = args[0].ToByteArray(); 

    Console.Writeline(parser.Parse(mp3Data).Title); 
} 

In diesem Beispiel zeige ich, wie Code zu testen, die auf dem IShazamService (der ShazamMp3Parser) abhängig ist, auf diese Weise können Sie Unit-Test der Parsing des Titels, ohne eine Internetverbindung herstellen und Live-Daten abrufen zu müssen. Mit dem Mock-Service können Sie Daten und Komponententests simulieren, wie Ihr Parsing-Code funktioniert.

Ich habe die Factory nicht implementiert, da ich nicht das Gefühl habe, dass es in diesem Szenario notwendig ist, aber wenn Sie steuern wollten, wann der Service instanziiert wird, könnten Sie eine Factory-Schnittstelle schreiben, gefolgt von zwei Implementierungen, von denen die eine konstruiert Live-Service und einer, der den Test baut.

Wenn Sie später mutig werden oder es Ihnen übel machen, überall Mock-Klassen zu schreiben, können Sie ein spöttisches Framework (wie moq) verwenden, um Ihr Gerät schneller testen zu lassen.

[Test] 
public void ShouldParseTitleOfSong() 
{ 
    // arrange 
    var mockShazamService = new Mock<IShazamService>(); 

    mockShazamService.Setup(x => x.IdentifySong(It.IsAny<byte[]>())) 
        .Returns("<html><title>Bon Jovi - Shock to the Heart</title></html>"); 

    var parser = new ShazamMp3Parser(mockShazamService.Object); 

    // act 
    var result = parser.Parse(new byte[0]); 

    // assert 
    Assert.AreEqual("Bon Jovi - Shock to the Heart", result.Title); 
} 
Verwandte Themen