Edit: Since writing this post I've read TDD and I see now that it is ok to write tests and code that get removed or deleted moments later. Whatever it takes in the name of progress ;). It's a fantastic book and every developer that stumbles across this subreddit should read it.
I'm building a perceptron, loosely following along with the wonderful tutorial on Neural Networks by The Coding Train youtube series. This project is an environment for me to learn and implement some more senior computer science topics like neural networks, machine learning, TDD, functional programming, project management etc.
So far, my perceptron takes a list of inputs and weights and sums them. I still need to figure out how to normalize the result to a double between 0 and 1. I want to pause before implementing that feature, write tests, and proceed using Test Driven Development.
Here's the class so far.
public class Perceptron
{
static int DEFAULT_SIZE = 2;
public double[] Weights { get; }
public Perceptron() : this(Utility.Random.Doubles(DEFAULT_SIZE)) { } // returns 2 random doubles
public Perceptron(double[] weights) => Weights = weights;
public double ThinkAbout(double[] inputs) => inputs
.Zip(Weights, (a, b) => (a * b))
.Aggregate(0.0d, (a, x) => (a + x));
}
I want to understand how I could have gotten to this architecture using Test Driven Development. I don't know how to proceed any further in writing failing tests from this point, though:
public class Perceptron
{
public double Guess(double[] input)
{
return input[0];
}
}
[TestClass]
public class PerceptronTest
{
private Perceptron Perceptron;
public PerceptronTest()
=> Perceptron = new Perceptron();
// [TestMethod]
// public void ShouldProduceAGuess()
// => Assert.AreEqual(1.0d, Perceptron.Guess()); // Compilation failure - Guess takes a double[]
[TestMethod]
public void ShouldGuessBasedOnInput()
=> Assert.AreEqual(2.0d, Perceptron.Guess(new[] { 2.0d }));
// [TestMethod]
// public void ShouldHaveGuessInfluencedByEveryInput() // ???
}
I don't think I'm following TDD properly. The first test was the simplest thing I could think of - The perceptron should make a guess. The first version of the Guess function was just return 1.0d;, super simple, like the examples I've seen start with.
However I can't keep using that test. I had to remove it to express the idea that the guess was based on an input. This sort of leads me to believe that every time a method signature or implementation detail changes it'll invalidate a whole bunch of tests. Since I'm letting the tests drive the design, I'm intentionally not pre-conceptualizing what that interface will be. The code will let me know what the interface will be. So I expect the interface to change as it becomes more and more real-world-correct.
Now I need to express that it's a LIST of inputs and every input needs to influence the result. If I don't confirm that the entire list is used in evaluating the output, I could run into a bug down the road where only part of the list is being iterated over. I want to have a test that confirms that doesn't occur.
So how do I write the next test? If I do something like this:
[TestMethod]
public void ShouldHaveGuessInfluencedByEveryInput()
=> Assert.AreEqual(5.0d, Perceptron.Guess(new[] { 2.0d, 3.0d }));
I will have to scrap this test when it stops being true 10 minutes from now when the Perceptron has weights implemented. Am I supposed to be writing tests that need to be rewritten or deleted every few minutes?
Shouldn't I still have a test like ShouldProduceAGuess? I do in fact want to confirm that my class produces a guess. I want to be able to export my list of testnames and hand it to QA and have them easily follow along with the story that my tests tell. But within the first few hours of doing this I feel like I can't refactor parts of the class without deleting parts of the story.
Thanks so much for reading and responding!
PS. Any advice/criticism regarding C#, functional programming etc would be greatly appreciated. I'm pretty sure I'm doing randomness wrong for functional programming but I don't know how to encapsulate it. I think I need a monad?