C# 如何在MSTest中测试具有控制台I/O的类

C# 如何在MSTest中测试具有控制台I/O的类,c#,tdd,mstest,C#,Tdd,Mstest,如何在MSTest中向类的方法输入数据。我有以下代码,但在进行单元测试时无法输入GetData() public class MyData { private string _name; public void GetData() { Console.WriteLine("Please Enter your Name(only Alphabet)"); _name = Console.ReadLine(); Console

如何在MSTest中向类的方法输入数据。我有以下代码,但在进行单元测试时无法输入
GetData()

public class MyData
{
    private string _name;

    public void GetData()
    {
        Console.WriteLine("Please Enter your Name(only Alphabet)");
        _name = Console.ReadLine();
        Console.WriteLine(_name);
    }
}
测试班
我要做的是通过接口抽象出控制台方法。您的方法不应该关心控制台的工作方式。以后可能需要更改写或读一行的方式。大概是这样的:

public interface IConsoleMethods
{
    void WriteLine(string message);
    string ReadLine();
}
您可以按照您喜欢的方式实施它:

public class ConsoleMethods : IConsoleMethods
{
    public void WriteLine(string message)
    {
        Console.WriteLine(message);
    }

    public string ReadLine()
    {
        return Console.ReadLine();
    }
}
您必须为myData创建一个构造函数,该构造函数接受IConsoleMethods来初始化它。理想情况下,您希望根据使用情况注入它

public class MyData
{
    public MyData(IConsoleMethods consoleMethods) { this.console = consoleMethods; }

    public IConsoleMethods console;
    private string _name;

    public void GetData()
    {
        console.WriteLine("Please Enter your Name(only Alphabet)");
        _name = console.ReadLine();
        console.WriteLine(_name);
    }
}
您可以通过以下方式获得原始功能:

var myData = new MyData(new ConsoleMethods());
myData.GetData();
最后,您可以使用Mock测试它,并设置新的
控制台
类的期望值,就像这样(我使用Moq进行模拟):

[TestClass]
公共类MyDataTests
{
[测试方法]
public void GetDataTest()
{
const string expectedDisplayMessage=“请输入您的姓名(仅字母表)”;
const string readString=“test”;
var consoleMock=new Mock();
Setup(c=>c.ReadLine())。返回(readString);
var dd=新的MyData(consoleMock.Object);
dd.GetData();
//检查writeline是否被调用了两次
Verify(c=>c.WriteLine(It.IsAny()),Times.justice(2));
//检查是否在显示消息时调用了writeline
consoleMock.Verify(c=>c.WriteLine(expectedDisplayMessage),Times.Once);
//检查Readline是否被调用过一次
consoleMock.Verify(c=>c.ReadLine(),Times.Once);
//检查是否使用测试字符串调用了writeline一次
consoleMock.Verify(c=>c.WriteLine(readString),Times.Once);
}
}
我使用了(而不是MSTest)和(来模拟单元测试)

解释在代码中的注释中

using System;
using AlternativesCommon;
using NUnit.Framework;
using Moq;

/// <summary>
/// This demostrates how to use all approaches.
/// </summary>
namespace Usage
{
    public class Program
    {
        public static void Main()
        {
            var myData = new Original.MyData();
            myData.GetData();

            var myData1 = new Alternative1.MyData(new StandardConsole());
            myData1.GetData();

            var myData2 = new Alternative2.MyDataController(
                new StandardConsole(), 
                new Alternative2.MyData());
            myData2.GetData();
        }
    }

}

/// <summary>
/// This demonstrates how to test each approach.
/// </summary>
namespace Tests
{ 
    public class Alternative1Tests
    {
        /// <summary>
        /// The original apporach is too tightly coupled to be unit tested.
        /// </summary>
        [Test]
        public void Original_MyDataCannotBeAutomatedTested()
        {
        }

        /// <summary>
        /// The first alternative abstracts the console out.
        /// This is better, but still requires a lot of mocking and syntax.
        /// </summary>
        [Test]
        public void Alternative1_MyDataShouldWork()
        {
            // Arrange
            var mockConsole = new Mock<IConsole>();

            mockConsole.Setup(c => c.WriteLine(
                "Please Enter your Name(only Alphabet)"));
            mockConsole.Setup(c => c.ReadLine()).Returns("John");
            mockConsole.Setup(c => c.WriteLine("John"));

            var myData = new Alternative1.MyData(mockConsole.Object);

            // Act
            myData.GetData();

            // Assert
            mockConsole.VerifyAll();
        }

