【问题标题】:How do I unit-test that a method correctly assigns a new value to another class如何单元测试一个方法正确地将新值分配给另一个类
【发布时间】:2017-08-05 12:46:04
【问题描述】:

我正在尝试在我的 InsertCashTransaction 类中对方法 Execute() 进行单元测试。我想测试它是否正确地为 User.Balance 分配了一个新值。你可以在这里看到这两个类

InsertCashTransaction 类

    public class InsertCashTransaction : Transaction
{
    private IUser _userI;
    public InsertCashTransaction(User user, DateTime date, decimal amount) : base(user, date, amount)
    {
        User = user;
    }

    public InsertCashTransaction(IUser UserI)
    {
        this._userI = UserI;
    }

    public override string ToString()
    {
        return $"Transaction number: {TransactionId}, Date: {Date}: {Amount} has been inserted onto {User.Username}'s wallet.";
    }

    // Method I am trying to test
    public override void Execute()
    {
        if (Amount > 0)
        {
            User.Balance = User.Balance + Amount;
        }
        else if (Amount <= 0)
        {
            throw new ArgumentException("Not allowed to withdraw from users balance nor insert 0");
        }
    }
}

用户类

public class User : IUser 
{
    private int _userid;
    private string _firstname;
    private string _lastname;
    private string _username;
    private string _email;
    private decimal _balance;

    public int UserID
    {
        get { return _userid; }
        set
        {
            if (value < 1)
            {
                throw new ArgumentException("ID cannot be below one");
            }
            _userid = value;
        }
    }

    public string FirstName
    {
        get { return _firstname; }
        set
        {
            CheckIfNull(value);
            ValidateName(value);
            _firstname = value;
        }
    }

    public string LastName
    {
        get { return _lastname; }
        set
        {
            CheckIfNull(value);
            ValidateName(value);
            _lastname = value;
        }
    }

    public string Username
    {
        get { return _username; }
        set
        {

            CheckIfNull(value);
            foreach (char item in value)
            {
                if (char.IsUpper(item))
                {
                    throw new ArgumentException("Username is not allowed to hold use upper case letter");
                }

                if (char.IsSymbol(item))
                {
                    throw new ArgumentException("Username must not contains symbols");
                } else if (item == '-')
                {
                    throw new ArgumentException("Username must not contain symbols");
                }
            }
            _username = value;
        }
    }

    public string Email
    {
        get { return _email; }
        set
        {
            CheckIfNull(value);
            //Creates two out of the email separated by @
            string[] separation = value.Split('@');
            string localPart = separation[0];
            string domain = separation[1];
            foreach (char item in localPart)
            {
                if (char.IsLetterOrDigit(item) == false)
                {
                    if (item != '.' || item != '-' || item != '_' || item != ',')
                    {
                        continue;
                    }
                    else
                    {
                        throw new ArgumentException("Not a valid email");
                    }
                }
            }
            // Check if domain starts with '.' or '-'
            if (domain.Contains("."))
            {
                if (domain.StartsWith(".") || domain.StartsWith("-") || domain.EndsWith(".") || domain.EndsWith("-"))
                {
                    throw new ArgumentException("domain must not start with .");
                }
            }
            foreach (char item in domain)
            {
                if (char.IsSymbol(item))
                {
                    throw new ArgumentException("Domain must not contain any symbols");
                }
            }
            _email = value;
        }
    }

    public decimal Balance
    {
        get { return _balance; }
        set
        {
            if (value < 0)
            {
                throw new ArgumentException("Balance is below 0");
            }
            _balance = value;
        }
    }

    public override string ToString()
    {
        return $"{FirstName}, {LastName}, {Email}";
    }

    public override bool Equals(object obj)
    {
        if (obj == null)
        {
            return false;
        }
        if (this.GetType() != obj.GetType())
        {
            return false;
        }
        return Equals((User)obj);
    }

    public bool Equals(User obj)
    {
        if (obj == null)
        {
            return false;
        }

        if (ReferenceEquals(this, obj))
        {
            return true;
        }

        if (this.GetHashCode() != obj.GetHashCode())
        {
            return false;
        }
        System.Diagnostics.Debug.Assert(base.GetType() != typeof(object));
        if (!base.Equals(obj))
        {
            return false;
        }
        return UserID.Equals(obj.UserID);
    }

    public override int GetHashCode()
    {
        return UserID.GetHashCode();
    }

    public int CompareTo(User user)
    {
        if (UserID > user.UserID)
        {
            return -1;
        }
        return 1;
    }

    public User(int id, string firstName, string lastName, string username, string email, decimal balance)
    {
        UserID = id;
        FirstName = firstName;
        LastName = lastName;
        Username = username;
        Email = email;
        Balance = balance;
    }

    public User()
    {
    }

    public string CheckIfNull(string element)
    {
        if (string.IsNullOrEmpty(element))
        {
            throw new ArgumentNullException("Something is missing");
        }
        return element;
    }

    protected string ValidateName(string name)
    {
        foreach (char item in name)
        {
            if (char.IsDigit(item))
            {
                throw new ArgumentException("Something is wrong in either firstname or lastname");
            }
        }
        return name;
    }
}

到目前为止,我已尝试创建 User 类的接口,并通过 Nsubsitute 尝试替换测试中的类,您可以在此处看到

