应该在我的测试项目中使用IDisposable接口吗?

huangapple go评论85阅读模式
英文:

Should i use IDisposable interface for my test project?

问题

你正在尝试使用NUnit在.NET中学习单元测试,并且必须了解如何正确处理Dispose。你正在尝试在你的工作类中实现Dispose方法等。你不想为你的模拟对象实现IDisposable接口,因为你知道.NET Framework会为你处理它。

这是你的工作类:

public class ApplicationEvaluator : IDisposable
{
    // 类的代码...
}

这是你编写测试的类。你创建了一个方法,用于创建你经常使用的对象,而不是每次都创建新的对象,然后使用using语句来确保在初始化方法中正确地处理对象的生命周期。

public class ApplicationEvaluatorTests
{
    // 测试类的代码...
}

你的做法是有道理的,它遵循了单元测试的最佳实践,使用了模拟对象来隔离被测试的代码,并确保在测试完成后正确地处理资源。为了改进你的垃圾回收知识,你可以深入学习以下几个方面:

  1. 资源管理和IDisposable接口: 进一步了解IDisposable接口以及在Dispose方法中如何正确释放托管和非托管资源。

  2. 垃圾回收: 学习.NET的垃圾回收机制,包括垃圾回收器的工作原理、垃圾回收的代、内存管理等方面的知识。

  3. 性能优化: 了解如何编写高性能的代码,避免内存泄漏和不必要的资源占用。

  4. 单元测试: 继续学习单元测试的最佳实践,包括如何编写有效的测试用例、使用模拟对象和测试框架等。

通过深入学习这些方面,你可以进一步提高你的.NET开发和垃圾回收知识,从而编写更健壮、高性能的代码。

英文:

I'm trying to learn unit tests using NUnit in .net and I must know to dispose somehow. I'm trying to implement in my worker class like Dispose Method and etc. I didn't want to implement IDisposable for my mock objects here because I know .Net Framework takes care of it for me.

This is de working class

public class ApplicationEvaluator: IDisposable
    {
        private const int minAge = 18;
        private const int autoAcceptedYearsOfExperience = 10;
        private List<string> TechStackList = new() { "C#", "RabbitMQ", "Docker", "Microservice", "VisualStudio"};
        private IIdentityValidator _iIdentityValidator;
        private bool disposedValue;

        public ApplicationEvaluator(IIdentityValidator iIdentityValidator)
        {
            _iIdentityValidator = iIdentityValidator;
        }


        public ApplicatonResult Evaluate(JobApplication form)
        {
            if(form.Applicant == null)
            {
                throw new ArgumentNullException(nameof(form.Applicant));
            }
            
            if(form.Applicant.Age < minAge)
            {
                return ApplicatonResult.AutoReject;
            }

            _iIdentityValidator.ValidationMode = form.Applicant.Age > 50 ? ValidationMode.Detailed : ValidationMode.Qucik;


            var validIdentity = _iIdentityValidator.IsValid(form.Applicant.IdNumber);
            if(!validIdentity)
            {
                return ApplicatonResult.TransferredToHR;
            }
            
            var similarTechStackCount = GetSimilarTechStackCount(form.TechStackList);
            if (similarTechStackCount < 25)
            {
                return ApplicatonResult.AutoReject;
            }


            if (similarTechStackCount > 75 &&
                form.YearsOfExperience >= autoAcceptedYearsOfExperience)
            {
                return ApplicatonResult.AutoAccept;
            }

            if (_iIdentityValidator.CountryDataProvider.CountyData.Country != "TURKEY")
            {
                return ApplicatonResult.TransferredToCTO;
            }



            return ApplicatonResult.AutoAccept;
        }

        private int GetSimilarTechStackCount(List<string> List)
        {
            int count = List
                .Where(x => TechStackList.Contains(x, StringComparer.OrdinalIgnoreCase))
                .Count();

            return (int)((double)(count / TechStackList.Count) * 100);
        }

        public enum ApplicatonResult
        {
            AutoReject,
            AutoAccept,
            TransferredToHR,
            TransferredToLead,
            TransferredToCTO,

        }


        // Default Dispose Pattern
        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // TODO: dispose managed state (managed objects)
                    _iIdentityValidator.Dispose();
                }

                // TODO: free unmanaged resources (unmanaged objects) and override finalizer
                // TODO: set large fields to null
                disposedValue = true;
            }
        }

        ~ApplicationEvaluator()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }
    }

Here is the class where I write my tests. I have created a method for the objects that I use all the time, and instead of newing the objects every time, I call the method. That's why using(//here is the object to delete) use only in initialise methods.

public class ApplicationEvaluatorTests
    {
        private Mock<IIdentityValidator> InitialiseTestMock()
        {
            var mock = new Mock<IIdentityValidator>();
            mock.DefaultValue = DefaultValue.Mock;
            mock.Setup(i => i.CountryDataProvider.CountyData.Country).Returns("TURKEY");
            mock.Setup(i => i.IsValid(It.IsAny<string>())).Returns(true);

            return mock;
            
            
        }

        private ApplicationEvaluator InitialiseTestEvaluator(Mock<IIdentityValidator> mock)
        {
            using (ApplicationEvaluator evaluator = new ApplicationEvaluator(mock.Object))
            {
                return evaluator;
            }
        }

        private JobApplication InitialiseTestJobApplictaion()
        {

            using (JobApplication form = new JobApplication())
            {
                using(form.Applicant = new Applicant())
                {
                    form.Applicant.Age = 18;
                    form.Applicant.IdNumber = "12345678910";
                    form.TechStackList = new List<string>() { "C#", "RabbitMQ", "Docker", "Microservice", "VisualStudio" };
                };
                return form;
            }

        }


        // Yaş 18'den küçükse, AutoReject mi?
        [Test]
        public void Application_WithUnderAge_TransferredToAutoRejected()
        {
            // Arrange
            var mock = InitialiseTestMock();
            var evaluator = InitialiseTestEvaluator(mock);

            var form = InitialiseTestJobApplictaion();
            form.Applicant.Age = 17; // Test Case

            // Act
            var result = evaluator.Evaluate(form);

            // Assert
            // Assert.AreEqual(ApplicatonResult.AutoReject, result); Aşağıdaki ile aynı
            result.Should().Be(ApplicatonResult.AutoReject);
        }
.
.
.
(Test codes goes on)
}

Does what I'm doing make any sense? How can i improve my GC knowlage.

答案1

得分: 0

I would not recommend implementing IDisposable in unit tests. Instead, rely on attributes provided by the testing framework, such as OneTimeSetup, OneTimeTearDown, Setup, and teardown, to control the lifecycle of variables. This way, we reduce complexity in test code and allow the test runner's default behavior (which relies on these attributes) to control the lifecycle.

In the test code you mentioned, it would work even when another test is added. This is because of the 'using' statement, which ensures proper cleanup. So, thumbs up for the code. If I were to add one more test, I would be even lazier to just cleanup the variables in teardown.

英文:

I would not recommend implementing IDisposable in unit tests instead rely on attributes provided by testing framework like OneTimeSetup, OneTimeTearDown, Setup, teardown to control the lifecycle of variables. This way we reduce the complexity in test code and also let the test runner's default behavior(which relies on these attributes) to control the life cycle.
In the test code that you mentioned it would work even when another test is added. Its because of the using which is doing a proper cleanup. So my thumbs up for the code. If I were to add one more test then I would be even more lazy to just cleanup the variables in teardown.

huangapple
  • 本文由 发表于 2023年7月6日 20:03:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/76628629.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定