Vì vậy, bạn muốn có ví dụ? Học kỳ trước tôi đã tham gia một khóa học trình biên dịch. Trong đó chúng tôi đã phải viết một cấp phát đăng ký. Nói một cách đơn giản, chương trình của tôi có thể được tóm tắt như sau:
Đầu vào: Một tệp được viết bằng ngôn ngữ lập trình giả tạo cho sách giáo khoa của tôi. Các hướng dẫn trong tập tin có tên đăng ký như "r <number>
". Vấn đề là chương trình sử dụng nhiều thanh ghi như nó cần, thường lớn hơn số lượng thanh ghi trên máy đích.
Đầu ra: Một tệp khác được viết bằng ILOC. Lần này, các hướng dẫn được viết lại để nó sử dụng số lượng đăng ký tối đa chính xác được cho phép.
Để viết chương trình này, tôi phải tạo một lớp có thể phân tích cú pháp tệp ILOC. Tôi đã viết một loạt các bài kiểm tra cho lớp đó. Dưới đây là các bài kiểm tra của tôi (tôi thực sự đã có nhiều hơn, nhưng đã loại bỏ chúng để giúp rút ngắn điều này. Tôi cũng đã thêm một số ý kiến để giúp bạn đọc nó). Tôi đã làm dự án trong C++, vì vậy tôi đã sử dụng khung kiểm tra C++ của Google (googletest) có vị trí here.
Trước khi hiển thị mã cho bạn ... hãy để tôi nói điều gì đó về cấu trúc cơ bản. Về cơ bản, có một lớp kiểm tra. Bạn có thể đặt một loạt các công cụ thiết lập chung trong lớp thử nghiệm. Sau đó, có các macro thử nghiệm được gọi là TEST_F. Khung kiểm tra chọn lên trên và hiểu rằng chúng cần phải được chạy thử nghiệm. Mỗi TEST_F có 2 đối số, tên lớp kiểm tra và tên của bài kiểm tra (nên rất mô tả ... theo cách đó nếu kiểm tra không thành công, bạn biết chính xác những gì không thành công).Bạn sẽ thấy cấu trúc của mỗi thử nghiệm là tương tự: (1) thiết lập một số công cụ ban đầu, (2) chạy phương pháp bạn đang thử nghiệm, (3) xác minh đầu ra là chính xác. Cách bạn kiểm tra (3) là sử dụng các macro như EXPECT_ *. EXPECT_EQ(expected, result)
kiểm tra xem result
có bằng expected
không. Nếu không, bạn nhận được một thông báo lỗi hữu ích như "kết quả là blah, nhưng mong đợi Blah". Đây là mã (Tôi hy vọng điều này không quá khó hiểu ... nó chắc chắn không phải là một ví dụ ngắn hoặc dễ dàng, nhưng nếu bạn dành thời gian bạn sẽ có thể theo dõi và có được hương vị chung của nó như thế nào công trinh).
// 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();
}
Sau khi tất cả được nói và thực hiện ... TẠI SAO TÔI LÀM NÀY !? Vâng đầu tiên của tất cả. Tôi đã viết các bài kiểm tra từng bước khi tôi chuẩn bị viết từng đoạn mã. Nó đã giúp tôi yên tâm rằng mã tôi đã viết đã hoạt động đúng cách. Sẽ thật điên rồ khi viết tất cả mã của tôi và sau đó chỉ cần thử nó trên một tập tin và xem chuyện gì đã xảy ra. Có rất nhiều lớp, làm thế nào tôi có thể biết một lỗi sẽ đến từ đâu trừ khi tôi có từng mảnh nhỏ được thử nghiệm trong sự cô lập?
NHƯNG ... QUAN TRỌNG NHẤT! Thử nghiệm không thực sự về việc bắt các lỗi ban đầu trong mã của bạn ... đó là về việc bảo vệ bản thân khỏi vô tình phá vỡ mã của bạn. Mỗi lần tôi refactored hoặc thay đổi lớp IlocParser của tôi, tôi đã tự tin tôi đã không thay đổi nó một cách xấu bởi vì tôi có thể chạy thử nghiệm của tôi (trong vài giây) và thấy rằng tất cả các mã vẫn hoạt động như mong đợi. Đó là việc sử dụng các bài kiểm tra đơn vị tuyệt vời.
Có vẻ như họ mất quá nhiều thời gian ... nhưng cuối cùng, họ giúp bạn tiết kiệm thời gian theo dõi lỗi vì bạn đã thay đổi một số mã và không biết điều gì đã xảy ra. Họ là một cách hữu ích để xác minh rằng các đoạn mã nhỏ đang làm những gì họ có nghĩa vụ phải làm, và chính xác.
Sao y bản sao. Chỉ cần tìm kiếm tràn ngăn xếp cho "Thử nghiệm đơn vị" là gì và bạn sẽ có đủ để nhai trong nhiều tháng. – womp
@womp: Tôi vừa làm và tôi không thấy bất kỳ tựa game nào tương tự. –
http://stackoverflow.com/search?q=unit+testing – Nifle