用户界面

    public interface IUser
{
    int UserID { get; set; }
    string FirstName { get; set; }
    string LastName { get; set; }
    string Username { get; set; }
    string Email { get; set; }
    decimal Balance { get; set; }
}

InsertCashTransactionTest 类

    [TestFixture]
class InsertCashTransactionTest
{
    [TestCase(0)]
    [TestCase(-1)]
    [TestCase(-10)]
    [TestCase(-50)]
    public void AmountBelowZero_throwException(decimal number)
    {
        IUser user = Substitute.For<IUser>();
        InsertCashTransaction icTransaction = new InsertCashTransaction(user);
        icTransaction.Amount = number;
        Assert.Catch<ArgumentException>(() => icTransaction.Execute());
    }

    // Test that isn't working
    [TestCase(1)]
    [TestCase(10)]
    [TestCase(50)]
    public void AmountAboveZero_InsertToUserBalance(decimal number)
    {
        //Arrange 
        IUser user = Substitute.For<IUser>();
        InsertCashTransaction icTransaction = new InsertCashTransaction(user);
        user.Balance = 0;
        icTransaction.Amount = number;
        decimal actualresult = number;
        // Act
        // Somewhere here it goes wrong
        icTransaction.Execute();
        //Assert
        Assert.AreEqual(actualresult, user.Balance);

    }

我的实际问题 当我尝试运行测试时,我得到一个 Nullreference 异常,我的问题是我不知道我在哪里或做错了什么。问题似乎是每当 icTransaction.Execute() 被调用。我希望你能帮助我找出我做错了什么。

如果有不清楚的地方需要进一步解释,请询问

【问题讨论】:

  • 您初始化了public InsertCashTransaction(IUser UserI) { this._userI = UserI; },但在Execute 方法中,您检索了User.Balance + Amount 值。你确定你发布了InsertCashTransaction类的所有代码还是错误的?
  • 我已经发布了 InsertCashTransaction 类中的所有代码
  • 看起来你的第二个构造函数没有正确初始化基类,所以Usernull。尝试删除该构造函数,并让您的第一个构造函数采用IUser 而不是User(您还需要对基类构造函数进行相同的更改)。然后在单元测试中使用三参数构造函数。

标签: c# unit-testing nsubstitute


【解决方案1】:

从发布的代码中,我认为在这种情况下不需要模拟 User。模拟主要是为了替代由于某些副作用、不确定性、依赖性或速度而难以测试的类。 示例包括发送电子邮件,或需要完整的数据库设置,或模拟变化的网络条件等。

除了解决@Cleriston 提出的问题外,我建议放弃IUser 接口,而是使用真正的User 实例。然后,您可以使用以下内容为真实逻辑编写测试:

[TestCase(1)]
[TestCase(10)]
[TestCase(50)]
public void AmountAboveZero_InsertToUserBalance(decimal number)
{
    //Arrange 
    var user = new User(1, "FirstName", "Surname", "tst", "tst@example.com", 0);
    var icTransaction = new InsertCashTransaction(user, DateTime.Now, number);
    // Act
    icTransaction.Execute();
    //Assert
    Assert.AreEqual(number, user.Balance);
}

【讨论】:

  • 到目前为止,我也一直在这样做,由于我不确定是尝试模拟类还是使用真实的用户实例,因此您的评论已经从我的脑海中消除了很多疑问。
【解决方案2】:

您似乎遇到了范围问题。

  • 您的构造函数需要:用户、日期和金额作为参数。当您仅使用 User 初始化您的类时,其他参数为空。看看吧。

    [TestCase(1)]
    [TestCase(10)]
    [TestCase(50)]
    public void AmountAboveZero_InsertToUserBalance(decimal number)
    {
        //Arrange 
        IUser user = Substitute.For<IUser>();
        InsertCashTransaction icTransaction = new InsertCashTransaction(user, null, number);
        user.Balance = 0;
        decimal actualresult = number;
        // Act
        // Somewhere here it goes wrong
        icTransaction.Execute();
        //Assert
        Assert.AreEqual(actualresult, user.Balance);
    }
    

注意避免使用本机类型名称声明变量。例如:数字、布尔值、整数等

  • 参考父母的用户群(良好的做法和干净的代码):

    if (base.Amount <= 0)
    {
        throw new ArgumentException("Not allowed to withdraw from users balance nor insert 0");
    }
    User.Balance = User.Balance + base.Amount;
    

当您迷失在错误可能出现的地方时。检查控制台的输出,您可以在其中找到错误的文件、行和位置。此外,共享错误输出错误会更容易跟踪您的问题。

【讨论】:

  • 我已经添加了您必须更改的点的代码本身。此外,我还指出了原因,因为我相信理解比获得解决方案更好。
  • 这可能是个白痴问题。但这是否需要我删除行 Iuser user = subtitute.for 并只写出示例用户?
  • 我认为问题不在于您的用户构造函数。根本不是白痴。我只是建议您告知您的经验水平,以便在您的回答中获得更多详细信息。
猜你喜欢
  • 2020-05-28
  • 1970-01-01
  • 2023-04-03
  • 1970-01-01
  • 1970-01-01
  • 2010-11-24
  • 2021-10-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多