Recently I read C# In Depth 4.0 where I met the new Tuple design in C# 7. It is really a cool feature. Beside the syntax sugar, it offers capacities that developers can leverage.
At the time of reading it, I was tasked with writing unit tests in my job. It triggered my memory about Semantic Comparison with Likeness. The main idea of semantic comparison is to compare 2 objects with certain properties. It allows developers to define what equality means. The tuple supports the equality by default. So maybe I should be able to use the tuple to accomplish the same thing as Likeness.
In this post, I will write a simple unit test without Likeness or tuple, then refactors it with Likeness, finally uses Tuple. Let’s explore some code.
public class Product
{
public Guid Id { get; set;}
public string Name { get; set;}
public double Price { get; set;}
public string Description { get; set;}
}
[TestFixture]
public class ProductTests
{
[Test]
public void Test_Are_Products_Same()
{
var expectedProduct = new Product
{
Name = "C#",
Price = 10,
Description = "For the purpose of demoing test"
};
var reality = new Product
{
Name = "C#",
Price = 10,
Description = "For the purpose of demoing test"
};
// Assert that 2 products are the same. Id is ignored
}
}
The task is simple. How are we going to assert the 2 products?
Old Fashion
Very simple. We simply assert property by property.
[TestFixture]
public class ProductTests
{
[Test]
public void Test_Are_Products_Same()
{
var expectedProduct = new Product
{
Name = "C#",
Price = 10,
Description = "For the purpose of demoing test"
};
var reality = new Product
{
Name = "C#",
Price = 10,
Description = "For the purpose of demoing test"
};
Assert.AreEqual(expectedProduct.Name, reality.Name);
Assert.AreEqual(expectedProduct.Price, reality.Price);
Assert.AreEqual(expectedProduct.Description, reality.Description);
}
}
Some might think that we should override the Equals method in the Product class. I do not think it is a good idea. The definition of equality between production and unit test are tremendously different. Be careful before overriding equality.
The product class has 3 properties (except the Id property). So the code still looks readable. Think about the situation where there are 10 properties.
Likeness – Semantic Comparison
There is a blog post explaining it in the detail. In this demo, we can rewrite our simple test.
[TestFixture]
public class ProductTests
{
[Test]
public void Test_Are_Products_Same()
{
var expectedProduct = new Product
{
Name = "C#",
Price = 10,
Description = "For the purpose of demoing test"
}.AsSource()
.OfLikeness<Product>();
var reality = new Product
{
Name = "C#",
Price = 10,
Description = "For the purpose of demoing test"
};
Assert.AreEqual(expectedProduct, reality);
}
}
Likeness is a powerful tool in your testing toolbox. Check it out if you are interested in.
Tuple – Customized
The idea is that we can produce a tuple containing asserted properties and compare them. This allows us to flatten the structure if wished.
[TestFixture]
public class ProductTests
{
[Test]
public void Test_Are_Products_Same()
{
var expectedProduct = (Name = "C#", Price = 10, Description = "For the purpose of demoing test");
var reality = new Product
{
Name = "C#",
Price = 10,
Description = "For the purpose of demoing test"
};
Assert.AreEqual(expectedProduct, (reality.Name, reality.Price, reality.Description));
}
}
It might not look different from the Likeness approach. And I do not say which approach is better. It is just another way of doing things.
Summary
So which approach is better? None of them. Each has their own advantages and disadvantages. They are options in your toolbox. How they are used depends on you, developers. Definitely I will take advantages of the new Tuple in both production and unit test code.
Unit tests are part of the job that developers do while building software. Some developers might not write unit tests. But, IMO, majority does. If you are one of those, how do you treat the unit test code comparing to the production code?
Do you think about maintainability?
Will you refactor the test to make it better? Note: I use the term refactoring from the Refactoring book by Martin Fowler.
Have you used the advantages that the test framework offer?
To be honest, I had not thought about that much. In the beginning of my career, I wrote tests that followed the current structure of the projects. I did not question that much. Over the time, I started to feel the pain so I made changes Unit Test from Pain to Joy which has served me well in that project.
Write Tests
Recently, I have had a chance to work on another project. I was tasked with writing unit tests (and integration tests) to get used to the system and to be able to run tests with multiple credentials, AKA login user.
Here is the test, not a real one of course. But the idea is the same. I want to run the test with different credentials. The username and password must be passed to the parameters. This is very useful when you look at a test report. By parameterizing the report will show values passed to the test.
[TestFixture]
internal class MultiCredentialsTest
{
[Test]
[TestCase("read_user", "P@ssword")]
[TestCase("write_user", "P@ssword")]
public void RunWithDifferentCredentials(string username, string password)
{
// The test body goes here.
}
}
And there will be many of them. It worked as expected. But there are potential problems. Can you guess the problem?
What happens when one of test users is changed either username or password?
What if we want to add more test users into the test suites?
Think about the situation where there are hundreds even thousands of them. It will be a pain. I need a solution to centralize the test data. My process has started.
Make Them Better – Manageable
It was time to look at what NUnit offers. NUnit has supported TestCaseSource. You should check it out first if you have not known it. In the nutshell, it allows developers to centralize test data in a manageable manner. That was exactly what I was looking for.
I created a TestCredentialsSource to produce the same test data. I would prefer the name TestCredentialsFactory, but seems the "source" fits better in the unit test context.
internal class TestCredentialsSource
{
public static IEnumerable<object> ReadWriteUsers = new IEnumerable<object>{
new object[]{"read_user", "P@ssword"},
new object[]{"write_user", "P@ssword"}
}
}
The test was rewritten with version V1. There are 2 versions for comparison.
[TestFixture]
internal class MultiCredentialsTest
{
[Test]
[TestCase("read_user", "P@ssword")]
[TestCase("write_user", "P@ssword")]
public void RunWithDifferentCredentials(string username, string password)
{
// The test body goes here.
}
[Test]
[TestCaseSource(typeof(TestCredentialsSource), "ReadWriteUsers")]
public void RunWithDifferentCredentials_V1(string username, string password)
{
// The test body goes here.
}
}
In the test, I did not have to deal with test values. The test data was encapsulated in the TestCredentialsSource with the ReadWriteUsers static field.
Make Them Even Better – Reuse and Duplication
It was good with known specific set of users. There were certain tests that want to run with a specific user. It should be fairly easy with another property in the TestCredentialsSource
internal class TestCredentialsSource
{
public static IEnumerable<object> ReadWriteUsers = new IEnumerable<object>{
new object[]{"read_user", "P@ssword"},
new object[]{"write_user", "P@ssword"}
}
public static IEnumerable<object> SpecificUser = new IEnumerable<object>{
new object[] {"special_user", "P@ss12345"}
}
}
What if I wanted to test with only "read_user"? What if I wanted to combine the "read_user" with "special_user" for another test? One option was to define them in the TestCredentialsSource. Which was still fine because it was still manageable in a single file. But it was awkward.
Was there any better alternative?
Yes, there was. Let’s encapsulate the data in a class. Welcome to TestCredentials class.
internal class TestCredentials
{
public string Username { get; }
public string Password { get; }
public TestCredentials(string username, string password)
{
Username = username;
Password = password;
}
///<summary>
/// Convert the object into an array of properties object which can be used by the TestDataSource
///</summary>
public object[] ToTestSource() => new object[] { Username, Password };
public static TestCredentials ReadUser = new TestCredentials("read_user", "P@ssword");
public static TestCredentials WriteUser = new TestCredentials("write_user", "P@ssword");
public static TestCredentials SpecialUser = new TestCredentials("special_user", "P@ss12345");
}
The class supplied 3 factory methods to construct the needed credentials. This was the only single place where the data was provided without any duplication.
The TestCredentialsSource became much cleaner
internal class TestCredentialsSource
{
public static IEnumerable<object> ReadWriteUsers = new IEnumerable<object>{
TestCredentials.ReadUser.ToTestSource(),
TestCredentials.WriteUser.ToTestSource()
}
public static IEnumerable<object> SpecificUser = new IEnumerable<object>{
TestCredentials.SpecialUser.ToTestSource()
}
}
Cool! The data has gone from the source definition. But there was still one thing that I did not like much – the setup of "SpecificUser" in the TestCredentialsSource. Having a source for a single value did not sound right to me.
There was a solution – convert the TestCredentials to a source that NUnit can understand. Implement the IEnumerable. TestCaseData is defined by the NUnit framework
internal class TestCredentials : IEnumerable<TestCaseData>
{
public string Username { get; }
public string Password { get; }
public TestCredentials(string username, string password)
{
Username = username;
Password = password;
}
///<summary>
/// Convert the object into an array of properties object which can be used by the TestDataSource
///</summary>
public object[] ToTestSource() => new object[] { Username, Password };
public static TestCredentials ReadUser = new TestCredentials("read_user", "P@ssword");
public static TestCredentials WriteUser = new TestCredentials("write_user", "P@ssword");
public static TestCredentials SpecialUser = new TestCredentials("special_user", "P@ss12345");
public IEnumerator<TestCaseData> GetEnumerator()
{
return new List<TestCaseData>
{
new TestCaseData(Username, Password)
}.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
With the in place, I could write the 2 below tests. There was no limitation of what combinations I could make of test credentials.
[Test]
[TestCaseSource(typeof(TestCredentials), "SpecialUser")]
public void RunWithDifferentCredentials_SpecialUser(string username, string password)
{
// The test body goes here.
}
[Test]
[TestCaseSource(typeof(TestCredentials), "SpecialUser")]
[TestCaseSource(typeof(TestCredentials), "WriteUser")]
public void RunWithDifferentCredentials_CombinedUsers(string username, string password)
{
// The test body goes here.
}
The End
The good result does not come by accident. I could have stopped at any step in the process. By pushing a little bit further, by asking the right questions, the end result was more than I had expected.
If you are writing tests code, should you look at them again and ask questions? Give it a try and see how far it takes you.
Recently I have made an improvement in a project regarding its unit test. The improvement has a huge impact on the system in a good way. It also has a huge impact on my thinking, my philosophy about unit test, again, in a good way.
The Story
I have been working on a huge, complex codebase. It is still written with .NET 4.0, about 6 years old. Part of the system is WCF service employed CQRS style. The code has its existing unit test. And we have added more. The tests have both integration tests and mocked tests.
Integration tests, in our context, means starting with the top-most layer (presentation layer at WCF service) down to the database, and then getting data back from the database using Query Handler or Repository. In short, it is a complex testing style. And to my opinion, it is not good. I would not have done that.
Mocked tests, in our context, means mocking all the dependencies. In short, all the interfaces are mocked. Even for the domain objects, we also create proxy objects and mock their properties, methods. It turns out a big mistake.
First, it is hard to write. To mock dependencies, you have to know all the dependencies. Which means developers have to read the implementation code, figure out how they are interacting with each other, figure out what methods call on which interfaces. Those are not really fun. And they do not bring any real value.
Second, it is fragile. Whenever we add code or refactor a piece of code, the unit test breaks. Because the unit test assumes that a number of certain methods are there and that they are called in a specific order. Which is, in our context, not suitable.
Third, it is damn hard to write tests to verify a bug fix.
Root Cause
How did the hell on earth I end up in that situation? Everything has its own reasons. And I want to figure out mine.
I have bought this excuse for so long. And I was happy with it, unfortunately
Because the codebase is complex. The design was wrong. We do not have time to redesign it.
The fingers were pointing to the other guys, other developers. No, it was not true.
“What role have I played in that mess?“, I asked. Oh, turn out I play a very big role. After reflection, here are some reasons to me not doing well on my that area.
Wrong Mindset
A long time ago, when I started knowing and writing unit tests, I was sold the thought that we have to cut the dependencies, especially the database dependency. And with the raising of mocking, the promising of TDD, I mocked (in the hope of cutting dependencies) as much as I could. It works in many scenarios. However, because I believe it is the right thing, I forget to ask right questions.
Not Ask Right Questions
I just wrote tests without asking right questions.
What am I testing here?
Kind of a stupid question, but very powerful. Depending on types of application, on the architecture, answers are different. Because of my wrong mindset, I focused on how instead of what. Answering that question allows me to analyze further, allows me to actually look at the system in a systematical way, instead of a theoretical way.
Solution
First, I decided to throw away what I thought I know about Unit Test. Here are what I want my Unit Test should be
Easy to write, easy to understand from code.
Resilient to refactoring. Do not have to modify unit tests when using another interface in the implementation code. In short, the tests should be there to guarantee the code correctness at maintenance phase.
Such a simple but powerful question! However, I sometimes forgot to ask. We know that we should write unit test for our code. Have we ever considered to answer that question properly? Take an example, given that we will build a simple web service (WCF) to CRUD an Account into the SQL database. What are we going to test? Each will have a different answer, thus, drive their unit test implementation.
When asking that question, the important is to remove the term Unit. I find it is a trap. When that term presents, my mind is trapped in defining what unit is. Therefore, I forget the purpose of my testing.
From my own opinion, at the abstract level, I will categorize them into 2 categories 1) Functional Test and 2) Architecture Test
Functional Test
That are tests to govern the correctness of the system. For this kind of test, we have to define clearly what is the final state.
To implement a proper test, one must clearly define the Output. Some common outputs are 1) Database, 2) Filesystem
To define a good output, we have to define the proper scope (which comes later in this post)
Architecture Test
For some systems, architecture is important. Let’s say all the call to the Database must go through a repository. Or that the Controller (MVC application) must delegate the work to the next layer (such as Service or Command/Query Handler).
Usually, we use Mock to accomplish the testing goal. Because we do not really care about the actual implementation. We care about the sequence of calls.
What are Dependencies?
Dependencies must be listed out explicitly. At the minimum, there should be a simple diagram like this
And do not go into the detail of those dependencies. Better keep the high-level view.
What is the Scope?
Without a scope, things get messy. A proper, explicit said scope will help to define the Input and Output. I made a mistake at this question so I defined a wrong scope. I, once, defined scope at the Project level. I had a unit test for Command Handler project, which will mock the dependency to the Repository project. Then I had another unit test for Repository project. They, first, looked logical and reasonable. However, with the tested of the time, it proves I was wrong.
Once I realized it (and that is why I write this post), I defined the scope at Command Handler level only, remove the concept of Repository test. Which allows me to define the Input is the Command, and the Output is the changes in the database.
This is a game changer step for me. For years, I have been focusing on the term Unit. The problem is that it is hard to define the unit. Will it be a function, a method? Will it be a class? or Will it be an assembly? Well, I do not know. Better I just choose to forget about them.
So what do I have so far in my toolbox regarding unit test? Here they are
Ask the question: What am I testing?
Explicitly list all dependencies at high-level
Define testing scope
Applicants
Back to the story, the system I have been working with is a complex system, a data-driven system. The data is back by SQL Server. From the architecture point of view, it is WCF service with CQRS architecture. When a command is executed, there are a bunch of things involved, the domain, the domain service, the external services (AD FS, payment service, …), … eventually, the data is saved into SQL Server database.
From the command side:
Q: What am I testing here?
A: Save data correctly in the database
We should not care about what domain involved, what domain service called, … They are internal implementation. And they are changed frequently. We chased the wrong rabbit hole.
From the query side:
Q: What am I testing here?
A: Get data from the database correctly.
We should not care about how data is filtered, how data is combined, … They are internal implementation. And the same reasoning goes as in Command.
In both cases, the test will give an input and verify the output. However, we still have a problem with dependencies. A big change in my mindset is that I no longer see the database as a dependency. Rather it is a part of the internal system. Why? Because it is an essential part. It can be set up locally and in CI environment. Therefore, my definition of dependency is
Dependency is external systems that we do not control. That they are hard to set up. That we do not care about their implementation. Database should NOT be one of them.
How to mock those dependencies of not using a Mocking framework? A good practice is that for every dependency, there should be a Proxy (the Proxy design pattern). The proxy implementation is injected at the runtime with the help of an IoC framework such as Windsor Container. For Unit Test, I create a fake implementation and tweak as I want.
I took me a little while to set up all those things. But it works. It gives a lot of payoffs.
Implementation Detail
[PS: This section has not finished yet. However, this post has started for a while. I think I should publish it and save the implementation detail for another post.]
To implement this kind of test, I need to interact with the database to insert and get the data regardless of the system logic. This separation is very important. To accomplish this, I use Linq To SQL.
Due to the confidential contract, I am going to create a simple demo instead of using a real application. Let’s create a simple User form MVC application.
[Code]
Having a separated assembly allows me to isolate the changes. The Linq DBContext allows me to interact with the database as I need.
All the tests have a pattern
Assumption: Prepare data. This step insert the data into the database for the command to execute
Arrange: Prepare the command to verify.
Act: Invoke the command handler correspondent to the command. Each command has its own Command Handler.
Assert: Verify the result. Use Linq To SQL to get the data from the database and verify our expectations.
Instead of repeating the steps, I create a Strategy pattern
When Mock?
There are still scenarios where the Mocking is a perfect solution. Usually, it is the top layer of the system. Let’s take MVC (WebAPI) as an example. In my opinion, the Controller should be as light-weight as possible. What a controller should do are
Validate input
Prepare a request (can be a Command or a Query)
Dispatch the request to the next layer. If the system employees CQRS, that layer is usually a Command Handler or Query Handler.
Return the result
Which steps should be mocked? The step #3. What are we testing? We test to ensure that the Controller sends correct command/query to the next layer. We test the behavior of Controllers. The mock might be a perfect fit for Architecture Test.
[Code]
What’s Next?
The implementation detail for all the stuff I write here. Now it is time to let this post out so I get something DONE.
Unit Test is an art. There is no silver bullet for what to test, how to test, what tools to use. In my opinion, everything depends on the kind of projects, the maturity of the projects, and the skill of developers in the projects.
The Story
Given this piece of code. It is not a real production code nor production quality code. I made it up to demonstrate in this post.
public class Account
{
public virtual bool IsClosed { get; set; }
public virtual decimal Amount { get; set; }
public virtual void Withdraw(decimal? amount)
{
Amount -= amount.Value;
}
}
public class AccountManager
{
private readonly Account _account;
public AccountManager(Account account)
{
_account = account;
}
public void Withdraw(decimal? amount)
{
if (_account.IsClosed)
return;
if (amount == null)
throw new ArgumentNullException(nameof(amount));
_account.Withdraw(amount);
}
}
This is not a design or best practice post. Let’s focus on the business logic and what/how to test.
There is an Account with 2 properties:
IsClosed (true/false): If closed, cannot withdraw money.
Amount (decimal): The current total amount left in the bank Account. The amount is reduced by the Withdraw method.
I keep the Account class pretty simple.
To manage an account, we have AccountManager. What it does are
If the account is closed, nothing happens.
If not closed, and the amount is null (to demonstrate the point that it is invalid withdrawn amount), throw ArgumentNullException.
If all is valid, the account’s amount is reduced by the withdrawn amount.
How are we going to test that 3 expected behaviors?
The Analysis
I believe at this phase, we have to depend on the mind of the developer. First, we need to recognize what kind of test we should perform.
#1: Look like an interaction test. We want to verify a certain behavior: Nothing happens.
#2: Verify input valid which expects an exception to be thrown.
#3: Look like a state test. We want to verify the account’s state. Why? Because we do not really care how the money is withdrawn. We only care about the amount left.
The Technology
Let bring in the tools we know to test. At this phase, depend on organizations; size and kind of projects. I work on .NET stack. I will use MS Test + RhinoMock (for interaction test).
#1: When the account is closed, nothing happens.
The account class might have many properties (now or later), but I only care about “IsClosed” property.
[TestClass]
public class AccountManagerTest
{
[TestMethod]
public void Withdraw_AccountClosed_DoNothing()
{
// Arrange
var account = MockRepository.GenerateMock<Account>();
account.Expect(x => x.IsClosed)
.Return(true);
account.Expect(x => x.Withdraw(Arg<decimal?>.Is.Anything))
.Repeat.Never();
var sut = new AccountManager(account);
// Act
sut.Withdraw(null);
// Assert
account.VerifyAllExpectations();
}
}
The main point is at the “Arrange” phase. I expect these behaviors
The “IsClosed” must be called
The “Withdraw” must not be called. It means: Do nothing.
I am happy to see that test passed.
#2: Throw ArgumentNullException
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void Withdraw_AccountNotClosedAndAmountNull_ThrowArgumentException()
{
// Arrange
var account = new Account {IsClosed = false};
var sut = new AccountManager(account);
// Act
sut.Withdraw(null);
// Assert
}
I can use either Mock or real object at this test as far as ensuring the IsClosed is false.
#3: If all is valid, account’s amount is reduced by the withdrawn amount
[TestMethod]
public void Withdraw_AllValid_AmountReducedByWithdrawnAmount()
{
// Arrange
var account = new Account
{
IsClosed = false,
Amount = 100
};
var sut = new AccountManager(account);
// Act
sut.Withdraw(30);
// Assert
Assert.AreEqual(70, account.Amount);
}
At this test, I do not care how the system implemented. Given a valid account, and a valid amount to withdraw, my account must have the right amount left.
Be very careful, fellows. If you use mock here, you will end up with interaction test.
As I said in the beginning, unit test is an art. You have to research, learn about it every day. Watch other people do it. Make up your own mind. During my career, I found out that developers have a tendency to jump directly to “The Technologies” phase. “The Analysis” phase is very important.
A while ago, I wrote about refactoring 6K LoC class The main benefit of such a refactoring is to have a maintainable codebase.
Last week, I fixed a small bug. The bug itself is trivial. However, the way I enjoyed the new code and wrote unit test was more fun to share. I called it a beautiful solution. And I loved that work.
The Story
In the Refactoring post, I introduced the School and Student to demonstrate the problems. One day, I got a bug report (speak in the blog language, not my real project language)
When removing a student by student’s id, an exception was thrown if the id is invalid.
The expected behavior is not throwing an exception if id is invalid (or not found). It is just a normal business rule in the application.
To demonstrate the story here is the simplest code
public class School
{
protected internal IList<Student> _students = new List<Student>();
private readonly StudentManager _studentManager;
public School()
{
_studentManager = new StudentManager(this);
}
public virtual void AddStudent(Student student)
{
_studentManager.Add(student);
}
public virtual Student FindStudent(Guid id)
{
throw new ArgumentException($"The student you are looking for does not exist {id}", nameof(id));
}
public virtual void Remove(Student student)
{
_students.Remove(student);
}
}
public class Student
{
public virtual Guid Id { get; protected set; }
}
And the consumer code (a simple console application)
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Welcome to the test");
try
{
var school = new School();
var studentId = Guid.NewGuid();
var student = school.FindStudent(studentId);
school.Remove(student);
}
catch (Exception e)
{
Console.WriteLine(e);
}
Console.WriteLine("Press key to exit.");
Console.Read();
}
}
Once you run the code
The Manager
Remember that School is a ghost class. And the student management is delegated to StudentManager. Any attempt to fix directly in School class is a big risk.
There is a small issue in the consumer code. The client has to do 2 things
Find a student by id
Ask School to remove that student
They are too much for a client to know and make decisions. What the client wants is the ability to remove a student by id without throwing an exception.
public class StudentManager
{
private readonly School _school;
public StudentManager(School school)
{
_school = school;
}
public void Add(Student student)
{
_school._students.Add(student);
}
public bool TryRemove(Guid studentId)
{
var student = _school._students.FirstOrDefault(x => x.Id == studentId);
if (student == null)
return false;
return _school._students.Remove(student);
}
}
The StudentManager will take care of the logic.
The Client will be happy
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Welcome to the test");
try
{
var school = new School();
var studentId = Guid.NewGuid();
var studentManager = new StudentManager(school);
var result = studentManager.TryRemove(studentId);
Console.WriteLine($"Are you removed? {result}");
}
catch (Exception e)
{
Console.WriteLine(e);
}
Console.WriteLine("Press key to exit.");
Console.Read();
}
}
Why? Because it just needs to ask StudentManager to handle the logic (in a single method call) and deal with result in its own way.
The Unit Test
So far I am very happy with the fix without touching the existing giant class (School class). Good!
Good! How am I going to test this implementation?
The system data access layer uses NHibernate which requires all domain properties virtual. That design brings us a big benefit in term of unit testing. We can either mock or substitute objects.
Due to the complexity of the legacy code, I tended to use Mock (which is an interaction test) rather than Substitute (which is a state test).
Ok, what do I mean by Interaction Test and State Test in this example? We will test the StudentManager.TryRemove method.
What To Test?
From the business logic, we will test at least 2 things
A student is removed if the student id is valid.
If a student id is not valid, do not throw an exception.
How To Test?
The #2 logic should be simple to test. This code will ensure that
[TestClass]
public class StudentManagerTests
{
[TestMethod]
public void TryRemove_InvalidStudentId_NotThrowException()
{
// Arrange
var school = new School();
var sut = new StudentManager(school);
// Act
var result = sut.TryRemove(Guid.NewGuid());
// Assert
Assert.IsFalse(result);
}
}
However, #1 is not straight away depending on the mind of the developer. Here are at least 2 ways to think about it
The implementation has to find a student, then remove him if found. Keywords here are the 2 verbs (find and remove). Some might refer to verify those 2 actions invoked. That is the interaction test.
The other might simply think: it does not matter what it does (the implementation) as far as a student is removed. Before the school has 10 students. After invoking the method, it has exactly 9 students, and cannot find that student in the school anymore. That is the state test.
I would go for the state test.
Everything should be easy if all properties are public. But that is not the case. School holds an internal protected student list. Student has a protected Id setter. And we do not want to expose them as public.
The key is to Substitute them. In the Unit Test we create derived classes and override the method we want to hook in or bypass.
Prepare school and student substituted.
internal class StudentSchool : School
{
public StudentSchool(IEnumerable<Student> students)
{
_students = students.ToList();
}
}
internal class IdStudent : Student
{
public IdStudent(Guid id)
{
Id = id;
}
}
They are internal (only visible in the unit test assembly).
Without any change to the production code, I have tests covered
internal class StudentSchool : School
{
public StudentSchool(IEnumerable<Student> students)
{
_students = students.ToList();
}
public int StudentCount => _students.Count;
public bool Exists(Guid studentId)
{
return _students.Any(x => x.Id == studentId);
}
}
internal class IdStudent : Student
{
public IdStudent(Guid id)
{
Id = id;
}
}
[TestClass]
public class StudentManagerTests
{
[TestMethod]
public void TryRemove_InvalidStudentId_NotThrowException()
{
// Arrange
var school = new School();
var sut = new StudentManager(school);
// Act
var result = sut.TryRemove(Guid.NewGuid());
// Assert
Assert.IsFalse(result);
}
[TestMethod]
public void TryRemove_ValidStudentId_CountReduce1AndCannotFindAgain()
{
// Arrange
var batman = new IdStudent(Guid.NewGuid());
var superman = new IdStudent(Guid.NewGuid());
var spiderman = new IdStudent(Guid.NewGuid());
var school = new StudentSchool(new[] {batman, spiderman, superman});
var sut = new StudentManager(school);
// Act
var result = sut.TryRemove(superman.Id);
// Assert
Assert.IsTrue(result);
Assert.AreEqual(2, school.StudentCount);
Assert.IsFalse(school.Exists(superman.Id));
}
}
Oh yeah! Superman is removed 🙂 Who needs Superman btw 😛
There are pain and joy in refactoring + unit testing code. The more you code with the improvement mindset, the more joy you have. It is a journey where you become better and better after each line of code. Keep going my friends.