-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathSample.cpp
More file actions
345 lines (279 loc) · 12.6 KB
/
Sample.cpp
File metadata and controls
345 lines (279 loc) · 12.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
#include <algorithm>
#include <cctype>
#include <functional>
#include <sstream>
// Include the test framework header.
#include "AccTest.h"
// All framework elements are isolated inside ProTest namespace.
using namespace ProTest;
// Abstract interface for the UI connected to our application under test.
class ICalcUserInterface {
public:
virtual void SetStatusBar(const std::string& msg) = 0;
virtual void SetResultContents(const std::string& msg) = 0;
virtual void SetTitleBar(const std::string& msg) = 0;
virtual void SetAddButtonCallBack(std::function<void () > callBack) = 0;
virtual void SetSubtractButtonCallBack(std::function<void () > callBack) = 0;
virtual std::string GetTextBoxContents() = 0;
virtual void Close() = 0;
};
// This is the main application to be tested. A pointer to the UI is passed to it during construction. We shall take advantage
// of this to pass in our own (fake, stub, mock, whatever) version of the UI. This is the only requirement for the application
// test. Database connections, IO, network, etc could be replaced with simulators or stubs might be used for them as well.
class MyCalcApplication {
public:
MyCalcApplication(ICalcUserInterface* ui)
: m_UI(ui) {
m_UI->SetAddButtonCallBack([this]() {
Add(); });
m_UI->SetSubtractButtonCallBack([this]() {
Subtract(); });
}
void StartUp() {
m_UI->SetTitleBar("My Calculator");
m_UI->SetStatusBar("Ready");
m_UI->SetResultContents("0");
}
void Exit() {
m_UI->Close();
}
private:
void Add() {
int value;
if (!ParseTextBox(value))
return;
std::ostringstream output;
m_CurrentResult += value;
output << m_CurrentResult;
m_UI->SetResultContents(output.str());
m_UI->SetStatusBar("Ready");
}
void Subtract() {
int value;
if (!ParseTextBox(value))
return;
std::ostringstream output;
m_CurrentResult -= value;
output << m_CurrentResult;
m_UI->SetResultContents(output.str());
m_UI->SetStatusBar("Ready");
}
bool ParseTextBox(int& value) {
auto text = m_UI->GetTextBoxContents();
if (std::find_if(text.cbegin(), text.cend(), [](char ch) {
return !std::isdigit(ch); }) != text.cend()) {
m_UI->SetStatusBar("Error");
return false;
}
std::istringstream input(text);
input >> value;
return true;
}
int m_CurrentResult = 0;
ICalcUserInterface* m_UI;
};
// Our fake version of the user interface implementing the same abstract interface but only used in test.
class FakeCaclUserInterface : public ICalcUserInterface {
public:
void SetAddButtonCallBack(std::function<void () > callBack) override {
m_AddButtonCallBack = callBack;
}
void SetSubtractButtonCallBack(std::function<void () > callBack) override {
m_SubtractButtonCallBack = callBack;
}
std::string GetTextBoxContents() override {
return m_TextBoxContents;
}
void SetStatusBar(const std::string& msg) override {
m_StatusBar = msg;
}
void SetResultContents(const std::string& msg) override {
m_ResultContents = msg;
}
void SetTitleBar(const std::string& msg) override {
m_TitleBar = msg;
}
void Close() override {
if (m_ExpectedCallsToClose > 0)
--m_ExpectedCallsToClose;
else
throw "Unexpected call to Close()";
}
void ExpectClose(int cardinality) {
m_ExpectedCallsToClose += cardinality;
}
bool VerifyExpectedClose() {
return m_ExpectedCallsToClose == 0;
}
std::string m_TitleBar, m_ResultContents, m_StatusBar, m_TextBoxContents;
std::function<void() > m_AddButtonCallBack, m_SubtractButtonCallBack;
private:
int m_ExpectedCallsToClose = 0;
};
// The test context is required to hold state of the application, test stubs, and other test related data. It is initialized
// within the test scenario Setup, used and affected by each step, passed on to the next step, and the disposed within the test
// test scenario Teardown.
struct CalcTestContext {
FakeCaclUserInterface* UI;
std::shared_ptr<MyCalcApplication> App;
};
// Each step of the test scenario inherits AccTestStep<T> where T is the type serving as our text context.
// Apart from that, every step class will probably need to override the Act() and Verify() methods. Some test steps might also
// need to override Setup(), Expect(), and Teardown(). See AccTestStep for more info.
class TestStepInitAppSetsCorrectUIState : public AccTestStep<CalcTestContext> {
public:
TestStepInitAppSetsCorrectUIState(const std::string& name)
: AccTestStep<CalcTestContext>(
name, "When the app is initialized title bar, status bar, and contents must be set correctly.", false, false) {
}
void Act() override {
auto ctx = GetTestContext();
ctx->App = std::make_shared<MyCalcApplication>(ctx->UI);
ctx->App->StartUp();
}
void Verify() override {
auto ui = GetTestContext()->UI;
ACC_TEST_CHECK_EQUAL(ui->m_TitleBar, "My Calculator");
ACC_TEST_CHECK_EQUAL(ui->m_StatusBar, "Ready");
ACC_TEST_CHECK_EQUAL(ui->m_ResultContents, "0");
}
};
// A parameterized test step: the parameters are passed in through constructor and determined when adding the instance to the
// scenario.
class TestStepInput_PressAdd_Status_Result : public AccTestStep<CalcTestContext> {
public:
TestStepInput_PressAdd_Status_Result(const std::string& testName,
const std::string& input, const std::string& status, const std::string& result)
: AccTestStep<CalcTestContext>(testName, CreateDescription(input, status, result), false, false),
m_Input(input), m_Status(status), m_Result(result) {
}
void Act() override {
auto ui = GetTestContext()->UI;
ui->m_TextBoxContents = m_Input;
ui->m_AddButtonCallBack();
}
void Verify() override {
auto ui = GetTestContext()->UI;
ACC_TEST_CHECK_EQUAL(ui->m_StatusBar, m_Status);
ACC_TEST_CHECK_EQUAL(ui->m_ResultContents, m_Result);
}
private:
static std::string CreateDescription(const std::string& input, const std::string& status, const std::string& result) {
std::ostringstream desc;
desc << "When the string \"" << input << "\" is put in and the Add button pressed, status bar must show \"" <<
status << "\" and the result must show \"" << result << "\".";
return desc.str();
}
std::string m_Input, m_Status, m_Result;
};
// Another test step
class TestStepInput_PressSubtract_Status_Result : public AccTestStep<CalcTestContext> {
public:
TestStepInput_PressSubtract_Status_Result(const std::string& testName,
const std::string& input, const std::string& status, const std::string& result)
: AccTestStep<CalcTestContext>(testName, CreateDescription(input, status, result), false, false),
m_Input(input), m_Status(status), m_Result(result) {
}
void Act() override {
auto ui = GetTestContext()->UI;
ui->m_TextBoxContents = m_Input;
ui->m_SubtractButtonCallBack();
}
void Verify() override {
auto ui = GetTestContext()->UI;
bool success = ui->m_StatusBar == m_Status && ui->m_ResultContents == m_Result;
ACC_TEST_CHECK_EQUAL(ui->m_StatusBar, m_Status);
ACC_TEST_CHECK_EQUAL(ui->m_ResultContents, m_Result);
}
static std::string CreateDescription(const std::string& input, const std::string& status, const std::string& result) {
std::ostringstream desc;
desc << "When the string \"" << input << "\" is put in and the Subtract button pressed, status bar must show \"" <<
status << "\" and the result must show \"" << result << "\".";
return desc.str();
}
std::string m_Input, m_Status, m_Result;
};
// Another test step
class TestStepExitingAppMustCloseTheUI : public AccTestStep<CalcTestContext> {
public:
TestStepExitingAppMustCloseTheUI(const std::string& name)
: AccTestStep<CalcTestContext>(name, "When close request is sent to the app, "
"the Close() from user interface must be called once.", false, false) {
}
void Expect() override {
auto ui = GetTestContext()->UI;
ui->ExpectClose(1);
}
void Act() override {
GetTestContext()->App->Exit();
GetTestContext()->App = nullptr;
}
void Verify() override {
auto ui = GetTestContext()->UI;
Check(ui->VerifyExpectedClose()) << "Close not called on the GUI!";
}
};
// Each test scenario, which is usually composed of many steps, must inherit AccTestScenario. During construction, it must add
// its respective test steps using AddStep in the same order which they must be run. The test scenario creates the context for you,
// but you need to create the application object and other test stubs. This can be done by overriding Setup(). Make sure to perform
// any clean-ups required within Teardown() by overriding it.
class MyTestScenario : public AccTestScenario<CalcTestContext> {
public:
MyTestScenario()
: AccTestScenario<CalcTestContext>("MyTestScenario", "This is the only scenario, at least until now!") {
CreateStep<TestStepInitAppSetsCorrectUIState>("1_InitApp_MustSet_Status_Result_Title");
CreateStep<TestStepInput_PressAdd_Status_Result>(
"2_Input10_Add_Result10_Status_Ready", "10", "Ready", "10");
CreateStep<TestStepInput_PressAdd_Status_Result>(
"3_Input20_Add_Result30_Status_Ready", "20", "Ready", "30");
CreateStep<TestStepInput_PressSubtract_Status_Result>(
"4_Input15_Subtract_Result15_Status_Ready", "15", "Ready", "15");
CreateStep<TestStepInput_PressSubtract_Status_Result>(
"5_Input7_Subtract_Result8_Status_Ready", "7", "Ready", "8");
CreateStep<TestStepInput_PressAdd_Status_Result>(
"6_Input52_Add_Result60_Status_Ready", "52", "Ready", "60");
CreateStep<TestStepInput_PressAdd_Status_Result>(
"7_Input\"3gsg\"_Add_Result60_Status_Error", "3gsg", "Error", "60");
CreateStep<TestStepInput_PressSubtract_Status_Result>(
"8_Input23_Subtract_Result37_Status_Ready", "23", "Ready", "37");
CreateStep<TestStepInput_PressSubtract_Status_Result>(
"9_Input\"sdvn8e\"_Subtract_Result8_Status_Ready", "sdvn8e", "Error", "37");
CreateStep<TestStepInput_PressAdd_Status_Result>(
"10_Input32_Add_Result69_Status_Ready", "32", "Ready", "69");
CreateStep<TestStepExitingAppMustCloseTheUI>("11_ExitApp_UIMustBeClosed");
CreateStep<TestStepInitAppSetsCorrectUIState>("12_InitApp_MustSet_Status_Result_Title");
// Yes, we can start the app again after exiting! Sure some scenarios work like that; like when you're testing persistence
// of app state, you definitely need to exit the app and start it again to see if things stay the same after restarting.
CreateStep<TestStepInput_PressSubtract_Status_Result>(
"13_Input-25_Subtract_Result25_Status_Ready", "-25", "Ready", "25");
// Step 13 will fail because our calculator doesn't accommodate for negative numbers and treats them as non-numerical
// strings. Therefore, our subsequent steps that count on current app state will probably fail.
CreateStep<TestStepInput_PressAdd_Status_Result>(
"14_Input20_Add_Result45_Status_Ready", "20", "Ready", "45");
CreateStep<TestStepExitingAppMustCloseTheUI>("15_ExitApp_UIMustBeClosed");
}
void Setup() override {
auto ctx = GetTestContext();
ctx->UI = new FakeCaclUserInterface();
}
void Teardown() override {
auto ctx = GetTestContext();
delete ctx->UI;
}
};
// Each test executable must have exactly one test suite. Ours has only one scenario (start to finish).
class MyTestSuite : public AccTestSuite {
public:
MyTestSuite() {
CreateScenario<MyTestScenario>();
}
};
// Use the default main() function. You could also clone macro contents and create your own main() function to customize your test
// application. But why would you want to do that, I don't get it! Anyway, essentially, this inserts a main function that creates
// an instance of MyTestSuite and passes it to an instance of AccTestRunner. AccTestRunner will take care of your argc and argv.
ACC_TEST_DEFAULT_MAIN_FUNC(MyTestSuite)