我正在尝试用C#为静态方法编写单元测试

本文关键字:静态方法 单元测试 | 更新日期: 2023-09-27 18:04:29

我正在学习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;
    }
}

我正在尝试用C#为静态方法编写单元测试

您正在尝试测试一个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'sMapPath(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);
}

测试静态本身很容易,只需调用它们并对结果进行断言(实际问题是测试使用静态的代码,因为它们无法模拟出来(。这种特殊情况下的测试因另一件事而变得复杂。

Log方法的真正问题是ti自己创建了一个ServerPath实例,这排除了任何依赖注入的机会(相反,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实例,然后可以使用该实例注入模拟

在这种情况下,您需要公开一种设置依赖关系的方法。目前,您在方法中直接使用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来模拟服务器路径

这里有一个使用依赖注入的简单方法。

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);
}

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

下面是一篇演示将记录器注入类的文章。通常有更好的方法来实现相同的目的(比如使用拦截器(,但至少它可以防止代码在任何地方都依赖于某个具体类。