public class SimpleTestClass { [TestCase(null)] [TestCase("")] [TestCase(" ")] public void SimpleTestCase(string parametr) { Assert.IsTrue(string.IsNullOrWhiteSpace(parametr)); } }
A gdyby tak trzymać kolekcję przypadków testowych w oddzielnym miejscu? Mamy taką możliwość - możemy użyć TestCaseSource. Niestety wszystkie metody muszą być statyczne.
[TestCaseSource(typeof(StringTestCaseSource), "NullAndWhiteSpaceStrings")] public void It_should_return_true(string arg1) { Assert.IsTrue(string.IsNullOrWhiteSpace(arg1)); }
Implementacja metody, która zwraca nam potrzebne dane testowe:
public static class StringTestCaseSource { public static IEnumerable<TestCaseData> NullAndWhiteSpaceStrings { get { yield return new TestCaseData(null); yield return new TestCaseData(""); yield return new TestCaseData(" "); } } }
Inny przypadek użycia - wartości enumów. Chcielibyśmy uruchomić test dla wszystkich przypadków enuma. Możemy podawać nazwy elementów w enumie jak i jego wartości. Dla enuma:
public enum MoneyDirection { Pay, Receive, }
Możemy napisać proste testy:
[TestCaseSource(typeof(EnumTestCaseSource<MoneyDirection>), "Names")] public void It_should_parse_moneyDirection_string(string nameOfEnums) { var objectEnum = Enum.Parse(typeof (MoneyDirection), nameOfEnums); Assert.IsNotNull(objectEnum); } [TestCaseSource(typeof(EnumTestCaseSource<MoneyDirection>), "Values")] public void It_should_parse_moneyDirection_type(MoneyDirection valuesOfEnums) { Assert.IsTrue(Enum.IsDefined(typeof(MoneyDirection), valuesOfEnums)); }A sama implementacja metod jest następująca:
public static IEnumerable<TestCaseData> Names { get { return Enum.GetNames(typeof(T)) .Select(x => new TestCaseData(x)); } } public static IEnumerable<TestCaseData> Values { get { return Enum.GetValues(typeof(T)) .Cast<T>() .ToTestCaseData(); //with extension method } }
Przypuśćmy, że mamy klasę, która zawiera statyczne formaty dat:
public class DateTimeRequiredFormats { public const string Req1 = "yyyyMMdd"; //const is public readonly static string Req2 = "yyyy MM dd"; public static string Req3 = "yyyy MM dd"; public string Req4 = ""; //not used because not static //////////// public string Req5 { get { return "a"; } } public string Req6 { get; set; } public DateTimeRequiredFormats(string req6) { Req6 = req6; } }
Aby sprawdzić działanie statycznych formatów (Req1, Req2, Req3) wystarczyło wywołać:
[TestCaseSource(typeof(FieldTestCaseSource<DateTimeRequiredFormats>), "Static")] public void It_should_parse_dateTimeFormat(object arg1) { var dateTime = DateTime.ParseExact("20131029", arg1.ToString(), null, DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AllowInnerWhite ); Assert.AreEqual(new DateTime(2013,10,29), dateTime); }
A metoda, która zwraca te zmienne ma implementację:
public static class FieldTestCaseSource<T> { public static IEnumerable<TestCaseData> Static { get { var propertyInfos = typeof(T).GetFields() .Where(x=>x.IsStatic) .Select(x=>x.GetValue(null)) ; return propertyInfos.ToTestCaseData(); } } }
A co z metoda? Tutaj już jest troszkę trudniejsza sytuacja. Oprócz klasy z metodami to potrzebny będzie nam obiekt, który będzie mógł uruchomić metodę. Poniżej klasa z metodami do uruchamiania w teście:
public class InformationGetter { public static int GetSuperNumber() { return 77; } public static int GetNotSuperNumber() { return 7; } public string GetCharAt(int indexNumber) { return _specialString[indexNumber] .ToString(CultureInfo.InvariantCulture); } private string _specialString; public InformationGetter(string specialString) { _specialString = specialString; } public void ConsoleWrite() { Console.WriteLine("ConsoleWirte:" + _specialString); } }Poniżej sam test. Zauważ, że podany tej tutaj typ zwracany przez metodę (int).
[TestCaseSource(typeof(MethodTestCaseSource<InformationGetter,int>), "Static")] public void It_should_return_int_bigger_than_0(MethodInvoker<int> staticMethodToInvoke) { Assert.Greater(staticMethodToInvoke.Invoke(), 1); }
public static class MethodTestCaseSource<T, TReturn> { public static IEnumerable<TestCaseData> Static { get { var methodInfos= MethodTestCaseSource<T>.GetStaticMethods() .Where(x => x.ReturnType == typeof (TReturn)); var methodInvokers = MethodInvoker<TReturn>.ToInvoker(methodInfos); return methodInvokers .ToTestCaseData(x=>x.Method.Name); } } public static IEnumerable<TestCaseData> NonStatic { get { var methods = MethodTestCaseSource<T>.GetInstanceMethods() .Where(x => x.ReturnType == typeof(T)); var methodInvokers = MethodInvoker.ToInvoker(methods); return methodInvokers .ToTestCaseData(x => x.Method.Name); } } }Klasa MethodInvoker wywołuje daną metodę. Klasa ma dwie postacie (bez i z użyciem generyków). Jedna i druga metoda ma podobną implementację.
public class MethodInvoker<TReturn> : MethodInvoker { public MethodInvoker(MethodInfo methodInfo):base(methodInfo){} public TReturn Invoke(object obj=null, object[] parameters=null) { return (TReturn)Method.Invoke(obj, parameters); } public static IEnumerable<MethodInvoker<TReturn>> ToInvoker(IEnumerable<MethodInfo> methodInfos) { return methodInfos.Select(x => new MethodInvoker<TReturn>(x)); } }Dzięki MethodInvoker możemy też przesyłać metody, które nie są statyczne. Wystarczy dla metody Invoke podać obiekt:
[TestCaseSource(typeof(MethodTestCaseSource<InformationGetter, string>), "NonStatic")] public void It_should_return_first_char(MethodInvoker<string> nonStaticMethodToInvoke) { InformationGetter getter=new InformationGetter("Abc"); var fistCharString = nonStaticMethodToInvoke.Invoke(getter, new object[] {0}); StringAssert.AreEqualIgnoringCase("a", fistCharString); }
Kod źródłowy projektu jest udostępniony na GitHubie. Coś może dodam do tych kolekcji testcesów np. wygenerowane losowe stringi, dane z plików resx, najważniejsze daty w DateTime, adresy url i mail.
Możesz też zainstalować sobie paczkę nugetową. Wystarczy wpisać:
>Install-Package Nunit.Framework.TestCaseStorage
Brak komentarzy:
Prześlij komentarz