我试图在C#中为静态方法编写单元测试

时间:2016-08-28 16:37:44

标签: c# unit-testing

我正在学习c#web应用程序的单元测试。我被困在上面提到的场景中。我不确定我是否以正确的方式做到这一点。我有单独测试的FakePath类。如何在MSTest中为静态方法Abc.log()编写单元测试?

public class Abc
{
    public static void log(string msg)
    {
        //Read on Write on path;
        string path = getPath(new ServerPath());
    }

    public static string getPath(IServerPath path)
    {
        return path.MapPath("file.txt");
    }
}

interface IServerPath()
{
    string MapPath(string file);
}

class ServerPath : IServerPath
{
    string MapPath(string file)
    {
        return HttpContext.Current.Server.MapPath(file);
    }
}

class FakeServerPath : IServerPath
{
    string MapPath(string file)
    {
        return @"C:\"+file;
    }
}

4 个答案:

答案 0 :(得分:2)

您正在尝试测试void方法,因此断言此方法的一个选项是验证是否调用该方法:

string expectedStr = "c:\file.txt";
[TestMethod]
public void FakeServerPath_VerifyMapPathWasCalled()
{
    var fakeServerPath = Isolate.Fake.NextInstance<ServerPath>();
    Isolate.WhenCalled(() => fakeServerPath.MapPath("")).WillReturn(expectedStr);

    Abc.log("");

    Isolate.Verify.WasCalledWithExactArguments(() => fakeServerPath.MapPath("file.txt"));
}

另一种选择是测试getPath(IServerPath path)方法的返回值,方法是修改ServerPath's MapPath(string file)方法的返回值以返回所需值,并断言返回值正如所料。

string expectedStr = "c:\file.txt";
[TestMethod]
public void ModifyReturnValueFromMapPath_IsEqualToExpactedStr()
{
    var fakeServerPath = Isolate.Fake.NextInstance<ServerPath>();

    Isolate.WhenCalled(() => fakeServerPath.MapPath("")).WillReturn(expectedStr);

    var result = Abc.getPath(fakeServerPath);

    Assert.AreEqual(expectedStr, result);
}

请注意,通过使用TypeMock Isolator,您可以在不更改原始代码的情况下伪造未来的“ServerPath”实例。 如果需要,TypeMock也可以模拟HttpContext类:

string expectedStr = "c:\file.txt";
[TestMethod]
public void ModifyReturnValueFromHttpContext_IsEqualToExpactedStr()
{
    var serverPath = new ServerPath();

    Isolate.WhenCalled(() => HttpContext.Current.Server.MapPath("")).WillReturn(expectedStr);

    var result = Abc.getPath(serverPath);

    Assert.AreEqual(expectedStr, result);
}

答案 1 :(得分:0)

在这种情况下,您需要公开一种设置依赖关系的方法。目前,您在该方法中直接使用了new ServerPath(),这使您很难注入FakeServerPath进行测试。

您可以修改Abc

public class Abc
{
    static Abc() { ServerPath = new ServerPath(); }

    public static IServerPath ServerPath { get; set; }

    public static void log(string msg) {
        //Read on Write on path;
        string path = getPath(ServerPath);
    }

    public static string getPath(IServerPath path) {
        return path.MapPath("file.txt");
    }
}

测试看起来像这样

[TestMethod]
public void Abc_log_Test() {
    //Arrange
    string filename = "fakeFile.txt";
    string expected = @"C:\" + filename;
    var mockServerPath = new Mock<IServerPath>();
    mockServerPath
        .Setup(m => m.MapPath(filename))
        .Returns(expected)
        .Verifiable();
    Abc.ServerPath = mockServerPath.Object;
    var message = "Hello world";

    //Act
    Abc.log(message);

    //Assert
    mockServerPath.Verify();
}

注意我使用Moq来模拟服务器路径

答案 2 :(得分:0)

测试静态本身很简单,只需调用它们并对结果进行断言(实际问题是测试使用静态的代码,因为它们无法被模拟掉)。在这种特殊情况下进行测试很复杂。

Log方法的真正问题在于ti会自行创建ServerPath个实例,从而排除了Dependency Injection的任何可能性(相反,GetPath方法对测试非常友好,因为它将接口作为参数)。

我将在Abc类上引入重构,以便对其进行更好的测试。我会这样修改它:

public static class Logger
{
    public static void Log(IServerPath path, string msg)
    {
        //add parameter checking here

        string path = path.MapPath("file.txt");
        //actual log goes here
    }
}

注意,现在测试将负责创建IServerPath实例,然后可以使用它来注入模拟

答案 3 :(得分:0)

这是一种使用依赖注入的简单方法。

public class FileLogger
{
    private readonly string _filePath;

    public FileLogger(string filePath)
    {
        _filePath = filePath;
    }

    public void Log(string msg)
    {
        //write to the log
    }
} 

现在您的日志记录类只有一个责任 - 写入文件。 负责确定要写入哪个文件。它希望将这个值注入其中。

另外,我避免使用static方法。如果方法是static,则会再次出现类似的问题:如何测试依赖于日志记录类的类?

通过使其成为非静态的,您可以重复相同的模式 - 模拟记录器,以便您可以测试依赖于它的类。

public interface ILogger
{
    void Log(string msg);
}

然后您的日志记录类可以实现该接口,您可以将该接口(而不是具体类)注入可能需要写入日志的类中。

Here's a post演示了如何将一个记录器注入一个类。通常有更好的方法来实现相同的目的(比如使用拦截器),但至少它会阻止你的代码在某个具体的类中到处都有依赖。