简体   繁体   English

单元测试和模拟域对象

[英]Unit testing and mocking domain objects

I have a domain class, which looks like this: 我有一个域类,如下所示:

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);
    }
}

I would like to test given methods but I'm stuck. 我想测试给定的方法,但遇到了麻烦。

I have: 我有:

    [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
    }

In the first test I would like also assert that Languages.Add(skill) method was called but I have no idea how to do it. 在第一个测试中,我还想断言Languages.Add(skill)方法已被调用,但我不知道该怎么做。

  1. Is it an elegant way to do it? 这是一种优雅的方式吗? I thought about mocking Add method but I'm not sure if it is a good idea. 我曾考虑过模拟Add方法,但不确定是否是一个好主意。

In the second test I cannot simply mock Employee object as it is not an interface. 在第二个测试中,我不能简单地模拟Employee对象,因为它不是接口。 I thought about exposing Employee but I read I should not do that just for testing purpose. 我曾考虑过要公开Employee,但我读到我不应该仅仅出于测试目的。

  1. How should I mock Languages property without exposing Employee as interface? 如何在不将Employee作为接口暴露的情况下模拟Languages属性? Is it possible? 可能吗? Is it any good practice to do this kind of things? 做这种事情有什么好习惯吗?
  2. Is my general concept for test these methods is okay? 我测试这些方法的一般概念还可以吗? (I'm new in unit testing) (我是单元测试的新手)

You should tests objects as "black box" without relying on implementation details. 您应该在不依赖实现细节的情况下将对象测试为“黑匣子”。 In your case implementation details is that Employee class uses ICollection.Add method. 在您的情况下,实现细节是Employee类使用ICollection.Add方法。

And you definitely don't need to mock at all in your case. 而且您绝对不需要在您的情况下嘲笑。
Mock only dependencies which will make tests slow or very very very very complex to configure for the test. 仅模拟将使测试变慢或非常非常非常非常复杂的依赖关系,以配置测试。

[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);
}

Use public API Employee class provide to setup it for the test (back box). 使用公共API Employee类提供的为测试进行设置(后框)。
For testing DeleteLanguage add dummy languages through public API of the class. 为了测试DeleteLanguage通过类的公共API添加伪语言。

[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.");
}

Did you notice how cumbersome employee.EmployeeLanguages reads, you can rename property to just employee.Languages . 您是否注意到了employee.EmployeeLanguages读取的麻烦,您可以将属性重命名为employee.Languages

For readable assertions I used FluentAssertions library 对于可读的断言,我使用了FluentAssertions库

For domain objects with very simple behavior and no dependencies, mocking isn't strictly needed. 对于行为非常简单且没有依赖关系的域对象,严格不需要模拟。 You can test Add simply with this: 您可以使用以下命令测试“ Add

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

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

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

Testing in this fashion you can achieve perfectly good code coverage and plenty of confidence that the Employee class works as designed. 以这种方式进行测试,您可以获得完全好的代码覆盖率,并且对Employee类按设计方式工作充满了信心。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM