Pisząc testy jednostkowe często musimy brać skądś testowe wartości, których wykorzystanie będziemy następnie weryfikować. Przy okazji ostatniego posta mieliśmy takie linijki:
1: string userName = "testUserName"; 2: string password = "testPassword";
Łatwo jednak sobie wyobrazić ile takich miejsc może pojawić się w skali całego projektu. Tysiące! I czy będzie się komuś chciało nadawać każdej zmiennej sensowną wartość? Wątpię. Z pewnością bardzo szybko da się zaobserwować takie zjawisko:
1: string userName = "asdawdczcasead"; 2: string password = "dq23rwfasdawdswsadfsd";
Fe.
Czytałem gdzieś kiedyś, że stosowanie wartości losowych w testach jednostkowych jest złe. Poniekąd się z tym zgadzam, jednak jak sobie z tym radzić? Przypisywanie coś_mówiących stringów mija się w tym przypadku z celem. A niekontrolowany rajd po klawiaturze to nic innego jak… wartość losowa! Tyle że jest jeszcze gorsza niż napis losowy wygenerowany przez przemyślany mechanizm działający według zdefiniowanych reguł.
Ja od dłuższego czasu przy testach jednostkowych korzystam z własnej klasy Randomizer, która generuje mi wartości o spełnionych kryteriach (jak napis o określonej długości, losowa data, liczba mieszcząca się w pewnych granicach itd). Dla typów bardziej skomplikowanych, wchodzących w skład domeny, można pokusić się o zastosowanie Object Mother, Test Data Builder czy czegoś w ten deseń, ale dla takich prostych scenariuszy poniższa klasa spełnia swoje zadanie. Enjoy, uwagi jak zwykle mile widziane:
1: public static class Randomizer 2: { 3: private static readonly Random _random = new Random(); 4: 5: #region String 6: 7: private const int A = 'A'; 8: private const int z = 'z'; 9: 10: /// <summary> 11: /// Generates a random string of a random length. 12: /// </summary> 13: public static string String() 14: { 15: return String(_random.Next(200)); 16: } 17: 18: /// <summary> 19: /// Generates a random string of a specified length. 20: /// </summary> 21: /// <param name="length">Length of the result string.</param> 22: public static string String(int length) 23: { 24: StringBuilder result = new StringBuilder(length); 25: 26: for (int i = 0; i < length; i++) 27: { 28: var charToAppend = (char)_random.Next(A, z + 1); 29: if (char.IsLetter(charToAppend) == false) 30: { 31: i--; 32: } 33: else 34: { 35: result.Append(charToAppend); 36: } 37: } 38: 39: return result.ToString(); 40: } 41: 42: #endregion 43: 44: #region Number 45: 46: public static int Number() 47: { 48: return _random.Next(); 49: } 50: 51: public static int Number(int min, int max) 52: { 53: return _random.Next(min, max); 54: } 55: 56: public static int Number(int max) 57: { 58: return _random.Next(max); 59: } 60: 61: public static int[] NumberArray() 62: { 63: return NumberArray(_random.Next(2, 15)); 64: } 65: 66: public static int[] NumberArray(int length) 67: { 68: int[] result = new int[length]; 69: 70: for (int i = 0; i < length; i++) 71: { 72: result[i] = Number(); 73: } 74: 75: return result; 76: } 77: 78: #endregion 79: 80: #region TimeSpan 81: 82: public static TimeSpan TimeSpan() 83: { 84: return new TimeSpan(Number()); 85: } 86: 87: #endregion 88: 89: #region Guid 90: 91: public static Guid Guid() 92: { 93: return System.Guid.NewGuid(); 94: } 95: 96: #endregion 97: 98: #region DateTime 99: 100: public static DateTime DateTime() 101: { 102: return new DateTime( 103: Number(2000, 2010), Number(1, 13), Number(1, 25), Number(0, 24), Number(0, 60), Number(0, 60) 104: ); 105: } 106: 107: #endregion 108: }