Это вопрос любопытства по поводу принятых методов кодирования.Я (в первую очередь) разработчик Java и все больше и больше прилагаю усилия для модульного тестирования моего кода.Я потратил некоторое время на то, как написать наиболее тестируемый код, уделяя особое внимание руководству Google Как написать непроверяемый код (что стоит посмотреть, если вы его еще не видели).
Естественно, я недавно спорил с другом, более ориентированным на C ++, о преимуществах модели наследования каждого языка, и я подумал, что вытащу козырь, сказав, насколько сложнее программисты на C ++ проверяют свой код.постоянно забывая ключевое слово virtual
(для C ++ - это значение по умолчанию в Java; вы избавляетесь от него с помощью final
).
Я опубликовал пример кода, который, как мне казалось, продемонстрируетПреимущества модели Java довольно хорошо (полная версия на GitHub закончена).Короткая версия:
class MyClassForTesting {
private final Database mDatabase;
private final Api mApi;
void myFunctionForTesting() {
for (User u : mDatabase.getUsers()) {
mRemoteApi.updateUserData(u);
}
}
MyClassForTesting ( Database usersDatabase, Api remoteApi) {
mDatabase = userDatabase;
mRemoteApi = remoteApi;
}
}
Независимо от качества того, что я здесь написал, идея состоит в том, что класс должен сделать несколько (потенциально довольно дорогих) обращений к базе данных и некоторому API (возможно,на удаленном веб-сервере).myFunctionForTesting()
не имеет возвращаемого типа, так как вы можете это проверить?В Java, я думаю, что ответ не слишком сложен - мы высмеиваем:
/*** Tests ***/
/*
* This will record some stuff and we'll check it later to see that
* the things we expect really happened.
*/
ActionRecorder ar = new ActionRecorder();
/** Mock up some classes **/
Database mockedDatabase = new Database(ar) {
@Override
public Set<User> getUsers() {
ar.recordAction("got list of users");
/* Excuse my abuse of notation */
return new Set<User>( {new User("Jim"), new User("Kyle")} );
}
Database(ActionRecorder ar) {
this.ar = ar;
}
}
Api mockApi = new Api() {
@Override
public void updateUserData(User u) {
ar.recordAction("Updated user data for " + u.name());
}
Api(ActionRecorder ar) {
this.ar = ar;
}
}
/** Carry out the tests with the mocked up classes **/
MyClassForTesting testObj = new MyClassForTesting(mockDatabase, mockApi);
testObj.myFunctionForTesting();
// Check that it really fetches users from the database
assert ar.contains("got list of users");
// Check that it is checking the users we passed it
assert ar.contains("Updated user data for Jim");
assert ar.contains("Updated user data for Kyle");
Путем макетирования этих классов мы внедряем зависимости с нашими собственными облегченными версиями, на которые мы можем делать утверждения для модулятестирование и избегайте дорогостоящих, отнимающих много времени звонков в базу данных / api-land.Дизайнеры Database
и Api
не должны быть слишком осведомлены о том, что это то, что мы собираемся сделать, а дизайнер MyClassForTesting
, конечно, не должен знать!Мне кажется, это довольно хороший способ сделать что-то.
Мой друг на C ++, однако, ответил, что это ужасный хак, и есть веская причина, по которой C ++ не позволит вам сделать это!Затем он представил решение на основе Generics, которое делает то же самое.Для краткости, я просто перечислю часть решения, которое он дал, но снова вы можете найти все это на Github .
template<typename A, typename D>
class MyClassForTesting {
private:
A mApi;
D mDatabase;
public MyClassForTesting(D database, A api) {
mApi = api;
mDatabase = database;
}
...
};
, которое затем будет испытано многокак прежде, но с важными битами, которые заменяются, показанными ниже:
class MockDatabase : Database {
...
}
class MockApi : Api {
...
}
MyClassForTesting<MockApi, MockDatabase>
testingObj(MockApi(ar), MockDatabase(ar));
Итак, мой вопрос таков: какой метод предпочтительнее?Я всегда думал, что подход, основанный на полиморфизме, был лучше - и я не вижу причин, по которым он не был бы в Java - но обычно считается, что лучше использовать Generics, чем виртуализировать все в C ++?Что вы делаете в вашем коде (при условии, что вы делаете модульный тест)?