Good job.
The main thing is that you have a subject area, namely, what you need to be able to fulfill (the requirements and possibilities of the entity).
In your case, we start by creating a class that implements the interface. While empty:
public class VendingMachine : IVendingMachine { public Money Amount { get; } public Product[] Products { get; set; } public Money InsertCoin(Money amount) { throw new NotImplementedException(); } public Money ReturnMoney() { throw new NotImplementedException(); } public Product Buy(int productNumber) { throw new NotImplementedException(); } public string Manufacturer { get; } }
The first requirement is you can update the product list at any time
The interface does not implement exactly how it will happen, and therefore the main question for TDD is how it will be convenient for a third-party developer to update the assortment.
We write a test for this case:
var machine = new VendingMachine(); machine.AddProduct();
We immediately think about it, but how convenient it is to steer products, because judging by the structure of the product, it sets the quantity \ name and price of the item. I would prefer a simple option:
var beerPrice = new Money() {Euros = 1, Cents = 10 }; machine.AddOrUpdateProduct("Пиво", beerPrice, 3);
Immediately add a method with this signature to the class:
public void AddOrUpdateProduct(string name, Money beerPrice, int count) { throw new NotImplementedException(); }
Now we finish the test so that it checks the case we need:
var machine = new VendingMachine(); var beerPrice = new Money() {Euros = 1, Cents = 10 }; var count = 3; machine.AddOrUpdateProduct("Пиво", beerPrice, count); Assert.AreEqual(machine.Products.Length, 1); Assert.AreEqual(machine.Products[0].Available, count);
Run the test - we get the error:
The TDD.UnitTest1.UpdateVendingMachineProducts validation method threw an exception: System.NotImplementedException: Method or operation is not implemented ..
The difficult part of the work is completed, it remains to implement a simple way that will lift the test. Simple, but acting as if the essence exists in the real world, it is not necessary to fence the magic.
I personally do not like working with arrays, so I’ll hide the list in the implementation, with the result:
public Product[] Products { get { return products.ToArray(); } set { products = value.ToList(); } } private List<Product> products = new List<Product>(); public void AddOrUpdateProduct(string name, Money beerPrice, int count) { products.Add(new Product() {Available = count, Name = name, Price = beerPrice}); }
The test has successfully passed, the first feature is implemented, hurray.
Second - you can insert coins, get coins back and get remainder
The description gives an elementary hint of three tests - put in money, returned, received change. The first two can be realized right now, the third - it is better to postpone, the change can only be from the purchase, but not a word about it. Here the interface is set quite well, we write tests on it:
[TestMethod] public void InsertCoin() { var machine = new VendingMachine(); var inserted = new Money() {Euros = 1}; var returned = machine.InsertCoin(inserted); Assert.AreEqual(machine.Amount, inserted); Assert.AreEqual(returned, inserted); } [TestMethod] public void ReturnMoney() { var machine = new VendingMachine(); var count = 1; machine.InsertCoin(new Money() { Euros = count }); var back = machine.ReturnMoney(); Assert.AreEqual(back.Euros, count); Assert.AreEqual(machine.Amount.Euros, 0); }
We realize:
Money overload, let them add up. While elementary:
public static Money operator +(Money m1, Money m2) { return new Money() { Euros = m1.Euros + m2.Euros, Cents = m1.Cents + m2.Cents }; }
So for the first test:
public Money Amount { get; protected set; } public Money InsertCoin(Money amount) { Amount = Amount + amount; return Amount; }
And for the second:
public Money ReturnMoney() { var amount = Amount; Amount = new Money(); return amount; }
Do not forget about the substantive part - the amount in the machine should change during these operations. Hooray, we have three green test.
Third case - You can buy 1 product at once for inserted coins
If I understand correctly, you can buy only one product at a time. After that, the return will be returned. I did not use the machine guns, if I made a mistake - well, sorry.
The test immediately broke me off - the input interface for the user is digital! We assume that the numbers are from 1. We write the test:
[TestMethod] public void Buy() { var machine = new VendingMachine(); var beerPrice = new Money() { Euros = 1, Cents = 10 }; var count = 3; var name = "Пиво"; machine.InsertCoin(new Money() { Euros = count }); machine.AddOrUpdateProduct(name, beerPrice, count); var product = machine.Buy(1); Assert.AreEqual(machine.Products[0].Available, count - 1); Assert.AreEqual(product.Name, name); Assert.AreEqual(machine.Amount, default(Money)); }
Fuh, outside a simple test raises a bunch of questions. Money is easier to make at least partially working with simple operations:
public static Money operator +(Money m1, Money m2) { return new Money() { Euros = m1.Euros + m2.Euros, Cents = m1.Cents + m2.Cents }; } public static Money operator -(Money m1, Money m2) { return new Money() { Euros = m1.Euros - m2.Euros, Cents = m1.Cents - m2.Cents }; } public static bool operator <(Money m1, Money m2) { if (m1.Euros != m2.Euros) return m1.Euros < m2.Euros; return m1.Cents < m2.Cents; } public static bool operator >(Money m1, Money m2) { return m2 < m1; } public static bool operator ==(Money m1, Money m2) { return m1.Euros == m2.Euros && m1.Cents == m2.Cents; } public static bool operator !=(Money m1, Money m2) { return !(m1 == m2); }
This also does not hurt the product:
public static Product operator +(Product m1, int m2) { return new Product() { Available = m1.Available + m2, Price = m1.Price, Name = m1.Name }; } public static Product operator -(Product m1, int m2) { return new Product() { Available = m1.Available - m2, Price = m1.Price, Name = m1.Name }; }
Then, the purchase looks more or less simple, I really did not understand what product the interface should return - I return the balance in the machine so that it can be displayed for example.
public Product Buy(int productNumber) { if (products.Count < productNumber || productNumber < 1) throw new IndexOutOfRangeException("Товара нет."); var index = productNumber - 1; var product = products[index]; if (product.Available <= 0) throw new IndexOutOfRangeException("Товара нет."); if (Amount < product.Price) throw new Exception("Не хватает денег."); Amount = Amount - product.Price; ReturnMoney(); product = product - 1; products[index] = product; return product; }
So, the third test is now also green. If you think that the code is written easily - I assure you, quick launch of the test makes writing it even easier. All exceptions in the Buy method are written solely due to the test, but even so, I could miss some cases.
I will not describe the last case - in it all the salt of the tests. We need to write a test, add restrictions and catch the pain of the fall of previous tests that worked with the purchase and the curves of the entered data. This is the whole point of TDD - you wrote yourself the logic of the application developer, then implemented some kind of internal and complex crap, and then got the very pain that everyone who uses your development experiences. Your suffering is much cheaper than the suffering of users, and therefore TDD (which is completely PainDD) helps write tests and a stable product.
Run the test more often during development - he will immediately say that you have forgotten. When developing methods - do not forget to check the input data and the data of the surrounding world, and then in the tests there is always a desire to give invalid data and get a result. Buy a product without entering the money, request money without adding them, etc. The more real-life case studies you have, the more specific and useful your tests will be; you shouldn’t invent nonsense for testing.
UPD: a lot of things are missed because they are not needed for tests and because of laziness. Cents do not add up to euros, the goods can only be added, although in reality you need a quantitative addition, the price of the goods cannot be changed. Above that, you need to think that you need to clarify something with a business analyst or domain specialist - there are enough difficulties.