2013-07-24 15 views
5

Oto mój obiekt:Autofixture wygenerowania niestandardowej listy

public class Symbol 
{ 
    private readonly string _identifier; 
    private readonly IList<Quote> _historicalQuotes; 

    public Symbol(string identifier, IEnumerable<Quote> historicalQuotes = null) 
    { 
     _identifier = identifier; 
     _historicalQuotes = historicalQuotes; 
    } 
} 

public class Quote 
{ 
    private readonly DateTime _tradingDate; 
    private readonly decimal _open; 
    private readonly decimal _high; 
    private readonly decimal _low; 
    private readonly decimal _close; 
    private readonly decimal _closeAdjusted; 
    private readonly long _volume; 

    public Quote(
     DateTime tradingDate, 
     decimal open, 
     decimal high, 
     decimal low, 
     decimal close, 
     decimal closeAdjusted, 
     long volume) 
    { 
     _tradingDate = tradingDate; 
     _open = open; 
     _high = high; 
     _low = low; 
     _close = close; 
     _closeAdjusted = closeAdjusted; 
     _volume = volume; 
    } 
} 

muszę instancję symbolu zaludnionych z listą cytując.

W moim teście chcę sprawdzić, czy mogę zwrócić wszystkie oferty, których cena zamknięcia jest niższa lub wyższa od określonej wartości. Oto moja próba:

[Fact] 
public void PriceUnder50() 
{ 
    var msftIdentifier = "MSFT"; 
    var quotes = new List<Quote> 
    { 
     new Quote(DateTime.Parse("01-01-2009"), 0, 0, 0, 49, 0, 0), 
     new Quote(DateTime.Parse("01-02-2009"), 0, 0, 0, 51, 0, 0), 
     new Quote(DateTime.Parse("01-03-2009"), 0, 0, 0, 50, 0, 0), 
     new Quote(DateTime.Parse("01-04-2009"), 0, 0, 0, 10, 0, 0) 
    }; 

    _symbol = new Symbol(msftIdentifier, quotes); 


    var indicator = new UnderPriceIndicator(50); 
    var actual = indicator.Apply(_symbol); 

    Assert.Equal(2, actual.Count); 
    Assert.True(actual.Any(a => a.Date == DateTime.Parse("01-01-2009"))); 
    Assert.True(actual.Any(a => a.Date == DateTime.Parse("01-04-2009"))); 
    Assert.True(actual.Any(a => a.Price == 49)); 
    Assert.True(actual.Any(a => a.Price == 10)); 
} 

OK.

Teraz chcę to zrobić za pomocą Autofixture, jestem prawdziwym początkującym. Czytałem prawie wszystko, co mogłem w Internecie na temat tego narzędzia (blog autora, FAQ codeplex, kod źródłowy github). Rozumiem podstawowe cechy autoformowania, ale teraz chcę używać autofiltru w moim prawdziwym projekcie. Oto, co próbowałem do tej pory.

var msftIdentifier = "MSFT"; 

var quotes = new List<Quote>(); 
var random = new Random(); 
fixture.AddManyTo(
    quotes, 
    () => fixture.Build<Quote>().With(a => a.Close, random.Next(1,49)).Create()); 
quotes.Add(fixture.Build<Quote>().With(a => a.Close, 49).Create()); 

_symbol = new Symbol(msftIdentifier, quotes); 

// I would just assert than 49 is in the list 
Assert.True(_symbol.HistoricalQuotes.Contains(new Quote... blabla 49)); 

Idealnie, wolałbym bezpośrednio stworzyć urządzenie o symbolu, ale nie wiem, jak dostosować moją listę cytatów. I nie jestem pewien, czy mój test jest ogólny, ponieważ w innym teście będę musiał sprawdzić, czy konkretna wartość jest powyżej, zamiast pod, więc powielę "kod urządzenia" i ręcznie dodam wycenę = 51.

Więc moje pytania to:

1 - Czy to jest sposób, w jaki ja mam użyć autofixture?

2 - Czy w moim przykładzie można poprawić sposób używania autofunkcji?

Odpowiedz

10

AutoFixture został pierwotnie zbudowany jako narzędzie do testowego programowania (TDD), a TDD to wszystko o sprzężeniu zwrotnym. W duchu GOOS, powinieneś posłuchać swoich testów. Jeśli testy są trudne do napisania, powinieneś rozważyć swój projekt interfejsu API. AutoFixture ma tendencję do wzmacniania tego rodzaju opinii, i oto co mi to mówi.

