简体   繁体   English

单元测试......如何改进它

[英]Unit testing… how to improve it

I want to test a piece of code which returns an object. 我想测试一段返回对象的代码。

I use NUnit and in a test class, I write a method to test if my method works fine... 我使用NUnit并在测试类中编写一个方法来测试我的方法是否正常工作...

[Test]
public void GetMyObjectFromLog()
{
     string _xmlFilePath = @"C:\XmlFile.xml";
     MyObjectParser _myObjectParser = new MyObjectParser();
     MyObject _mockMyObject = new MyObject
                              {
                                   Title = "obj",
                                   Name = "objName"
                              }
     MyObject _myObject = _myObjectParser.GetMyObjectFromLog(_xmlFilePath);

     Assert.AreEqual(_mockMyObject , _myObject);
}

This test does not work because MyObject does not override Equals method, and I don't want to override Equals method just for test purpose. 此测试不起作用,因为MyObject不会覆盖Equals方法,并且我不想仅为测试目的而重写Equals方法。

So I rewrite the test like this : 所以我重写了这样的测试:

[Test]
public void GetMyObjectFromLog()
{
     string _xmlFilePath = @"C:\XmlFile.xml";
     MyObjectParser _myObjectParser = new MyObjectParser();
     MyObject _myObject = _myObjectParser.GetMyObjectFromLog(_xmlFilePath);

     Assert.AreEqual("obj", _myObject.Title);
     Assert.AreEqual("objName", _myObject.Name);
}

Ok, it works... but is this test is pertinent ? 好吧,它有效......但这个测试是否相关? Moreover, there is a dependance to a file. 而且,对文件有依赖性。

Is it pertinent to use a Mock Framework instead of ? 使用模拟框架而不是相关吗? And how to use it ? 以及如何使用它?

Thank you ! 谢谢 !

First of all, the method inside the parser should be name "Parse" instead of "Get". 首先,解析器内部的方法应该是名称“Parse”而不是“Get”。

Second of all, if you don't want the object itself to be able to compare itself to another, then it's perfectly fine to compare them just like you did (property by property). 其次,如果你不希望对象本身能够将自己与另一个对象进行比较,那么就像你所做的那样比较它们(属性属性)就完全没问题了。 But this can be extracted into a helper method in the test class. 但是这可以在测试类中提取到辅助方法中。

And lastly, you don't really want to tight-couple your parser with a file. 最后,您并不想将解析器与文件紧密耦合。 You just want to parse text. 你只想解析文本。 If you want to include a static helper method that also opens a file and everything, that's your choice, but it shouldn't be a pure instance dependency. 如果要包含一个静态帮助器方法,该方法也会打开一个文件和所有内容,这是您的选择,但它不应该是纯实例依赖项。

[Test]
public void ParsesObjectFromXml()
{
     string xmlInput = " ... ";
     MyObjectXmlParser parser = new MyObjectXmlParser();
     MyObject expected = new MyObject() {Title = "obj", Name="objName"};

     AssertMyObjectsAreEqual(expected, parser.Parse(xmlInput));
}

private bool AssertMyObjectsAreEqual(MyObject expected, MyObject actual)
{
     Assert.AreEqual(expected.Title, actual.Title);
     Assert.AreEqual(expected.Name, actual.Name);
}

Now both your class and your test are clearer and only have a single responsibility. 现在你的班级和考试都更清晰,只有一个责任。

About that dependency on the file (that even appears not to be in the project!): 关于对文件的依赖(甚至看起来不在项目中!):

You could override that _myObjectParser.GetMyObjectFromLog to also accept a Stream. 您可以覆盖_myObjectParser.GetMyObjectFromLog以接受Stream。 Then you can add that XML file as embedded resource and read it from the assembly. 然后,您可以将该XML文件添加为嵌入式资源,并从程序集中读取它。

its absolutly ok to have a reference to a file. 绝对可以有一个文件的引用。 Generally there are two kinds of test types: unit tests and integration tests. 通常有两种测试类型:单元测试和集成测试。

Integration tests are always interacting with some file / database or what ever. 集成测试总是与某些文件/数据库或其他任何东西进行交互。 In your case you could improve the test by mocking the _myObjectParser.GetMyObjectFromLog. 在您的情况下,您可以通过模拟_myObjectParser.GetMyObjectFromLog来改进测试。

You could either write the mock yourself or use a framework like rhinomocks. 您可以自己编写模拟或使用像rhinomocks这样的框架。 A very good book for nunit / rhinomocks is: The Art of Unit Testing. nunit / rhinomocks的一本非常好的书是:单元测试的艺术。

A better testable version of GetMyObjectFromLog could look like this: 一个更好的可测试版本的GetMyObjectFromLog可能如下所示:

public MyObject GetMyObjectFromLog(IMyXmlReader reader)
{
    var xmlData = reader.GetData();
    //make your object here
    var obj = new MyObject(xmlData);
    return obj;  
}

You can then implement 2 new classes that implemments a Interface IMyXmlReader, one that reads from a file for your productive code, and one that returns just always the same string on GetData(). 然后,您可以实现2个实现接口IMyXmlReader的新类,一个从生成代码的文件中读取的类,以及一个在GetData()上始终返回相同字符串的类。

You can then use your Class that returns always a static string for your unit tests. 然后,您可以使用您的类,它始终返回单元测试的静态字符串。

Understand? 了解? Sorry for my english :) 对不起我的英语不好 :)

Yes it looks ok like that. 是的,它看起来不错。 You could, however, override Equals in your test project, if you don't need it in your main project. 但是,如果在主项目中不需要它,则可以在测试项目中覆盖Equals Eg usually I create classes which promote private and protected classes to public ones. 例如,通常我创建的类将私有和受保护的类提升为公共类。 So you're free to extend your main project in order to simply testing in your test project. 因此,您可以自由扩展主项目,以便在测试项目中进行简单测试。

A file dependency is ok, just put this file in your test suite project. 文件依赖是可以的,只需将此文件放在测试套件项目中即可。

The purpose of unit testing is to test the logic of the code, your test above is doing two things: 1. Logic 2. Search 单元测试的目的是测试代码的逻辑,上面的测试是做两件事:1。逻辑2.搜索

I said search because you are trying to match an object that you created in an external file. 我说搜索是因为你试图匹配你在外部文件中创建的对象。 If you do that the issue is your build will start to break if there is no external file present and you don't want that. 如果你这样做,问题是如果没有外部文件并且你不想要它,你的构建将开始中断。

Solution: Create the file on the go but don't create a physical file, create a File in memory and then insert the object that you need to match, then match the object. 解决方案:随时创建文件,但不创建物理文件,在内存中创建文件,然后插入需要匹配的对象,然后匹配该对象。

This way this test will never break. 这样这个测试永远不会破坏。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM