可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I am writing test for a class thats manage trees of Tag objects:
public class Tag
{
public virtual int Id { get; set; }
public virtual string Description{ get; set; }
private IList<Tag> children = new List<Tag>();
public virtual IEnumerable<Tag> Children
{
get {return children .ToArray();}
}
public void AddChildTag(Tag child)
{
children.Add(child);
}
public void RemoveChildTag(Tag child)
{
children.Remove(child);
}
}
As you can see the only mode to set the parent property is via the AddChildTag
method and this is exactly what i want, my problem is in unit test: since every test should be atomic, how can i test the RemoveChildTag
method?
Only way i see is a call to the add method and later to the remove, but in this way if Add as some errors, even the test of remove will fail, so atomicity is lost.
How can that be done?
EDIT
I removed parent property from Tag object, since i no more use it
Some test according to solution using NUnit and FluentAssertion
[Test]
public void AddChildTagAddsChildren()
{
//Arrange
Tag parent = new Tag();
Tag child = new Tag();
//Act
parent.AddChildTag(child);
//Assert
parent.Children.Should().Contain(child);
}
[Test]
public void RemoveChildTagRemovesAddedChildren()
{
//Arrange
Tag parent = new Tag();
Tag child = new Tag();
parent.AddChildTag(child);
//Act
parent.RemoveChildTag(child);
//Assert
parent.Children.Should().NotContain(child);
}
[Test]
public void RemoveChildTagThrowsNothingWhenNoChild()
{
//Arrange
Tag parent= new Tag();
Tag child= new Tag();
//Act
Action RemoveChild = () => parent.RemoveChildTag(child);
//Assert
RemoveChild.ShouldNotThrow();
}
回答1:
Your unit tests should reflect actual use cases of your class. How will your consumers use RemoveChildTag
method? Which makes more sense? Which is how you'd use collection of objects?
var parent = new Tag();
// later
parent.RemoveChildTag(child);
… or
var parent = new Tag();
parent.AddChildTag(child);
// later
parent.RemoveChildTag(child);
Your consumers will remove objects they previously added. This is your use case, "Remove removes elements previously added" (note that it also produces excellent test method name).
Add
and Remove
methods are often complementary - you can't test one without the other.
回答2:
There are some ways to test Remove
method:
- Mocking - mock your data structure, and call remove , so you can test calling the right methods
- Inheritance - make
children
protected. Your test class will inherit from Tag
class. Now you can init children
member so we can test the remove
- Use
Add
Method
I think option 3 is the best, it's ok using other methods in your unit testing, if Add
have some error , more then 1 test will fail - you should be able to understand what you need to fix.
Also, each unit test should test some basic behavior, even if you need to do some preparation before.
If those preparation failed, fail the test with the relevant comments
Option 1 - is best when you code is reaching to 3rd party - other server, file system & more. Since you don't want to make those calls in unit test - mock the response.
Option 2 - best I can say , is when you want to test protected / private method, without needing to make all the calls "on the way" that your code does (public method that make many calls that eventually call the method you want to test), since you want to test only a specific logic. It also easy to use this option when your class have some state that you want to test, without needing to write a lot of code to bring your class to this state.
回答3:
You could use an PrivateObject Class to Arrange your object under test
Allows test code to call methods and properties on the code under test
that would be inaccessible because they are not public.
EDIT
then you could get full access to wrapped object by PrivateObject.RealType and PrivateObject.Target Properties
EDIT
Anyway, every system which break the segregation and encapsulation of a class make useless the black box approach of Unit Testing in TDD and should be avoided as a poison :)
回答4:
A common approach to unit testing is the Arrange-Act-Assert paradigm.
I would personally have two tests for the remove method, one which is removing a child which was never added, what should happen? Should an exception be thrown?
This would look like:
[Test]
public void RemoveChildTagThrowsExceptionWhenNoChildren()
{
// Arrange
var tag = new Tag();
var tagToRemove = new Tag();
// Act & Assert
Expect(() => tag.RemoveChildTag(tagToRemove), Throws.ArgumentException);
}
Then I would have another test for what should happen when an added child is removed:
[Test]
public void RemoveChildTagRemovesPreviouslyAddedChild()
{
// Arrange
var tag = new Tag();
var childTag = new Tag();
tag.AddChildTag(childTag);
// Act
tag.RemoveChildTag(childTag);
// Assert
Expect(tag.Children.Contains(childTag), Is.False);
}
It may be of interest to note that quite a lot of .NET Remove
implementations return a bool result which indicates whether any removal was actually performed. See here.