Unit testing 什么';什么是单元测试?

Unit testing 什么';什么是单元测试?,unit-testing,Unit Testing,可能的重复项: 我认识到,对于95%的人来说,这是一个非常WTF的问题 所以。什么是单元测试?我知道,基本上你是在尝试隔离原子功能,但你如何测试它呢?什么时候有必要?什么时候可笑? 你能举个例子吗?(最好是用C语言?我在这个网站上主要是从Java开发者那里听说的,所以这可能是面向对象语言特有的?我真的不知道。) 我知道许多程序员笃信单元测试。这是怎么回事 编辑:另外,您通常花在编写单元测试上的时间与花在编写新代码上的时间的比率是多少?在计算机编程中,单元测试是一种软件验证和确认方法,程序员在

可能的重复项:

我认识到,对于95%的人来说,这是一个非常WTF的问题

所以。什么是单元测试?我知道,基本上你是在尝试隔离原子功能,但你如何测试它呢?什么时候有必要?什么时候可笑? 你能举个例子吗?(最好是用C语言?我在这个网站上主要是从Java开发者那里听说的,所以这可能是面向对象语言特有的?我真的不知道。)

我知道许多程序员笃信单元测试。这是怎么回事


编辑:另外,您通常花在编写单元测试上的时间与花在编写新代码上的时间的比率是多少?

在计算机编程中,单元测试是一种软件验证和确认方法,程序员在其中测试单个源代码单元是否适合使用。单元是应用程序中最小的可测试部分。在过程编程中,单元可以是单个程序、函数、过程等,而在面向对象编程中,最小的单元是类,它可能属于基类/超类、抽象类或派生类/子类


例如,如果您有一个矩阵类,您可能需要一个单元测试来检查它

矩阵A=矩阵(…);
A.inverse()*A==Matrix::Identity

单元测试是您编写的另一个软件,它可以练习您的主代码以接受所需的功能

我可以写一个计算器程序,它看起来很漂亮,有按钮,看起来像一个TI计算器,它可以产生2+2=5。看起来不错,但是我,开发人员可以在我的代码上运行一些自动化的、编码的单元测试,而不是将某些代码的每次迭代发送给一个人工测试人员,并提供一个长长的检查列表

基本上,单元测试应该由同行自己测试,或者由其他仔细的审查来回答“这是我想要的测试吗?”

单元测试将有一组“给定”或“输入”,并将其与预期的“输出”进行比较

当然,关于如何、何时以及在多大程度上使用单元测试,有不同的方法论(关于这些方面的一些问题,请检查)。然而,在最基本的情况下,它们是一个程序,或者是其他一些程序的可加载模块,可以做出断言

单元测试的标准语法可能是有一行代码,如下所示:
Assert.AreEqual(A,b)

单元测试方法主体可以设置输入和实际输出,并将其与预期输出进行比较

HelloWorldExample helloWorld = new HelloWorldExample();
string expected = "Hello World!";
string actual = helloWorld.GetString();

Assert.AreEqual( expected, actual );
如果您的单元测试是用特定框架的语言编写的(例如jUnit、NUnit等),则标记为“测试运行”一部分的每个方法的结果都将聚合为一组测试结果,例如一个红色圆点表示失败,绿色圆点表示成功,和/或XML文件等

作为对你最新评论的回应,“理论”可以提供一些现实世界的见解。测试驱动开发(TDD)说明了何时以及多久使用一次测试。在我的最新项目中,我们没有遵守TDD,但我们确实使用了单元测试来验证我们的代码是否达到了预期的效果

假设您选择实现汽车接口。汽车界面如下所示:

interface ICar
{
    public void Accelerate( int delta );
    public void Decelerate( int delta );
    public int GetCurrentSpeed();
}
public static void TestAcceleration( ICar car )
{
    int oldSpeed = car.GetCurrentSpeed();
    car.Accelerate( 5 );
    int newSpeed = car.GetCurrentSpeed();
    Assert.IsTrue( newSpeed > oldSpeed );
}
public static void TestDeceleration( ICar car )
{
    int oldSpeed = car.GetCurrentSpeed();
    car.Decelerate( 5 );
    int newSpeed = car.GetCurrentSpeed();
    Assert.IsTrue( newSpeed < oldSpeed );
}
您选择在FordTaurus类中实现Car接口:

class FordTaurus : ICar
{
    private int mySpeed;
    public Accelerate( int delta )
    {
        mySpeed += delta;
    }
    public Decelerate( int delta )
    {
        mySpeed += delta;
    }
    public int GetCurrentSpeed()
    {
        return mySpeed;
    }
}
你假设要让金牛座减速,你必须通过一个负值。但是,假设您有一组针对Car接口编写的单元测试,它们如下所示:

interface ICar
{
    public void Accelerate( int delta );
    public void Decelerate( int delta );
    public int GetCurrentSpeed();
}
public static void TestAcceleration( ICar car )
{
    int oldSpeed = car.GetCurrentSpeed();
    car.Accelerate( 5 );
    int newSpeed = car.GetCurrentSpeed();
    Assert.IsTrue( newSpeed > oldSpeed );
}
public static void TestDeceleration( ICar car )
{
    int oldSpeed = car.GetCurrentSpeed();
    car.Decelerate( 5 );
    int newSpeed = car.GetCurrentSpeed();
    Assert.IsTrue( newSpeed < oldSpeed );
}
公共静态无效测试加速(ICar车辆)
{
int oldSpeed=car.GetCurrentSpeed();
汽车加速(5);
int newSpeed=car.GetCurrentSpeed();
IsTrue(newSpeed>oldSpeed);
}
公共静态减速度试验(ICar车)
{
int oldSpeed=car.GetCurrentSpeed();
汽车减速(5);
int newSpeed=car.GetCurrentSpeed();
IsTrue(newSpeed

测试告诉你,你可能已经实现了错误的汽车接口。

< P>我现在是java,在C++之前,我完全相信我所做的每一件工作,我现在都不感到羞愧,是通过我选择的测试策略而增强的。略过测试是有害的

我肯定你会测试你写的代码。你用什么技巧?例如,您可以坐在调试器中,单步执行代码并观察发生了什么。您可以针对某人提供给您的一些测试数据执行代码。您可以设计特定的输入,因为您知道您的代码对于某些输入值有一些有趣的行为。假设你的东西使用了别人的东西,但还没有准备好,你模拟了他们的代码,这样你的代码至少可以使用一些假答案

在所有情况下,您都可能在某种程度上需要进行单元测试。最后一个是特别有趣的-您正在进行隔离测试,测试您的单元,即使他们还没有准备好

我的意见是:

1) 。可以很容易地重新运行的测试非常有用——捕捉到无止境的后期蠕变缺陷。 相比之下,坐在调试器中进行测试是令人麻木的

2) 。在编写代码时或在编写代码之前构造有趣的测试的活动使您将注意力集中在边缘案例上。那些恼人的零和零输入,那些“一个接一个的错误”。我认为良好的单元测试会产生更好的代码

3) 。维护测试是有成本的。一般来说,这是值得的,但不要低估让它们工作的努力

4) 。可能存在过度使用单元测试的趋势。真正有趣的bug往往是在集成各个部分时出现的。你用真实的东西替换你嘲笑的那个库,瞧!它不像罐头上说的那样。此外,手动或爆炸性测试仍有作用。有洞察力的测试人员会发现特殊缺陷。