Dokonaj porównania łatwiejsze

Po pierwsze, podczas gdy nie związane z AutoFixture klasa Quote tylko nasuwa zostać przekształcony właściwego wartość obiektu, więc będę przesłonić Equals aby ułatwić porównanie przewidywanych i rzeczywistych przykładów:

public override bool Equals(object obj) 
{ 
    var other = obj as Quote; 
    if (other == null) 
     return base.Equals(obj); 

    return _tradingDate == other._tradingDate 
     && _open == other._open 
     && _high == other._high 
     && _low == other._low 
     && _close == other._close 
     && _closeAdjusted == other._closeAdjusted 
     && _volume == other._volume; 
} 

(Upewnij się, aby przesłonić GetHashCode zbyt.)

Kopiowanie i aktualizować

Powyższa próba testu wydaje się sugerować, że jesteśmy brakuje sposób zmieniać pojedyncze pole utrzymując stałą odpoczynek. Biorąc przykład z języków funkcyjnych, możemy przedstawić sposób, aby to zrobić na klasę samą Quote:

public Quote WithClose(decimal newClose) 
{ 
    return new Quote(
     _tradingDate, 
     _open, 
     _high, 
     _low, 
     newClose, 
     _closeAdjusted, 
     _volume); 
} 

Ten rodzaj API wydaje się być bardzo przydatny na temat wartości obiektów, do punktu, gdzie zawsze dodać takie metody do moich obiektów wartości.

Zróbmy to samo z Symbol:

public Symbol WithHistoricalQuotes(IEnumerable<Quote> newHistoricalQuotes) 
{ 
    return new Symbol(_identifier, newHistoricalQuotes); 
} 

To sprawia, że ​​znacznie łatwiej zapytaj AutoFixture do czynienia ze wszystkimi rzeczami nie dbasz o przy czym wyraźnie zaznacza tylko to, co cię obchodzi.

Testowanie z AutoFixture

Oryginalny test można teraz zapisać jako:

[Fact] 
public void PriceUnder50() 
{ 
    var fixture = new Fixture(); 
    var quotes = new[] 
    { 
     fixture.Create<Quote>().WithClose(49), 
     fixture.Create<Quote>().WithClose(51), 
     fixture.Create<Quote>().WithClose(50), 
     fixture.Create<Quote>().WithClose(10), 
    }; 
    var symbol = fixture.Create<Symbol>().WithHistoricalQuotes(quotes); 
    var indicator = fixture.Create<UnderPriceIndicator>().WithLimit(50); 

    var actual = indicator.Apply(symbol); 

    var expected = new[] { quotes[0], quotes[3] }; 
    Assert.Equal(expected, actual); 
} 

Test ten stanowi jedynie, te części testu, że Ci zależy, a AutoFixture dba o wszystko pozostałe wartości, które nie mają żadnego wpływu na przypadek testowy. Dzięki temu test jest bardziej solidny i bardziej czytelny.

+1

+1 @Gui i inne Mark jest zbyt grzeczny, aby go zgłosić, ale jego [Test zaawansowanej jednostki PluralSight] (http://pluralsight.com/training/courses/TableOfContents?CourseName=advanced-unit-testing) kurs jest klinem pełen tego rodzaju dobroci. Warto za to zapłacić, ale jeszcze lepiej mają darmowy okres próbny i nie mogę wymyślić lepszego sposobu na wykorzystanie go (choć oczywiście [Poza testem napędzanym] (http://pluralsight.com/training/Courses/TableOfContents/ outside-in-tdd) jest blisko drugiej) –

+4

@ Mark Seemann Dzięki za komentarz Mark. Zgadzam się, że twój test jest zdecydowanie bardziej czytelny. Zamierzałem zadać jeszcze kilka pytań, ale twoja odpowiedź jest tak kompletna, że ​​odpowiada na każde z moich pytań za każdym razem, gdy ponownie przeczytam twoją odpowiedź. Omg, prawdopodobnie jesteś bogiem testów jednostkowych. – Gui

+0

Co powiesz na dodanie metod rozszerzenia dla 'WithClose',' WithLimit' * zdefiniowanych w projekcie testowym *, zamiast dodawać je jako metody instancji? (teraz w C# 6, nie muszą nawet być w statycznych klasach) –

Powiązane problemy