        /// <summary>
        /// The second alternative abstracts the data model out.
        /// This allows us to unit test just our domain logic.
        /// We can test the controller in a larger boundary or 
        /// integration test if we want (not shown).
        /// </summary>
        [Test]
        public void Alternative2_MyDataShouldWork()
        {
            // Arrange
            var name = "John";
            var myData = new Alternative2.MyData();
            var initialValue = myData.Name;

            // Act
            myData.Name = name;

            // Assert
            Assert.That(initialValue, Is.Null);
            Assert.That(myData.Name, Is.EqualTo(name));
        }

        /// <summary>
        /// This shows how one would unit test the controller.
        /// Lots of mocking (ew!).
        /// </summary>
        [Test]
        public void Alternative2_MyDataControllerShouldWork()
        {
            // Arrange
            var mockConsole = new Mock<IConsole>();

            mockConsole.Setup(c => c.WriteLine(
                "Please Enter your Name(only Alphabet)"));
            mockConsole.Setup(c => c.ReadLine()).Returns("John");
            mockConsole.Setup(c => c.WriteLine("John"));

            string name = null;
            var mockData = new Mock<Alternative2.IMyData>();
            mockData.SetupGet(d => d.Name).Returns(() => name);
            mockData.
                SetupSet(d => d.Name = It.IsAny<string>()).
                Callback((string value) => name = value);

            var controller = new Alternative2.MyDataController(
                mockConsole.Object, 
                mockData.Object);

            // Act
            controller.GetData();

            // Assert
            mockConsole.VerifyAll();
            mockData.VerifyAll();
        }
    }
}

namespace Original
{
    public class MyData
    {
        private string _name;

        public void GetData()
        {
            Console.WriteLine("Please Enter your Name(only Alphabet)");
            _name = Console.ReadLine();
            Console.WriteLine(_name);
        }
    }
}

namespace Alternative1
{
    public class MyData
    {
        private string _name;
        private IConsole _console;

        public MyData(IConsole console)
        {
            this._console = console;
        }

        public void GetData()
        {
            this._console.WriteLine("Please Enter your Name(only Alphabet)");
            this._name = this._console.ReadLine();
            this._console.WriteLine(this._name);
        }
    }
}

namespace Alternative2
{
    public interface IMyData
    {
        string Name
        {
            set;
            get;
        }
    }

    public class MyData : IMyData
    {
        private string _name;

        public string Name
        {
            set
            {
                // Do any validation here.  
                // For example, uncomment out the following 
                // (but don't forget to test!):

                //if (string.IsNullOrEmpty(value))
                //{
                //    throw new Exception(
                //        @"Name cannot be empty or null.");
                //}

                //if (value.Length > 100)
                //{
                //    throw new Exception(
                //        @"Name cannot be longer than 100 characters.");
                //}

                this._name = value;
            }
            get
            {
                return this._name;
            }
        }
    }

    public class MyDataController
    {
        private IConsole _console;
        private IMyData _data;

        public MyDataController(IConsole console, IMyData data)
        {
            this._console = console;
            this._data = data;
        }

        public void GetData()
        {
            this._console.WriteLine("Please Enter your Name(only Alphabet)");
            this._data.Name = this._console.ReadLine();
            this._console.WriteLine(this._data.Name);
        }
    }
}

/// <summary>
/// Defines the console abstraction used by both alternatives.
/// </summary>
namespace AlternativesCommon
{
    public interface IConsole
    {
        string ReadLine();
        void WriteLine(string line);
    }

    public class StandardConsole : IConsole
    {
        public string ReadLine()
        {
            return Console.ReadLine();
        }

