[英]"Simulate" command line argument in unit-tests
I have some functionality, which depends on command line arguments, and different arguments should lead to different results.我有一些功能,这取决于命令行参数,不同的参数应该导致不同的结果。
I can't directly "simulate" this arguments, since there are some sort of chain dependencies - I need to unit-test some xaml control, which depends on view-model, which depends on certain additional class, which fetches command line arguments using Environment.GetCommandLineArgs
, and I can't directly impact on this last class to set arguments manually instead of using GetCommandLineArgs
.我不能直接“模拟”这个参数,因为存在某种链依赖关系 - 我需要对一些 xaml 控件进行单元测试,它依赖于视图模型,它依赖于某些额外的类,它使用获取命令行参数
Environment.GetCommandLineArgs
,我不能直接影响最后一个类来手动设置参数而不是使用GetCommandLineArgs
。
So, I'd like to know, is there any way to make Environment.GetCommandLineArgs
return value I want it to return, for certain unit-test.所以,我想知道,对于某些单元测试,有什么方法可以让
Environment.GetCommandLineArgs
返回我希望它返回的值。
You need to abstract Environment.GetCommandLineArgs
or what ever is eventually calling it behind something you can mock您需要抽象
Environment.GetCommandLineArgs
或最终在您可以模拟的东西后面调用它的东西
public interface ICommandLineInterface {
string[] GetCommandLineArgs();
}
Which can eventually be implemented in a concrete class like最终可以在一个具体的类中实现,比如
public class CommandInterface : ICommandLineInterface {
public string[] GetCommandLineArgs() {
return Environment.GetCommandLineArgs();
}
}
And can be Tested using Moq
and FluentAssertions
并且可以使用
Moq
和FluentAssertions
进行测试
[TestMethod]
public void Test_Should_Simulate_Command_Line_Argument() {
// Arrange
string[] expectedArgs = new[] { "Hello", "World", "Fake", "Args" };
var mockedCLI = new Mock<ICommandLineInterface>();
mockedCLI.Setup(m => m.GetCommandLineArgs()).Returns(expectedArgs);
var target = mockedCLI.Object;
// Act
var args = target.GetCommandLineArgs();
// Assert
args.Should().NotBeNull();
args.Should().ContainInOrder(expectedArgs);
}
Since you are dealing with environment variables, why don't we wrap the outside dependencies into one EnvironmentHelper class, then inject the dependencies?既然你在处理环境变量,为什么我们不将外部依赖包装到一个 EnvironmentHelper 类中,然后注入依赖呢?
Here is my suggestion:这是我的建议:
public class EnvironmentHelper
{
Func<string[]> getEnvironmentCommandLineArgs;
// other dependency injections can be placed here
public EnvironmentHelper(Func<string[]> getEnvironmentCommandLineArgs)
{
this.getEnvironmentCommandLineArgs = getEnvironmentCommandLineArgs;
}
public string[] GetEnvironmentCommandLineArgs()
{
return getEnvironmentCommandLineArgs();
}
}
Here is the Mock method:这是模拟方法:
public static string[] GetFakeEnvironmentCommandLineArgs()
{
return new string[] { "arg1", "arg2" };
}
In your source code:在您的源代码中:
EnvironmentHelper envHelper = new EnvironmentHelper(Environment.GetCommandLineArgs);
string[] myArgs = envHelper.GetEnvironmentCommandLineArgs();
In your unit test code:在您的单元测试代码中:
EnvironmentHelper envHelper = new EnvironmentHelper(GetFakeEnvironmentCommandLineArgs);
string[] myArgs = envHelper.GetEnvironmentCommandLineArgs();
If you want something unit-testable it should have its dependencies on a abstraction that is at least as strict as its implementation.如果你想要一些可单元测试的东西,它应该依赖于至少与其实现一样严格的抽象。
Usually you'd get the dependencies through your constructor of your class or a property method.通常,您会通过类的构造函数或属性方法获取依赖项。 Constructor is preferred, generally, because now a consumer of your class knows at compile-time what dependencies are needed.
通常,构造函数是首选,因为现在您的类的使用者在编译时知道需要哪些依赖项。
public void int Main(string[] args)
{
// Validate the args are valid (not shown).
var config = new AppConfig();
config.Value1 = args[0];
config.Value2 = int.Parse(args[1]);
// etc....
}
public class MyService()
{
private AppConfig _config;
public MyService(AppConfig config)
{
this._config = config;
}
}
I normally don't put a config object behind an interface because it only has data - which is serializable.我通常不会将配置对象放在接口后面,因为它只有数据——这是可序列化的。 As long as it has no methods, then I shouldn't need to replace it with a subclass with
override
-d behavior.只要它没有方法,那么我就不需要用具有
override
-d 行为的子类替换它。 Also I can just new
it up directly in my tests.我也可以直接在我的测试中
new
它。
Also, I've never ran into a situation when I wanted to depend on an abstraction of the command line arguments themselves to a service - why does it need to know it's behind a command-line?此外,我从来没有遇到过这样的情况:我想依赖于对服务本身的命令行参数的抽象——为什么它需要知道它在命令行后面? The closest I've gotten is use PowerArgs for easy parsing, but I'll consume that right in
Main
.我得到的最接近的是使用PowerArgs来轻松解析,但我会在
Main
中使用它。 What I normally do is something like maybe read in the port number for a web server on the command-line arguments (I let the user of the app choose so that I can run multiple copies of my web server on the same machine - maybe different versions or so I can run automated tests while I'm debugging and not conflict ports), parse them directly in my Main
class.我通常做的可能是在命令行参数上读取 Web 服务器的端口号(我让应用程序的用户选择,以便我可以在同一台机器上运行我的 Web 服务器的多个副本 - 可能不同版本左右我可以在调试时运行自动化测试而不是冲突端口),直接在我的
Main
类中解析它们。 Then in my web server I depend on the parsed command-line arguments, in this case an int
.然后在我的网络服务器中,我依赖于解析的命令行参数,在本例中为
int
。 That way the fact that the configuration is coming from a command-line is irrelevant - I can move it to an App.config
file later (which is also basically bound to the lifecycle of the process) if I prefer - then I can extract common configuration to configSource
files.这样,配置来自命令行的事实是无关紧要的 - 如果我愿意,我可以稍后将其移动到
App.config
文件(这也基本上与进程的生命周期绑定) - 然后我可以提取公共配置到configSource
文件。
Instead of depending on an abstraction for command-line in general (which each service consuming would have to re-parse if you kept it pure), I usually abstract the command-line and App.config
dependencies to a strongly-typed object - maybe an app-level config class and a test-level config class and introduce multiple configuration objects as needed - (the app wouldn't necessarily care about this, while the E2E test infrastructure would need this in a separate part of the App.config
: where do I grab the client static files from, where do I grab the build scripts in a test or developer environment to auto-generate/auto-update an index.html file, etc.).我通常不依赖于命令行的抽象(如果你保持纯洁,每个服务消耗都必须重新解析),我通常将命令行和
App.config
依赖项抽象为强类型对象 - 也许一个应用程序级配置类和一个测试级配置类,并根据需要引入多个配置对象 - (应用程序不一定关心这一点,而 E2E 测试基础设施需要在App.config
的单独部分中使用它:我从哪里获取客户端静态文件,从哪里获取测试或开发环境中的构建脚本以自动生成/自动更新 index.html 文件等)。
You can do it much more easier with Typemock Isolator .使用Typemock Isolator可以更轻松地完成它。 It allows to mock not only interfaces, so.
它不仅允许模拟接口,因此。 Take a look:
看一看:
[TestMethod, Isolated]
public void TestFakeArgs()
{
//Arrange
Isolate.WhenCalled(() => Environment.GetCommandLineArgs()).WillReturn(new[] { "Your", "Fake", "Args" });
//Act
string[] args = Environment.GetCommandLineArgs();
//Assert
Assert.AreEqual("Your", args[0]);
Assert.AreEqual("Fake", args[0]);
Assert.AreEqual("Args", args[0]);
}
Mocking Environment.GetCommandLineArgs()
took only one line: Mocking
Environment.GetCommandLineArgs()
只用了一行:
Isolate.WhenCalled(() => Environment.GetCommandLineArgs()).WillReturn(new[] { "Your", "Fake", "Args" });
And you don't need to create new Interfaces and to change production code.而且您不需要创建新接口和更改生产代码。
Hope it helps!希望能帮助到你!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.