逐点:// Unit tests for the iloc_parser.{h, cc} #include <fstream> #include <iostream> #include <gtest/gtest.h> #include <sstream> #include <string> #include <vector> #include "iloc_parser.h" using namespace std; namespace compilers { // Here is my test class class IlocParserTest : public testing::Test { protected: IlocParserTest() {} virtual ~IlocParserTest() {} virtual void SetUp() { const testing::TestInfo* const test_info = testing::UnitTest::GetInstance()->current_test_info(); test_name_ = test_info->name(); } string test_name_; }; // Here is a utility function to help me test static void ReadFileAsString(const string& filename, string* output) { ifstream in_file(filename.c_str()); stringstream result(""); string temp; while (getline(in_file, temp)) { result << temp << endl; } *output = result.str(); } // All of these TEST_F things are macros that are part of the test framework I used. // Just think of them as test functions. The argument is the name of the test class. // The second one is the name of the test (A descriptive name so you know what it is // testing). TEST_F(IlocParserTest, ReplaceSingleInstanceOfSingleCharWithEmptyString) { string to_replace = "blah,blah"; string to_find = ","; string replace_with = ""; IlocParser::FindAndReplace(to_find, replace_with, &to_replace); EXPECT_EQ("blahblah", to_replace); } TEST_F(IlocParserTest, ReplaceMultipleInstancesOfSingleCharWithEmptyString) { string to_replace = "blah,blah,blah"; string to_find = ","; string replace_with = ""; IlocParser::FindAndReplace(to_find, replace_with, &to_replace); EXPECT_EQ("blahblahblah", to_replace); } TEST_F(IlocParserTest, ReplaceMultipleInstancesOfMultipleCharsWithEmptyString) { string to_replace = "blah=>blah=>blah"; string to_find = "=>"; string replace_with = ""; IlocParser::FindAndReplace(to_find, replace_with, &to_replace); EXPECT_EQ("blahblahblah", to_replace); } // This test was suppsoed to strip out the "r" from register // register names in the ILOC code. TEST_F(IlocParserTest, StripIlocLineLoadI) { string iloc_line = "loadI\t1028\t=> r11"; IlocParser::StripIlocLine(&iloc_line); EXPECT_EQ("loadI\t1028\t 11", iloc_line); } // Here I make sure stripping the line works when it has a comment TEST_F(IlocParserTest, StripIlocLineSubWithComment) { string iloc_line = "sub\tr12, r10\t=> r13 // Subtract r10 from r12\n"; IlocParser::StripIlocLine(&iloc_line); EXPECT_EQ("sub\t12 10\t 13 ", iloc_line); } // Here I make sure I can break a line up into the tokens I wanted. TEST_F(IlocParserTest, TokenizeIlocLineNormalInstruction) { string iloc_line = "sub\t12 10\t 13\n"; // already stripped vector<string> tokens; IlocParser::TokenizeIlocLine(iloc_line, &tokens); EXPECT_EQ(4, tokens.size()); EXPECT_EQ("sub", tokens[0]); EXPECT_EQ("12", tokens[1]); EXPECT_EQ("10", tokens[2]); EXPECT_EQ("13", tokens[3]); } // Here I make sure I can create an instruction from the tokens TEST_F(IlocParserTest, CreateIlocInstructionLoadI) { vector<string> tokens; tokens.push_back("loadI"); tokens.push_back("1"); tokens.push_back("5"); IlocInstruction instruction(IlocInstruction::NONE); EXPECT_TRUE(IlocParser::CreateIlocInstruction(tokens, &instruction)); EXPECT_EQ(IlocInstruction::LOADI, instruction.op_code()); EXPECT_EQ(2, instruction.num_operands()); IlocInstruction::OperandList::const_iterator it = instruction.begin(); EXPECT_EQ(1, *it); ++it; EXPECT_EQ(5, *it); } // Making sure the CreateIlocInstruction() method fails when it should. TEST_F(IlocParserTest, CreateIlocInstructionFromMisspelledOp) { vector<string> tokens; tokens.push_back("ADD"); tokens.push_back("1"); tokens.push_back("5"); tokens.push_back("2"); IlocInstruction instruction(IlocInstruction::NONE); EXPECT_FALSE(IlocParser::CreateIlocInstruction(tokens, &instruction)); EXPECT_EQ(0, instruction.num_operands()); } // Make sure creating an empty instruction works because there // were times when I would actually have an empty tokens vector. TEST_F(IlocParserTest, CreateIlocInstructionFromNoTokens) { // Empty, which happens from a line that is a comment. vector<string> tokens; IlocInstruction instruction(IlocInstruction::NONE); EXPECT_TRUE(IlocParser::CreateIlocInstruction(tokens, &instruction)); EXPECT_EQ(IlocInstruction::NONE, instruction.op_code()); EXPECT_EQ(0, instruction.num_operands()); } // This was a function that helped me generate actual code // that I could output as a line in my output file. TEST_F(IlocParserTest, MakeIlocLineFromInstructionAddI) { IlocInstruction instruction(IlocInstruction::ADDI); vector<int> operands; operands.push_back(1); operands.push_back(2); operands.push_back(3); instruction.CopyOperandsFrom(operands); string output; EXPECT_TRUE(IlocParser::MakeIlocLineFromInstruction(instruction, &output)); EXPECT_EQ("addI r1, 2 => r3", output); } // This test actually glued a bunch of stuff together. It actually // read an input file (that was the name of the test) and parsed it // I then checked that it parsed it correctly. TEST_F(IlocParserTest, ParseIlocFileSimple) { IlocParser parser; vector<IlocInstruction*> lines; EXPECT_TRUE(parser.ParseIlocFile(test_name_, &lines)); EXPECT_EQ(2, lines.size()); // Check first line EXPECT_EQ(IlocInstruction::ADD, lines[0]->op_code()); EXPECT_EQ(3, lines[0]->num_operands()); IlocInstruction::OperandList::const_iterator operand = lines[0]->begin(); EXPECT_EQ(1, *operand); ++operand; EXPECT_EQ(2, *operand); ++operand; EXPECT_EQ(3, *operand); // Check second line EXPECT_EQ(IlocInstruction::LOADI, lines[1]->op_code()); EXPECT_EQ(2, lines[1]->num_operands()); operand = lines[1]->begin(); EXPECT_EQ(5, *operand); ++operand; EXPECT_EQ(10, *operand); // Deallocate memory for (vector<IlocInstruction*>::iterator it = lines.begin(); it != lines.end(); ++it) { delete *it; } } // This test made sure I generated an output file correctly. // I built the file as an in memory representation, and then // output it. I had a "golden file" that was supposed to represent // the correct output. I compare my output to the golden file to // make sure it was correct. TEST_F(IlocParserTest, WriteIlocFileSimple) { // Setup instructions IlocInstruction instruction1(IlocInstruction::ADD); vector<int> operands; operands.push_back(1); operands.push_back(2); operands.push_back(3); instruction1.CopyOperandsFrom(operands); operands.clear(); IlocInstruction instruction2(IlocInstruction::LOADI); operands.push_back(17); operands.push_back(10); instruction2.CopyOperandsFrom(operands); operands.clear(); IlocInstruction instruction3(IlocInstruction::OUTPUT); operands.push_back(1024); instruction3.CopyOperandsFrom(operands); // Propogate lines with the instructions vector<IlocInstruction*> lines; lines.push_back(&instruction1); lines.push_back(&instruction2); lines.push_back(&instruction3); // Write out the file string out_filename = test_name_ + "_output"; string golden_filename = test_name_ + "_golden"; IlocParser parser; EXPECT_TRUE(parser.WriteIlocFile(out_filename, lines)); // Read back output file and verify contents are as expected. string golden_file; string out_file; ReadFileAsString(golden_filename, &golden_file); ReadFileAsString(out_filename, &out_file); EXPECT_EQ(golden_file, out_file); } } // namespace compilers int main(int argc, char** argv) { // Boiler plate, test initialization testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }