如何在 MSTest 中测试 class 具有控制台 I/O
How to test class having Console I/O in MSTest
如何向MSTest中的一个class方法输入数据。我有以下代码,但在进行单元测试时无法输入 GetData()
。
public class MyData
{
private string _name;
public void GetData()
{
Console.WriteLine("Please Enter your Name(only Alphabet)");
_name = Console.ReadLine();
Console.WriteLine(_name);
}
}
测试Class
[TestClass]
public class UnitTest1
{
MyData dd = new MyData();
[TestMethod]
public void TestMethod1()
{
dd.GetData();
}
}
我要做的是通过接口抽象出控制台方法。您的方法不应该关心 Console 是如何工作的。您可能希望稍后更改您书写或阅读一行的方式。像这样:
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 测试它并设置对新 Console
class 的期望(我使用 Moq 进行模拟):
[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);
}
}
我使用了 NUnit (instead of MSTest) and Moq(模拟单元测试)。
解释在代码中的注释中。
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);
}
}
}
如何向MSTest中的一个class方法输入数据。我有以下代码,但在进行单元测试时无法输入 GetData()
。
public class MyData
{
private string _name;
public void GetData()
{
Console.WriteLine("Please Enter your Name(only Alphabet)");
_name = Console.ReadLine();
Console.WriteLine(_name);
}
}
测试Class
[TestClass]
public class UnitTest1
{
MyData dd = new MyData();
[TestMethod]
public void TestMethod1()
{
dd.GetData();
}
}
我要做的是通过接口抽象出控制台方法。您的方法不应该关心 Console 是如何工作的。您可能希望稍后更改您书写或阅读一行的方式。像这样:
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 测试它并设置对新 Console
class 的期望(我使用 Moq 进行模拟):
[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);
}
}
我使用了 NUnit (instead of MSTest) and Moq(模拟单元测试)。
解释在代码中的注释中。
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);
}
}
}