        public void WriteLine(string line)
        {
            Console.WriteLine(line);
        }
    }
}
使用系统;
使用交替的通用程序;
使用NUnit.Framework;
使用最小起订量;
/// 
///这演示了如何使用所有方法。
/// 
命名空间使用
{
公共课程
{
公共静态void Main()
{
var myData=new Original.myData();
myData.GetData();
var myData1=new Alternative1.MyData(new StandardConsole());
myData1.GetData();
var myData2=new Alternative2.MyDataController(
新的StandardConsole(),
新的备选方案2.MyData());
myData2.GetData();
}
}
}
/// 
///这演示了如何测试每种方法。
/// 
命名空间测试
{ 
公开课备选测验
{
/// 
///最初的apporach耦合太紧密,无法进行单元测试。
/// 
[测试]
公共无效原件\u MyDataCannotBeAutomatedTested()
{
}
/// 
///第一个备选方案将控制台抽象出来。
///这更好,但仍然需要大量的模拟和语法。
/// 
[测试]
public void alternative 1_MyDataShouldWork()
{
//安排
var mockConsole=new Mock();
mockConsole.Setup(c=>c.WriteLine(
“请输入您的姓名(仅字母)”;
mockConsole.Setup(c=>c.ReadLine()).Returns(“John”);
mockConsole.Setup(c=>c.WriteLine(“John”);
var myData=new Alternative1.myData(mockConsole.Object);
//表演
myData.GetData();
//断言
mockConsole.VerifyAll();
}
/// 
///第二个备选方案将数据模型抽象出来。
///这允许我们只对域逻辑进行单元测试。
///我们可以在更大的边界或更大的范围内测试控制器
///如果需要,集成测试(未显示)。
/// 
[测试]
public void alternative 2_MyDataShouldWork()
{
//安排
var name=“John”;
var myData=new Alternative2.myData();
var initialValue=myData.Name;
//表演
myData.Name=Name;
//断言
Assert.That(initialValue,Is.Null);
Assert.That(myData.Name,Is.EqualTo(Name));
}
/// 
///这显示了如何对控制器进行单元测试。
///很多嘲弄(哎哟!)。
/// 
[测试]
public void alternative 2_MyDataControllerShouldWork()
{
//安排
var mockConsole=new Mock();
mockConsole.Setup(c=>c.WriteLine(
“请输入您的姓名(仅字母)”;
mockConsole.Setup(c=>c.ReadLine()).Returns(“John”);
mockConsole.Setup(c=>c.WriteLine(“John”);
字符串名称=null;
var mockData=new Mock();
SetupGet(d=>d.Name).返回(()=>Name);
模拟数据。
SetupSet(d=>d.Name=It.IsAny()。
回调((字符串值)=>name=value);
var controller=new Alternative2.MyDataController(
mockConsole.Object,
mockData.Object);
//表演
controller.GetData();
//断言
mockConsole.VerifyAll();
mockData.VerifyAll();
}
}
}
名称空间原始
{
公共类MyData
{
私有字符串\u名称;
public void GetData()
{
Console.WriteLine(“请输入您的姓名(仅字母表)”);
_name=Console.ReadLine();
Console.WriteLine(_名称);
}
}
}
名称空间可选1
{
公共类MyData
{
私有字符串\u名称;
专用IConsole_控制台;
公共MyData(IConsole控制台)
{
这个。_console=控制台;
}
public void GetData()
{
这个._console.WriteLine(“请输入您的姓名(仅字母表)”);
this._name=this._console.ReadLine();
这个。控制台。写线(这个
[TestClass]
public class MyDataTests
{
    [TestMethod]
    public void GetDataTest()
    {
        const string expectedDisplayMessage = "Please Enter your Name(only Alphabet)";
        const string readString = "test";

        var consoleMock = new Mock<IConsoleMethods>();
        consoleMock.Setup(c => c.ReadLine()).Returns(readString);

        var dd = new MyData(consoleMock.Object);

        dd.GetData();

        //Check that writeline was called twice
        consoleMock.Verify(c => c.WriteLine(It.IsAny<string>()), Times.Exactly(2));
        //Check that writeline was called with you display message
        consoleMock.Verify(c=>c.WriteLine(expectedDisplayMessage), Times.Once);
        //check that Readline was called once
        consoleMock.Verify(c=>c.ReadLine(),Times.Once);
        //Check that writeline was called with your test string once
        consoleMock.Verify(c=>c.WriteLine(readString), Times.Once);
    }
}
using System;
using AlternativesCommon;
using NUnit.Framework;
using Moq;

/// <summary>
/// This demostrates how to use all approaches.
/// </summary>
namespace Usage
{
    public class Program
    {
        public static void Main()
        {
            var myData = new Original.MyData();
            myData.GetData();

            var myData1 = new Alternative1.MyData(new StandardConsole());
            myData1.GetData();

            var myData2 = new Alternative2.MyDataController(
                new StandardConsole(), 
                new Alternative2.MyData());
            myData2.GetData();
        }
    }

}

/// <summary>
/// This demonstrates how to test each approach.
/// </summary>
namespace Tests
{ 
    public class Alternative1Tests
    {
        /// <summary>
        /// The original apporach is too tightly coupled to be unit tested.
        /// </summary>
        [Test]
        public void Original_MyDataCannotBeAutomatedTested()
        {
        }

        /// <summary>
        /// The first alternative abstracts the console out.
        /// This is better, but still requires a lot of mocking and syntax.
        /// </summary>
        [Test]
        public void Alternative1_MyDataShouldWork()
        {
            // Arrange
            var mockConsole = new Mock<IConsole>();

            mockConsole.Setup(c => c.WriteLine(
                "Please Enter your Name(only Alphabet)"));
            mockConsole.Setup(c => c.ReadLine()).Returns("John");
            mockConsole.Setup(c => c.WriteLine("John"));

            var myData = new Alternative1.MyData(mockConsole.Object);

            // Act
            myData.GetData();

            // Assert
            mockConsole.VerifyAll();
        }

        /// <summary>
        /// The second alternative abstracts the data model out.
        /// This allows us to unit test just our domain logic.
        /// We can test the controller in a larger boundary or 
        /// integration test if we want (not shown).
        /// </summary>
        [Test]
        public void Alternative2_MyDataShouldWork()
        {
            // Arrange
            var name = "John";
            var myData = new Alternative2.MyData();
            var initialValue = myData.Name;

            // Act
            myData.Name = name;

            // Assert
            Assert.That(initialValue, Is.Null);
            Assert.That(myData.Name, Is.EqualTo(name));
        }

        /// <summary>
        /// This shows how one would unit test the controller.
        /// Lots of mocking (ew!).
        /// </summary>
        [Test]
        public void Alternative2_MyDataControllerShouldWork()
        {
            // Arrange
            var mockConsole = new Mock<IConsole>();

            mockConsole.Setup(c => c.WriteLine(
                "Please Enter your Name(only Alphabet)"));
            mockConsole.Setup(c => c.ReadLine()).Returns("John");
            mockConsole.Setup(c => c.WriteLine("John"));

            string name = null;
            var mockData = new Mock<Alternative2.IMyData>();
            mockData.SetupGet(d => d.Name).Returns(() => name);
            mockData.
                SetupSet(d => d.Name = It.IsAny<string>()).
                Callback((string value) => name = value);

            var controller = new Alternative2.MyDataController(
                mockConsole.Object, 
                mockData.Object);

            // Act
            controller.GetData();

            // Assert
            mockConsole.VerifyAll();
            mockData.VerifyAll();
        }
    }
}

namespace Original
{
    public class MyData
    {
        private string _name;

        public void GetData()
        {
            Console.WriteLine("Please Enter your Name(only Alphabet)");
            _name = Console.ReadLine();
            Console.WriteLine(_name);
        }
    }
}

namespace Alternative1
{
    public class MyData
    {
        private string _name;
        private IConsole _console;

        public MyData(IConsole console)
        {
            this._console = console;
        }

        public void GetData()
        {
            this._console.WriteLine("Please Enter your Name(only Alphabet)");
            this._name = this._console.ReadLine();
            this._console.WriteLine(this._name);
        }
    }
}

namespace Alternative2
{
    public interface IMyData
    {
        string Name
        {
            set;
            get;
        }
    }

    public class MyData : IMyData
    {
        private string _name;

        public string Name
        {
            set
            {
                // Do any validation here.  
                // For example, uncomment out the following 
                // (but don't forget to test!):

                //if (string.IsNullOrEmpty(value))
                //{
                //    throw new Exception(
                //        @"Name cannot be empty or null.");
                //}

                //if (value.Length > 100)
                //{
                //    throw new Exception(
                //        @"Name cannot be longer than 100 characters.");
                //}

                this._name = value;
            }
            get
            {
                return this._name;
            }
        }
    }

    public class MyDataController
    {
        private IConsole _console;
        private IMyData _data;

        public MyDataController(IConsole console, IMyData data)
        {
            this._console = console;
            this._data = data;
        }

        public void GetData()
        {
            this._console.WriteLine("Please Enter your Name(only Alphabet)");
            this._data.Name = this._console.ReadLine();
            this._console.WriteLine(this._data.Name);
        }
    }
}

/// <summary>
/// Defines the console abstraction used by both alternatives.
/// </summary>
namespace AlternativesCommon
{
    public interface IConsole
    {
        string ReadLine();
        void WriteLine(string line);
    }

    public class StandardConsole : IConsole
    {
        public string ReadLine()
        {
            return Console.ReadLine();
        }

        public void WriteLine(string line)
        {
            Console.WriteLine(line);
        }
    }
}