I'm new to TDD, never used it ever. I understand the basic concepts but I'm now working on a small project which will be the first time I've ever actually used TDD.
The code is pretty self explanatory, it's a simple user data storage class. I've written an interface, a UserData class, and then implemented the interface with SimpleUserStore which uses lists.
I first wrote the tests, and then wrote the code so that they'd fail, and then "filled in" the code so that they would pass. When I realised I needed another method, I wrote the tests, then generated the stubs, then wrote the code so that they would pass.
My question is basically, am I doing this right? Are there things I'm missing out? Big gaping holes that make you go woah, wait a second!?
All responses appreciated.
public interface IUserStore
{
bool AddUser(UserData user);
bool DeleteUser(UserData user);
bool UpdateUser(UserData user);
UserData GetUserById(int id);
UserData GetUserByName(string name);
}
public class UserData
{
public UserData() { }
public virtual int UserId { get; set; }
public virtual string Username { get; set; }
public virtual string PasswordHash { get; set; }
public virtual string PasswordSalt { get; set; }
}
public class SimpleUserStore : IUserStore, IDisposable
{
private List<UserData> userdata;
public SimpleUserStore()
{
userdata = new List<UserData>();
}
public bool AddUser(UserData user)
{
if (user == null) throw new ArgumentNullException("user");
if (userdata.Contains(user)) return false;
userdata.Add(user);
return userdata.Contains(user);
}
public bool DeleteUser(UserData user)
{
if (user == null) throw new ArgumentNullException("user");
return userdata.Remove(user);
}
public bool UpdateUser(UserData user)
{
if (user == null) throw new ArgumentNullException("user");
if (!userdata.Contains(user)) return false;
UserData _user = userdata.FirstOrDefault(x => x.UserId == user.UserId);
_user = user;
return true;
}
public UserData GetUserById(int id)
{
if (id < 0) throw new ArgumentException("id");
return userdata.FirstOrDefault(x => x.UserId == id);
}
public UserData GetUserByName(string name)
{
if (name == null) throw new ArgumentNullException("name");
return userdata.FirstOrDefault(x => x.Username == name);
}
public void Dispose()
{
userdata = null;
}
}
I have another project for tests, and in it I have the following test helper class:
[ExcludeFromCodeCoverage]
public static class TestHelper
{
public static void ExpectException<T>(Action action) where T : Exception
{
try
{
action();
Assert.Fail("Expected exception " + typeof(T).Name);
}
catch (T)
{
// Expected
}
}
}
And the tests:
[ExcludeFromCodeCoverage]
[TestClass]
public class SimpleUserStoreTests
{
private SimpleUserStore simpleUserStore;
private UserData userA, userB;
[TestInitialize]
public void TestInitialize()
{
simpleUserStore = new SimpleUserStore();
userA = new UserData { UserId = 1, Username = "UserA", PasswordHash = "", PasswordSalt = "" };
userB = new UserData { UserId = 2, Username = "UserB", PasswordHash = "", PasswordSalt = "" };
}
[TestCleanup]
public void TestCleanup()
{
userA = null;
userB = null;
simpleUserStore.Dispose();
}
[TestMethod]
public void AddUserTest()
{
Assert.IsTrue(simpleUserStore.AddUser(userA));
}
[TestMethod]
public void AddUserAlreadyExistsTest()
{
simpleUserStore.AddUser(userA);
Assert.IsFalse(simpleUserStore.AddUser(userA));
}
[TestMethod]
public void AddUserNullArgumentTest()
{
TestHelper.ExpectException<ArgumentNullException>(() => simpleUserStore.AddUser(null));
}
[TestMethod]
public void DeleteUserTest()
{
simpleUserStore.AddUser(userA);
Assert.IsTrue(simpleUserStore.DeleteUser(userA));
}
[TestMethod]
public void DeleteUserNullArgumentTest()
{
TestHelper.ExpectException<ArgumentNullException>(() => simpleUserStore.DeleteUser(null));
}
[TestMethod]
public void UpdateUserTest()
{
simpleUserStore.AddUser(userA);
userA.Username = "Testing";
Assert.IsTrue(simpleUserStore.UpdateUser(userA));
}
[TestMethod]
public void UpdateUserNullArgumentTest()
{
TestHelper.ExpectException<ArgumentNullException>(() => simpleUserStore.UpdateUser(null));
}
[TestMethod]
public void UpdateUserNonExistTest()
{
Assert.IsFalse(simpleUserStore.UpdateUser(userA));
}
[TestMethod]
public void GetUserByIdTest()
{
simpleUserStore.AddUser(userA);
Assert.AreEqual(userA.UserId, simpleUserStore.GetUserById(1).UserId);
}
[TestMethod]
public void GetUserByIdNegativeArgumentTest()
{
TestHelper.ExpectException<ArgumentException>(() => simpleUserStore.GetUserById(-20));
}
[TestMethod]
public void GetUserByIdNonExistTest()
{
simpleUserStore.AddUser(userA);
Assert.AreEqual(null, simpleUserStore.GetUserById(12));
}
[TestMethod]
public void GetUserByNameTest()
{
simpleUserStore.AddUser(userA);
Assert.AreEqual(userA.UserId, simpleUserStore.GetUserByName("UserA").UserId);
}
[TestMethod]
public void GetUserByNameNullArgumentTest()
{
TestHelper.ExpectException<ArgumentNullException>(() => simpleUserStore.GetUserByName(null));
}
[TestMethod]
public void GetUserByNameNonExistTest()
{
simpleUserStore.AddUser(userA);
Assert.AreEqual(null, simpleUserStore.GetUserByName("Testing"));
}
}
AddUser
returns true, you should ensure that it actually added the user. Other than that, it sounds like you have the process down. \$\endgroup\$