diff --git a/Docs/Basic Unit Testing.pptx b/Docs/Basic Unit Testing.pptx new file mode 100644 index 0000000..a6ca253 Binary files /dev/null and b/Docs/Basic Unit Testing.pptx differ diff --git a/README.md b/README.md index f59ac78..64dda17 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# refactoring-training -Refactoring Training hands-on example +# unit-test-training +Unit Test Training hands-on example * Clone project in Visual Studio -* Refactor! +* Test! -[![Build Status](https://travis-ci.org/bkraitberg/refactoring-training-2.2.svg?branch=master)](https://travis-ci.org/bkraitberg/refactoring-training-2.2) +[![Build Status](https://travis-ci.org/roddewit/unit-test-training.svg?branch=master)](https://travis-ci.org/roddewit/unit-test-training) diff --git a/Refactoring.sln b/Refactoring.sln index e744380..94e9f0e 100644 --- a/Refactoring.sln +++ b/Refactoring.sln @@ -1,9 +1,9 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 -VisualStudioVersion = 12.0.40629.0 +VisualStudioVersion = 12.0.21005.1 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Refactoring", "Refactoring\Refactoring.csproj", "{2D3F5D0E-4A6B-44C0-8F63-8E95243AC028}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tusc", "Refactoring\Tusc.csproj", "{2D3F5D0E-4A6B-44C0-8F63-8E95243AC028}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTestProject", "UnitTestProject\UnitTestProject.csproj", "{1FAF99A1-CCBF-435D-B599-4DAE1DAD5105}" EndProject diff --git a/Refactoring/Authenticator.cs b/Refactoring/Authenticator.cs index 503dd0a..30e40f6 100644 --- a/Refactoring/Authenticator.cs +++ b/Refactoring/Authenticator.cs @@ -15,7 +15,7 @@ public Authenticator(List users) this.users = users; } - internal User Authenticate(string username, string password) + public User Authenticate(string username, string password) { return FindUserByCredentials(username, password); } @@ -24,7 +24,7 @@ private User FindUserByCredentials(string username, string password) { if (IsValidUsername(username)) { - return users.FirstOrDefault(user => user.Name.Equals(username) && user.Password.Equals(password)); + return users.FirstOrDefault(user => user.Name.Equals(username) && (password == null || user.Password.Equals(password))); } else { diff --git a/Refactoring/Data/Products.json b/Refactoring/Data/Products.json index 66d190f..4088ce5 100644 --- a/Refactoring/Data/Products.json +++ b/Refactoring/Data/Products.json @@ -1,37 +1,50 @@ [ { + "Id": "1", "Name": "Chips", "Price": 1.49, "Quantity": 50 }, { + "Id": "2", "Name": "Cookies", "Price": 1.0, "Quantity": 100 }, { + "Id": "3", "Name": "Gum", "Price": 0.85, "Quantity": 50 }, { + "Id": "4", "Name": "Pop", "Price": 0.75, "Quantity": 75 }, { + "Id": "5", "Name": "Candy", "Price": 0.85, "Quantity": 30 }, { + "Id": "6", "Name": "Chocolate Bars", "Price": 1.25, "Quantity": 25 }, { + "Id": "7", "Name": "Nuts", "Price": 1.0, "Quantity": 1 + }, + { + "Id": "8", + "Name": "Soup", + "Price": 1.25, + "Quantity": 50 } ] \ No newline at end of file diff --git a/Refactoring/LoginManager.cs b/Refactoring/LoginManager.cs index 5fb9733..318f587 100644 --- a/Refactoring/LoginManager.cs +++ b/Refactoring/LoginManager.cs @@ -29,7 +29,6 @@ public static User LogIn(List users) { WriteInvalidLoginMessage(); - // Exit gracefully Console.WriteLine(); Console.WriteLine("Press Enter key to exit"); Console.ReadLine(); diff --git a/Refactoring/Product.cs b/Refactoring/Product.cs index 2e42aa5..388420c 100644 --- a/Refactoring/Product.cs +++ b/Refactoring/Product.cs @@ -10,6 +10,8 @@ namespace Refactoring [Serializable] public class Product { + [JsonProperty("Id")] + public string Id; [JsonProperty("Name")] public string Name; [JsonProperty("Price")] diff --git a/Refactoring/Store.cs b/Refactoring/Store.cs index e68c497..d9a7c78 100644 --- a/Refactoring/Store.cs +++ b/Refactoring/Store.cs @@ -9,6 +9,7 @@ namespace Refactoring public class Store { private readonly User user; + private readonly List products; private readonly DataManager dataManager; public Store(User user, DataManager dataManager) @@ -17,8 +18,10 @@ public Store(User user, DataManager dataManager) this.dataManager = dataManager; } - public void Purchase(Product product, int quantity) + public void Purchase(string productId, int quantity) { + Product product = this.GetProductById(productId); + if (!UserHasFundsForPurchase(product, quantity)) { throw new InsufficientFundsException(); @@ -29,37 +32,38 @@ public void Purchase(Product product, int quantity) throw new OutOfStockException(); } - product.Quantity = product.Quantity - quantity; + product.Quantity = product.Quantity - quantity+1; user.Balance = user.Balance - product.Price * quantity; dataManager.SaveUser(user); dataManager.SaveProduct(product); } - public bool UserHasFundsForPurchase(Product product, int quantity) + private bool UserHasFundsForPurchase(Product product, int quantity) { double totalPurchasePrice = product.Price * quantity; return user.Balance >= totalPurchasePrice; } - public bool StoreHasStockForPurchase(Product product, int quantity) + private bool StoreHasStockForPurchase(Product product, int quantity) { return product.Quantity >= quantity; } - public void WriteProductList() + public string GetProductList() { - // Prompt for user input - Console.WriteLine(); - Console.WriteLine("What would you like to buy?"); + string output = "\n"; + output += "What would you like to buy?\n"; - foreach (var item in dataManager.Products.Select((product, index) => new { index, product })) + foreach (var product in dataManager.Products.Where(p => p.Quantity > 0)) { - string productDisplay = GetFormattedProductText(item.product, item.index + 1); - Console.WriteLine(productDisplay); + string productDisplay = GetFormattedProductText(product); + output += productDisplay + "\n"; } - Console.WriteLine(dataManager.Products.Count + 1 + ": Exit"); + output += "Type quit to exit the application\n"; + + return output; } public int NumberOfProducts() @@ -67,14 +71,19 @@ public int NumberOfProducts() return dataManager.Products.Count; } - public Product GetProductByIndex(int index) + public Product GetProductById(string productId) + { + return dataManager.Products.FirstOrDefault(p => p.Id.Equals(productId)); + } + + public bool ContainsProduct(string productId) { - return dataManager.Products[index]; + return dataManager.Products.Count(p => p.Id.Equals(productId)) > 0; } - private static string GetFormattedProductText(Product product, int productIndex) + private static string GetFormattedProductText(Product product) { - return String.Format("{0}: {1} ({2:C})", productIndex, product.Name, product.Price); + return String.Format("{0}: {1} ({2:C})", product.Id, product.Name, product.Price); } } } diff --git a/Refactoring/Tusc.cs b/Refactoring/Tusc.cs index c51a8ad..423e05e 100644 --- a/Refactoring/Tusc.cs +++ b/Refactoring/Tusc.cs @@ -26,17 +26,17 @@ public void Run() bool done = false; while (!done) { - store.WriteProductList(); + Console.Write(store.GetProductList()); - int productIndex = ReadProductIndex(store.NumberOfProducts()); + string productId = ReadProductId(); - if (productIndex == store.NumberOfProducts() + 1) + if (productId.Equals("quit")) { done = true; } - else + else if (!productId.Equals("")) { - Product product = store.GetProductByIndex(productIndex-1); + Product product = store.GetProductById(productId); WriteProductToPurchaseMessage(product); @@ -46,7 +46,8 @@ public void Run() { if (purchaseQuantity > 0) { - store.Purchase(product, purchaseQuantity); + store.Purchase(productId, purchaseQuantity); + WriteSuccessfulPurchaseMessage(product, purchaseQuantity); } else { @@ -138,28 +139,17 @@ private static string ReadText(string message) return Console.ReadLine(); } - private static int ReadProductIndex(int numProducts) + private string ReadProductId() { - int productIndex; - bool validIntegerEntered = Int32.TryParse(ReadText("Enter a number: "), out productIndex); - - while (!validIntegerEntered || !IsValidProductSelected(numProducts, productIndex)) + string productId = ReadText("Enter a product ID: "); + if (!productId.Equals("quit") && !store.ContainsProduct(productId)) { - Console.WriteLine("Invalid number entered, please enter a valid number"); + Console.WriteLine("Invalid product ID entered, please enter a valid ID"); + productId = ""; } - - return productIndex; - } - - private static bool IsExitProductSelected(List products, int enteredProductIndex) - { - return enteredProductIndex == products.Count + 1; - } - - private static bool IsValidProductSelected(int numProducts, int enteredProductIndex) - { - return enteredProductIndex > 0 || enteredProductIndex <= numProducts; + return productId; } + private static void WriteCurrentBalanceMessage(User loggedInUser) { diff --git a/Refactoring/Refactoring.csproj b/Refactoring/Tusc.csproj similarity index 100% rename from Refactoring/Refactoring.csproj rename to Refactoring/Tusc.csproj diff --git a/UnitTestProject/UnitTests.cs b/UnitTestProject/IntegrationTests.cs similarity index 71% rename from UnitTestProject/UnitTests.cs rename to UnitTestProject/IntegrationTests.cs index 9f71ef9..f526b8a 100644 --- a/UnitTestProject/UnitTests.cs +++ b/UnitTestProject/IntegrationTests.cs @@ -10,7 +10,8 @@ namespace UnitTestProject { [TestFixture] - public class UnitTests + //[Ignore("Disable integration tests")] + public class IntegrationTests { private List users; private List originalUsers; @@ -50,7 +51,7 @@ public void Test_StartingTuscFromMainDoesNotThrowAnException() { Console.SetOut(writer); - using (var reader = new StringReader("Jason\r\nsfa\r\n1\r\n1\r\n8\r\n\r\n")) + using (var reader = new StringReader("Jason\r\nsfa\r\n1\r\n1\r\nquit\r\n\r\n")) { Console.SetIn(reader); @@ -66,7 +67,7 @@ public void Test_TuscDoesNotThrowAnException() { Console.SetOut(writer); - using (var reader = new StringReader("Jason\r\nsfa\r\n1\r\n1\r\n8\r\n\r\n")) + using (var reader = new StringReader("Jason\r\nsfa\r\n1\r\n1\r\nquit\r\n\r\n")) { Console.SetIn(reader); @@ -147,7 +148,7 @@ public void Test_UserCanCancelPurchase() { Console.SetOut(writer); - using (var reader = new StringReader("Jason\r\nsfa\r\n1\r\n0\r\n8\r\n\r\n")) + using (var reader = new StringReader("Jason\r\nsfa\r\n1\r\n0\r\nquit\r\n\r\n")) { Console.SetIn(reader); @@ -176,7 +177,7 @@ public void Test_ErrorOccursWhenBalanceLessThanPrice() { Console.SetOut(writer); - using (var reader = new StringReader("Jason\r\nsfa\r\n1\r\n1\r\n8\r\n\r\n")) + using (var reader = new StringReader("Jason\r\nsfa\r\n1\r\n1\r\nquit\r\n\r\n")) { Console.SetIn(reader); @@ -204,7 +205,7 @@ public void Test_ErrorOccursWhenProductOutOfStock() { Console.SetOut(writer); - using (var reader = new StringReader("Jason\r\nsfa\r\n1\r\n1\r\n8\r\n\r\n")) + using (var reader = new StringReader("Jason\r\nsfa\r\n1\r\n1\r\nquit\r\n\r\n")) { Console.SetIn(reader); @@ -228,7 +229,7 @@ public void Test_ProductListContainsExitItem() { Console.SetOut(writer); - using (var reader = new StringReader("Jason\r\nsfa\r\n1\r\n1\r\n8\r\n\r\n")) + using (var reader = new StringReader("Jason\r\nsfa\r\n1\r\n1\r\nquit\r\n\r\n")) { Console.SetIn(reader); @@ -241,34 +242,90 @@ public void Test_ProductListContainsExitItem() tusc.Run(); } - Assert.IsTrue(writer.ToString().Contains("8: Exit")); + Assert.IsTrue(writer.ToString().Contains("Type quit to exit the application")); } } - //[Test] - //public void Test_UserCanExitByEnteringQuit() - //{ - // using (var writer = new StringWriter()) - // { - // Console.SetOut(writer); + [Test] + public void Test_UserCanPurchaseProductWhenOnlyOneInStock() + { + // Update data file + List tempProducts = DeepCopy>(originalProducts); + tempProducts.Where(u => u.Name == "Chips").Single().Quantity = 1; + + using (var writer = new StringWriter()) + { + Console.SetOut(writer); + + using (var reader = new StringReader("Jason\r\nsfa\r\n1\r\n1\r\nquit\r\n\r\n")) + { + Console.SetIn(reader); + + DataManager dataManager = new DataManager(users, products); + + User loggedInUser = LoginManager.LogIn(users); + Store store = new Store(loggedInUser, dataManager); - // using (var reader = new StringReader("Jason\r\nsfa\r\nquit\r\n\r\n")) - // { - // Console.SetIn(reader); + Tusc tusc = new Tusc(loggedInUser, store); + tusc.Run(); + } + + Assert.IsTrue(writer.ToString().Contains("You bought 1 Chips")); + } + } - // DataManager dataManager = new DataManager(users, products); + [Test] + public void Test_UserCanExitByEnteringQuit() + { + using (var writer = new StringWriter()) + { + Console.SetOut(writer); - // User loggedInUser = LoginManager.LogIn(users); - // Store store = new Store(loggedInUser, dataManager); + using (var reader = new StringReader("Jason\r\nsfa\r\nquit\r\n\r\n")) + { + Console.SetIn(reader); - // Tusc tusc = new Tusc(loggedInUser, store); - // tusc.Run(); - // } + DataManager dataManager = new DataManager(users, products); - // Assert.IsTrue(writer.ToString().Contains("Type quit to exit the application")); - // Assert.IsTrue(writer.ToString().Contains("Press Enter key to exit")); - // } - //} + User loggedInUser = LoginManager.LogIn(users); + Store store = new Store(loggedInUser, dataManager); + + Tusc tusc = new Tusc(loggedInUser, store); + tusc.Run(); + } + + Assert.IsTrue(writer.ToString().Contains("Type quit to exit the application")); + Assert.IsTrue(writer.ToString().Contains("Press Enter key to exit")); + } + } + + [Test] + public void Test_ProductsWithZeroQuantityDoNotAppearInMenu() + { + // Update data file + List tempProducts = DeepCopy>(originalProducts); + tempProducts.Where(u => u.Name == "Chips").Single().Quantity = 0; + + using (var writer = new StringWriter()) + { + Console.SetOut(writer); + + using (var reader = new StringReader("Jason\r\nsfa\r\nquit\r\n\r\n")) + { + Console.SetIn(reader); + + DataManager dataManager = new DataManager(users, tempProducts); + + User loggedInUser = LoginManager.LogIn(users); + Store store = new Store(loggedInUser, dataManager); + + Tusc tusc = new Tusc(loggedInUser, store); + tusc.Run(); + } + + Assert.IsFalse(writer.ToString().Contains(": Chips")); + } + } private static T DeepCopy(T obj) { diff --git a/UnitTestProject/StoreTests.cs b/UnitTestProject/StoreTests.cs new file mode 100644 index 0000000..9f0b866 --- /dev/null +++ b/UnitTestProject/StoreTests.cs @@ -0,0 +1,123 @@ +using Newtonsoft.Json; +using NUnit.Framework; +using Refactoring; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnitTestProject +{ + [TestFixture] + class StoreTests + { + private User createTestUser(string name, string password, double balance) + { + User testUser = new User(); + testUser.Name = name; + testUser.Password = password; + testUser.Balance = balance; + + return testUser; + } + + private Product createTestProduct(string id, string name, double price, int quantity) + { + Product testProduct = new Product(); + testProduct.Id = id; + testProduct.Name = name; + testProduct.Price = price; + testProduct.Quantity = quantity; + + return testProduct; + } + + [Test] + public void Test_PurchaseThrowsNoErrorForValidFunds() + { + //Arrange + const string TEST_PRODUCT_ID = "1"; + + var users = new List(); + users.Add(createTestUser("Test User", "", 99.99)); + + var products = new List(); + products.Add(createTestProduct(TEST_PRODUCT_ID, "Product", 9.99, 10)); + + var dataManager = new DataManager(users, products); + var store = new Store(users[0], dataManager); + + //Act + store.Purchase(TEST_PRODUCT_ID, 10); + + //Assert + Assert.Pass("No assertion really necessary here"); + } + + [Test] + public void Test_PurchaseRemovesProductFromStore() + { + //Arrange + + //Act + + //Assert + //(choose the appropriate statement(s)) + //Assert.AreEqual(1, products[0].Quantity); + //Assert.AreSame(1, products[0].Quantity); + //Assert.IsTrue(products[0].Quantity == 1); + } + + [Test] + public void Test_PurchaseThrowsExceptionWhenBalanceIsTooLow() + { + //Arrange + + //Act + + //Assert + } + + [Test] + public void Test_PurchaseThrowsExceptionWhenBalanceIsTooLowVersion2() + { + //Arrange + + //Act + + //Assert + } + + + // THE BELOW CODE IS REQUIRED TO PREVENT THE TESTS FROM MODIFYING THE USERS/PRODUCTS ON FILE + // This is not a good unit testing pattern - the unit test dependency on the file system should + // actually be broken ... training on how to do this will be coming. + private List originalUsers; + private List originalProducts; + + [SetUp] + public void Test_Initialize() + { + // Load users from data file + originalUsers = JsonConvert.DeserializeObject>(File.ReadAllText(@"Data/Users.json")); + + // Load products from data file + originalProducts = JsonConvert.DeserializeObject>(File.ReadAllText(@"Data/Products.json")); + } + + + [TearDown] + public void Test_Cleanup() + { + // Restore users + string json = JsonConvert.SerializeObject(originalUsers, Formatting.Indented); + File.WriteAllText(@"Data/Users.json", json); + + // Restore products + string json2 = JsonConvert.SerializeObject(originalProducts, Formatting.Indented); + File.WriteAllText(@"Data/Products.json", json2); + } + } +} diff --git a/UnitTestProject/UnitTestProject.csproj b/UnitTestProject/UnitTestProject.csproj index d2245a3..bcf3847 100644 --- a/UnitTestProject/UnitTestProject.csproj +++ b/UnitTestProject/UnitTestProject.csproj @@ -70,13 +70,14 @@ - + + - + {2d3f5d0e-4a6b-44c0-8f63-8e95243ac028} - Refactoring + Tusc