单元测试和模拟域对象

时间:2018-10-02 18:17:26

标签: c# unit-testing

我有一个域类,如下所示:

public class Employee
{
    public Guid EmployeeId { get; private set; }
    public string Name { get; private set; }
    public string Surname { get; private set; }
    ...
    // other properties

    public ICollection<Language> Languages { get; private set; }
        = new List<Language>();

    public ICollection<Skill> Skills { get; private set; }
        = new List<Skill>();

    public void AddLanguage(Language language)
    {
        if (language == null)
            return;

        Languages.Add(language);
    }

    public void DeleteLanguage(Guid languageId)
    {
        var languageToDelete = Languages
            .SingleOrDefault(x => x.LanguageId == languageId);

        if(languageToDelete == null)
            throw new ArgumentException("Language entry doesn't exist.");

        Languages.Remove(languageToDelete);
    }
}

我想测试给定的方法,但被困住了。

我有:

    [Fact]
    public void AddLanguage_AfterCallWithValidObject_LanguagesCollectionContainsAddedObject()
    {
        var language = new Mock<Language>();
        var employee = new Employee("Name", "Surname", ...);

        employee.AddLanguage(language.Object);

        Assert.Contains(employee.EmployeeLanguages, x => x.Language.Equals(language.Object));
    }

    [Fact]
    public void DeleteLanguage_WhenLanguageWithGivenIdDoesntExist_ThrowArgumentException()
    {
        var languageToDelete = new Language("English");

        var employee = new Mock<Employee>();
        employee.Setup(x => x.Languages).Returns(new List<Languages>
        {
            new Language("Spanish"),
            new Language("German")
        });

        employee.Object.DeleteLanguage(languageToDelete);

        // Asserts here
    }

在第一个测试中,我还要断言调用了 Languages.Add(skill)方法,但是我不知道该怎么做。

  1. 这是一种优雅的方式吗?我曾考虑过模拟 Add 方法,但不确定是否是一个好主意。

在第二个测试中,我不能简单地模拟 Employee 对象,因为它不是接口。 我考虑过公开 Employee ,但我读到我不应该仅仅出于测试目的。

  1. 如何在不暴露 Employee 作为界面的情况下模拟 Languages 属性?可能吗?做这种事情有什么好的习惯吗?
  2. 我测试这些方法的一般概念还可以吗? (我是单元测试的新手)

2 个答案:

答案 0 :(得分:2)

您应该将对象测试为“黑匣子”,而不要依赖于实现细节。在您的情况下,实现细节是other specific and rare factors类使用Employee方法。

而且您绝对不需要嘲笑。
仅模拟依赖项,这将使测试配置缓慢或非常非常复杂。

ICollection.Add

使用提供的公共API [Fact] public void AddLanguage_ShouldSaveGivenLanguage() { var language = new Language(); var employee = new Employee("Name", "Surname"); employee.AddLanguage(language); var expectedLanguages = new[] { language }; employee.EmployeeLanguages.Should().BeEquivalentTo(expectedLanguages); } 类为测试进行设置(后盖)。
为了测试Employee,请通过该类的公共API添加伪语言。

DeleteLanguage

您是否注意到[Fact] public void DeleteLanguage_WhenLanguageExists_Remove() { var language1 = new Language("German"); var language2 = new Language("French"); var languageToDelete = new Language("English"); var employee = new Employee("Name", "Surname"); employee.AddLanguage(language1); employee.AddLanguage(language2); employee.AddLanguage(languageToDelete); employee.DeleteLanguage(languageToDelete); var expectedLanguages = new[] { language1, language2 }; employee.EmployeeLanguages.Should().BeEquivalentTo(expectedLanguages); } [Fact] public void DeleteLanguage_WhenLanguageNotExists_ThrowException() { var language1 = new Language("German"); var language2 = new Language("French"); var notExistedLanguage = new Language("English"); var employee = new Employee("Name", "Surname"); employee.AddLanguage(language1); employee.AddLanguage(language2); Action delete = () => employee.DeleteLanguage(languageToDelete); delete.Should() .Throw<ArgumentException>() .WithMessage("Language entry doesn't exist."); } 读起来很麻烦,所以可以将属性重命名为employee.EmployeeLanguages

对于可读的断言,我使用了FluentAssertions library

答案 1 :(得分:1)

对于行为非常简单且没有依赖关系的域对象,严格不需要模拟。您可以使用以下方法简单地测试Add

//Arrange
var e = new Employee();
var l = new Mock<Language>(); 

//Act
e.AddLanguage(l.Object);

//Assert
Assert.IsTrue(e.Languages.Contains(l.Object));

以这种方式进行测试,您可以实现完美的代码覆盖率,并充分保证Employee类按设计工作。