Хрупкость тестов - это да. Но если Вы имеете в виду, что не стоит выставлять что-то наружу для всех только ради тестов, то это мудрая мысль. Публичный контракт - серьезное обязательство, а отказ от него сильно расстраивает пользователей, даже если делается по всем правилам этикета с предупреждениями obsolete в переходных версиях. Товарищ в коментарии ниже совершенно прав. Но существуют приемы открытия чего-то только для тестов. Вот есть класс:
public class Subject
{
private int value;
public Subject(int value) => this.value = value;
}
А нужен тривиальный тест, проверяющий присвоение значения полю в конструкторе. Тогда поле делаем видимым для наследника, а в тестопригодном наследнике выставляем наружу:
public class Subject
{
private int value;
// exposing a private field to subclasses
// is less harm than making it public
protected int Value => value;
public Subject(int value) => this.value = value;
}
public class TestableSubject : Subject
{
// transitively grant client code (i.e.test)
// access to the private field
public new int Value => base.Value;
public TestableSubject(int value) : base(value) { }
}
И тестируем спокойно:
[Test]
public void NormalTest()
{
// Arrange
int value = 42;
// Act
var sut = new TestableSubject(value);
// Assert
Assert.AreEqual(value, sut.Value);
}
Правда, такой фокус потом любой пользователь библиотеки повторить сможет.
public class Subject
{
private int value;
public Subject(int value) => this.value = value;
}
public class TestableSubject : Subject
{
public int Value => (int)typeof(Subject).GetField("value",
BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(this);
public TestableSubject(int value) : base(value) { }
}
Еще для .NET есть штуки вроде этой, которые могут патчить код после компиляции. Клиенты получают оригинальную библиотеку, а тесты патченную с открытым доступом к чему угодно. Думаю, для других платформ аналоги тоже есть.
Тесты, о которых Вы пишете - то, к чему надо стремиться. В рафинированном виде такие встречаются только в обучающих материалах по TDD. В жизни обычно приходишь на новый проект, смотришь на юнит тесты и ужасаешься. В некоторых компаниях их до сих пор вообще не применяют. А даже там, где юнит тестирование организованно более или менее прилично, могут быть иерархии наследования тестовых классов с переопределениями вспомогательных методов типа SetUp(), TearDown() или CreateSut(), чтобы не было дублирующегося кода. Если впадать в крайности пуризма, то такие методы даже без наследования тестовых классов надо объявлять ересью.
может ли посторонний человек посмотреть на код теста...
Это решается конвенционально. Также как, например, организации тестов по принципу AAA или создание sut только через CreateSut(). Просто вся команда знает (и в проекте даже соответствующая инструкция лежит), что если тест помечен DeclarativeCaseAttribute<TValidator, TScript, TCheck>, то для
[TestFixture]
public class DemoTests : IDeclarativeTest
{
public void Test<TValidator, TScript, TCheck>(bool expected)
where TValidator : IValidator, new()
where TScript : IScript, new()
where TCheck : ICheck, new() =>
DefaultDeclarativeTest.Test<TValidator, TScript, TCheck>(expected);
[DeclarativeCase<OrdinaryValidator, AdvancedAttack, AttackDetected>(false)]
[DeclarativeCase<AdvancedValidator, AdvancedAttack, AttackDetected>(true)]
public void TestAdvancedAttack() { }
}
это означает, что фактически тест содержится в void Test<TValidator, TScript, TCheck>(bool) тестового класса, а для
[TestFixture, Declarative]
public class DemoTests
{
[DeclarativeCase<OrdinaryValidator, AdvancedAttack, AttackDetected>(false)]
[DeclarativeCase<AdvancedValidator, AdvancedAttack, AttackDetected>(true)]
public void TestAdvancedAttack() { }
}
подразумевается, что непосредственно код теста лежит в void Test() аттрибута (пара кликов/хот-кеев - и перед Вами фактическая реализация теста). Если так делать постоянно, то оно начнет восприниматься, как само собой разумеющееся. Многие ли задумываются о том, что происходит под капотом, когда для добавления теста просто пишут [Test], а потом в Test Explorer видят результат его выполнения?
Вообще статья является продолжением другой публикации с пометкой "ненормальное программирование". Я необоснованно предположил, что в сознании читающего этот аттрибут неявно наследуется. Сейчас исправлю. В обеих описывается не как нужно делать, а как можно. Вот пришла человеку в голову занимательная идея, он ее воплотить попытался, глубже в инструменты погрузился, столкнулся с ограничениями, но нашел workaround'ы, а свой опыт здесь описал. Наверное, с названием "Как хакнуть NUnit, чтобы заставить делать странное" воспринималось бы иначе.
Вот автор вопроса на System.dll не ссылается, но если ему понадобятся LinkedList<T>, ObservableCollection<T> или Timer оттуда, то он её добавит. И тогда можно будет использовать Process из этой библиотеки:
using (var process = Process.Start(
new ProcessStartInfo(
"cmd.exe",
"/c echo WASTED> <file>")
{
CreateNoWindow = true,
UseShellExecute = false,
}))
process.WaitForExit();
Кроме Activator'а, там еще почти отсутствует защита от рефлексии. И вместо:
var type = Type.GetType("System.IO.StreamWriter");
using (var sw = (IDisposable)Activator.CreateInstance(type, new object[] { "<file>" }))
type.GetMethod("Write", new[] { typeof(string) }).Invoke(sw, new object[] { "WASTED" });
С одной стороны, да. А с другой, у автора вопроса может быть свой редактор кода с подсветкой синтаксиса, IntelliSense и прочим, и он хочет как-то визуально обращать внимание пользователя на недопустимые вызовы. Мы не знаем точно.
Да, действительно, это скорее про особенности yield и того, что генерируется под копотом. И код действительно нелинеен. Но, к счастью, и не параллелен, и поэтому наглядно пробегается в дебагере. Выглядит занятно. Особенное если это какое-нибудь нагромождение IEnumerable(IEnumerable(IEnumerable)) типа цепочек LINQ.
И про
Да, вот как раз в этом и дело. По-моему, просто пока не будет первого MoveNext() выполнение метода с yield вообще даже не начнется.
верно подмечено.
Поэтому, кстати, рекомендуют отделять проверки аргументов от yield. Иначе можно получить исключение в несовсем ожидаемом месте.
Пример:
Пусть есть такой код:
static object CreateObject(object arg)
{
if (arg == null)
throw new ArgumentNullException(nameof(arg));
return null;
}
static IEnumerable<object> CreateEnumerable(object arg)
{
if (arg == null)
throw new ArgumentNullException(nameof(arg));
yield return null;
}
static void Test(object obj)
{
if (obj is IEnumerable enumerable)
{
var enumerator = enumerable.GetEnumerator();
Console.WriteLine(enumerator.ToString());
Console.WriteLine(enumerator.GetType());
Console.WriteLine(enumerator.GetHashCode());
Console.WriteLine(enumerator.Equals(null));
Console.WriteLine(enumerator.Current ?? "NULL");
enumerator.MoveNext();
}
}
Тогда для Test(CreateObject(null)) вывод будет:
Unhandled exception. System.ArgumentNullException: Value cannot be null. (Parameter 'arg')
at TryFinally.Program.CreateObject(Object arg) in C:\Users\Admin\source\repos\TryFinally\TryFinally\Program.cs:line 19
at TryFinally.Program.Main(String[] args) in C:\Users\Admin\source\repos\TryFinally\TryFinally\Program.cs:line 11
Вызов метода Test не произошел.
А для Test(CreateEnumerable(null)):
TryFinally.Program+<CreateEnumerable>d__2
TryFinally.Program+<CreateEnumerable>d__2
58225482
False
NULL
Unhandled exception. System.ArgumentNullException: Value cannot be null. (Parameter 'arg')
at TryFinally.Program.CreateEnumerable(Object arg)+MoveNext() in C:\Users\Admin\source\repos\TryFinally\TryFinally\Program.cs:line 27
at TryFinally.Program.Test(Object obj) in C:\Users\Admin\source\repos\TryFinally\TryFinally\Program.cs:line 40
at TryFinally.Program.Main(String[] args) in C:\Users\Admin\source\repos\TryFinally\TryFinally\Program.cs:line 11
Метод Test не только был вызван, но и спокойно обращался к членам enumerator до вызова enumerator.MoveNext().
Кстати, try-finally с foreach имеет свои особенности.
Допустим, есть метод:
IEnumerable<int> CountDown(int count, int? stopAt = null, int? failAt = null)
{
try
{
while (count > -1)
{
yield return count;
if (count == stopAt)
yield break;
if (count == failAt)
throw new Exception($"Inner failure at {nameof(count)} = {count}");
count--;
}
}
finally
{
Console.WriteLine("Finally!!!");
}
}
Тогда
foreach (var i in CountDown(3))
Console.WriteLine(i);
Даст
3
2
1
0
Finally!!!
CountDown(3, stopAt: 1)выведет
3
2
1
Finally!!!
А CountDown(3, failAt: 1)
3
2
1
Unhandled exception. System.Exception: Inner failure at count = 1
at TryFinally.Program.CountDown(Int32 count, Nullable`1 stopAt, Nullable`1 failAt)+MoveNext() in C:\Users\Admin\source\repos\TryFinally\TryFinally\Program.cs:line 63
at TryFinally.Program.Main(String[] args) in C:\Users\Admin\source\repos\TryFinally\TryFinally\Program.cs:line 10
Finally!!!
Ранний breakfinally не помеха:
Код
foreach (var i in CountDown(3, failAt: 1))
{
Console.WriteLine(i);
break;
}
3
Finally!!!
Грубо говоря
foreach (var i in CountDown(3))
Console.WriteLine(i);
развернется в
Код
var enumerator = CountDown(3).GetEnumerator();
try
{
int i;
while (enumerator.MoveNext())
{
i = enumerator.Current;
Console.WriteLine(i);
}
}
finally
{
enumerator?.Dispose();
}
и результат будет идентичен foreach и для CountDown(3).GetEnumerator(), и для CountDown(3, stopAt: 1).GetEnumerator(), и для CountDown(3, failAt: 1).GetEnumerator(), и для break; после Console.WriteLine(i);.
Если закомментировать enumerator.Dispose();
Код
var enumerator = CountDown(3, stopAt: 1).GetEnumerator();
try
{
int i;
while (enumerator.MoveNext())
{
i = enumerator.Current;
Console.WriteLine(i);
break;
}
}
finally
{
// enumerator.Dispose();
}
то в выводе получим просто 3, и можно предположить, что код блока finally метода CountDown вызывается в enumerator.Dispose().
Но если также закомментировать и break;, то вывод будет:
3
2
1
Finally!!!
Если открыть сборку с методом с помощью ILSpy, метод примерно выглядит так:
И можно заметить, что код блока finally метода CountDown вызывается после yield break;. Один из них используется явно, а второй добавлен автоматически в конец метода компилятором.
А теперь выполним
var enumerator = CountDown(3, failAt: 1).GetEnumerator();
try
{
int i;
while (enumerator.MoveNext())
{
i = enumerator.Current;
Console.WriteLine(i);
}
}
finally
{
// enumerator.Dispose();
}
и получим
3
2
1
Unhandled exception. System.Exception: Inner failure at count = 1
at TryFinally.Program.CountDown(Int32 count, Nullable`1 stopAt, Nullable`1 failAt)+MoveNext() in C:\Users\Admin\source\repos\TryFinally\TryFinally\Program.cs:line 66
at TryFinally.Program.Main(String[] args) in C:\Users\Admin\source\repos\TryFinally\TryFinally\Program.cs:line 40
Finally!!!
То есть исключение тоже запустило выполнение блока finally.
Таким образом, при использовании try-finally с yield return, блок finally выполняется:
в enumerator.Dispose();
после исключения
и после yield break;
И не выполняется вовсе при невыполнении ни одного из этих условий.
Кстати
var enumerator = CountDown(3, failAt: 1).GetEnumerator();
enumerator.Dispose();
Все равно не запустит finally. Нужен хотябы один вызов enumerator.MoveNext().
Иногда замечаю странную запятую по типу a, b, and c в английских фразах в Duolingo. Всегда думал, что их программисты криво сделали составление предложений по шаблонам. (После примеров вроде «Мальчик ест мороженое с горчицей» или «Женщина разговаривает с конём» можно предположить, что они создаются скриптом.) А это, оказывается, на оксфордский манер.
Когда я учился, дневники были бумажные. И там была суббота, которая почти всегда пустовала. Вот живо представил, как администрация школы могла продавать эти рекламные площади местным ИП, а учителя бы объявления вклеивали. И как только они не додумались? Хоть сейчас предпринимательская мысль вперед шагнула.
Нет. Пример из реального кода. Типы назывались иначе и имели более сложное поведение и критерий слияния. От лишних деталей я абстрагировался для краткости, и чтобы не отвлекать внимание от главной цели (и NDA ненароком не нарушить). Вот и вышло так игрушечно.
Исходный вариант метода — это, грубо говоря, наивная реализация. Чтобы такое написать, необязательно знать толк в извращениях.
Золотые слова. Был когда-то неприятный опыт общения с компанией, назовем их условно brainbucket. Успешно прошел тех. интервью, был почти уверен, что сделают предложение. Пока тянули с ответом даже успел отказаться от оффера из другой компании (они об этом, кстати, знали). В итоге мне отказали с формулировкой «не соответствует ценностям компании». Так вот, они активно сотрудничают с кадровыми агентствами, и в этот раз от целых двух получил письма с предложениями рассмотреть вакансию той компании. Оба раза, не задумываясь, отказался. Зп хорошая и соц. пакет солидный, но, к сожалению, человеческое уважение в него не входит.
Сейчас вот тоже в поиске, и в этот раз как-то необычайно везет на интересные места. Прям поветрие какое. За три недели целая коллекция набралась.
— В одной гос. конторе агрессивная HR интересовалась кредитной историей и наличием просрочек по платежам. На вопрос, как это относится к работе, ответила, что это показатель порядочности и надежности человека. Затем спросила, согласен ли я на уменьшение зарплаты на первое время, т.к. не сразу включусь в работу на полную силу. Несмотря на отрицательный ответ пригласила на тех. собеседование, где в присутствии тех. специалиста задавала вопросы на психологический портрет. С самим специалистом разговора по существу не было. Но удалось выяснить, что в работе используется нелицензионное ПО. Потом она пообещала вернуться с ответом до конца недели и пропала.
— В другом месте человек (который единственный вышел со мной на связь по телефону, а не по почте, указанной как предпочтительный способ связи) под видом предложения о работе впаривал регистрацию на левом рекрутинговом сайте по его реферальной ссылке. Потом еще несколько раз звонил.
— Женщина, представившаяся сотрудницей кадрового агенства, на вопрос, почему она пишет не с корпоративной почты, а с «маша.эйчар@джимэйл.ком», кажется, обиделась и не стала отвечать.
— Еще одна компания предложила оффлайн собеседование. Поскольку они такие были единственные, решил погуглить их. Оказалось, что они существуют менее пяти лет, стабильно терпят убытки, несколько раз выступали ответчиками в суде и меняли данные юр. лица.
— Один представитель американского аутсорсера заявил, что в их цивилизованном мире слово джентльмена не подлежит сомнению, и предложил работать без договора. Прошел общее и тех. интервью ради практики английского и вежливо попрощался.
— Из одного стартапа пришло письмо, типа я с моим опытом могу у них сразу техлидом стать. Поскольку опыт мой независимо от размера точно не релевантен их деятельности (они занимаются супер-пупер-блокчейн-криптотрейдингом), решил поискать отзывы сотрудников. Отзывов оказалось много. И ни одного отрицательного. Но зато как красноречивы были положительные. Везде расписаны сказочные плюсы, а среди минусов только «Психологически трудно привыкнуть к высокому уровню доходов после обычной нищенской зарплаты», «Не знаю, куда девать подержаную kia, т.к. с первой зарплаты купил новенькую bmw», «Пришлось бросить девушку, потому что теперь нет отбоя от супер-моделей». Не хватает только «Посоны ИНФА 100%».
— Еще одна трейдинговая компания дала тестовое, больше похожее на хакатон. Обязательным требованием было невыкладывание решения в отрытый доступ.
— И мое пока самое любимое. Связавшиеся назвались представителями разработчика известной CRM. Прошел тех. интервью. Оно было вполне адекватным. Через пару дней звонок с оффером. Предложили на 20к больше моих ожиданий. Попросил выслать письмо, а сам посмотрел описание вакансии. Оказалось, что зп хоть и выше моих ожиданий, но на 30к меньше суммы в описании. Ладно, думаю, это мелочь, мне все равно хватает, но менеджеру не стоило представлять это как аттракцион невиданной щедрости. Дальше захожу на почту и вижу письмо, но отправлено оно с домена, отличного от того, с которого велась предыдущая переписка. Он принадлежит молодому ноунейм ООО с минимальным уставным капиталом. Оказывается, собеседовали не в известную фирму, а туда, но типа от ее имени. Хорошо, думаю, проделан большой путь и, если договор более или менее адекватный, стоит принять оффер. Прошу выслать договор и выпадаю в осадок. Там в размытых и обтекаемых формулировках минимизируется ответственность работодателя и максимизируется ответственность работника, права соответственно наоборот. Как вишенка на торте — фактически обязательство работника не работать в IT в течение трех лет после увольнения.
Били и отказы без собеседования, и нормальные собеседования, проходившие вроде бы по моим ощущениям хорошо, но остававшиеся без ответа. Были успешные тех. интервью, после которых назначались менеджерские, а менеджер решал, что у меня не достаточно опыта или что я не достаточно мотивирован/не разделяю их ценности.
Но я не унываю. И ты, читающий это и находящийся в похожем положении, не унывай. Приличную работу в нашем деле найти можно. Автор публикации на своём примера показал. Это вопрос времени. А о забавном опыте потом можно будет рассказывать анекдоты.
Есть кадры, которые предлагают либо электронную почту, либо лично, когда они все в офис выйдут. Тогда же заодно и договор подписать, а до этого поработать на честном слове. Такие, конечно, сразу отказ получают.
Про выделение памяти полезно ещё в Diagnostic Tools глянуть на выходе из метода.
Выше приведенный код даст помимо
ArrayList
миллион упакованныхInt32
.ArrayList
здесь ссылается наObject[]
, который ссылается наInt32
.Использование
List<int>
дает другую картину.Никаких упакованных
Int32
.List<Int32>
ссылается наInt32[]
. Использование памяти значительно сокращено.Хрупкость тестов - это да. Но если Вы имеете в виду, что не стоит выставлять что-то наружу для всех только ради тестов, то это мудрая мысль. Публичный контракт - серьезное обязательство, а отказ от него сильно расстраивает пользователей, даже если делается по всем правилам этикета с предупреждениями obsolete в переходных версиях. Товарищ в коментарии ниже совершенно прав. Но существуют приемы открытия чего-то только для тестов. Вот есть класс:
А нужен тривиальный тест, проверяющий присвоение значения полю в конструкторе. Тогда поле делаем видимым для наследника, а в тестопригодном наследнике выставляем наружу:
И тестируем спокойно:
Правда, такой фокус потом любой пользователь библиотеки повторить сможет.
У .NET есть InternalsVisibleToAttribute и вообще рефлексия:
Еще для .NET есть штуки вроде этой, которые могут патчить код после компиляции. Клиенты получают оригинальную библиотеку, а тесты патченную с открытым доступом к чему угодно. Думаю, для других платформ аналоги тоже есть.
Тесты, о которых Вы пишете - то, к чему надо стремиться. В рафинированном виде такие встречаются только в обучающих материалах по TDD. В жизни обычно приходишь на новый проект, смотришь на юнит тесты и ужасаешься. В некоторых компаниях их до сих пор вообще не применяют. А даже там, где юнит тестирование организованно более или менее прилично, могут быть иерархии наследования тестовых классов с переопределениями вспомогательных методов типа SetUp(), TearDown() или CreateSut(), чтобы не было дублирующегося кода. Если впадать в крайности пуризма, то такие методы даже без наследования тестовых классов надо объявлять ересью.
Это решается конвенционально. Также как, например, организации тестов по принципу AAA или создание sut только через CreateSut(). Просто вся команда знает (и в проекте даже соответствующая инструкция лежит), что если тест помечен
DeclarativeCaseAttribute<TValidator, TScript, TCheck>
, то дляэто означает, что фактически тест содержится в
void Test<TValidator, TScript, TCheck>(bool)
тестового класса, а дляподразумевается, что непосредственно код теста лежит в
void Test()
аттрибута (пара кликов/хот-кеев - и перед Вами фактическая реализация теста). Если так делать постоянно, то оно начнет восприниматься, как само собой разумеющееся. Многие ли задумываются о том, что происходит под капотом, когда для добавления теста просто пишут[Test]
, а потом в Test Explorer видят результат его выполнения?Вообще статья является продолжением другой публикации с пометкой "ненормальное программирование". Я необоснованно предположил, что в сознании читающего этот аттрибут неявно наследуется. Сейчас исправлю. В обеих описывается не как нужно делать, а как можно. Вот пришла человеку в голову занимательная идея, он ее воплотить попытался, глубже в инструменты погрузился, столкнулся с ограничениями, но нашел workaround'ы, а свой опыт здесь описал. Наверное, с названием "Как хакнуть NUnit, чтобы заставить делать странное" воспринималось бы иначе.
Или даже так:
Вот автор вопроса на
System.dll
не ссылается, но если ему понадобятсяLinkedList<T>
,ObservableCollection<T>
илиTimer
оттуда, то он её добавит. И тогда можно будет использоватьProcess
из этой библиотеки:Кроме Activator'а, там еще почти отсутствует защита от рефлексии. И вместо:
можно, например:
И вполне возможно, что на этом список уязвимостей не исчерпывается.
Такого кодоанализатора, на сколько я знаю, нет. Из коробки есть песочница AppDomain. Но в последних версиях фреймворка от нее осталось одно название. На поддержку AppDomain.PermissionSet, таких, напрмер, как FileIOPermission, подзабили. Еще есть Managed Add-In Framework для работы с плагинами. В новых версия фактически тоже не поддерживается.
С одной стороны, да. А с другой, у автора вопроса может быть свой редактор кода с подсветкой синтаксиса, IntelliSense и прочим, и он хочет как-то визуально обращать внимание пользователя на недопустимые вызовы. Мы не знаем точно.
Да, действительно, это скорее про особенности yield и того, что генерируется под копотом.
И код действительно нелинеен. Но, к счастью, и не параллелен, и поэтому наглядно пробегается в дебагере. Выглядит занятно. Особенное если это какое-нибудь нагромождение IEnumerable(IEnumerable(IEnumerable)) типа цепочек LINQ.
И про
верно подмечено.
Поэтому, кстати, рекомендуют отделять проверки аргументов от yield. Иначе можно получить исключение в несовсем ожидаемом месте.
Пример:
Пусть есть такой код:
Тогда для
Test(CreateObject(null))
вывод будет:Вызов метода
Test
не произошел.А для
Test(CreateEnumerable(null))
:Метод
Test
не только был вызван, но и спокойно обращался к членамenumerator
до вызоваenumerator.MoveNext()
.Кстати,
try-finally
сforeach
имеет свои особенности.Допустим, есть метод:
Тогда
Даст
CountDown(3, stopAt: 1)
выведетА
CountDown(3, failAt: 1)
Ранний
break
finally
не помеха:Код
Грубо говоря
развернется в
Код
и результат будет идентичен
foreach
и дляCountDown(3).GetEnumerator()
, и дляCountDown(3, stopAt: 1).GetEnumerator()
, и дляCountDown(3, failAt: 1).GetEnumerator()
, и дляbreak;
послеConsole.WriteLine(i);
.Если закомментировать
enumerator.Dispose();
Код
то в выводе получим просто
3
, и можно предположить, что код блокаfinally
методаCountDown
вызывается вenumerator.Dispose()
.Но если также закомментировать и
break;
, то вывод будет:Если открыть сборку с методом с помощью ILSpy, метод примерно выглядит так:
Код
И можно заметить, что код блока
finally
методаCountDown
вызывается послеyield break;
. Один из них используется явно, а второй добавлен автоматически в конец метода компилятором.А теперь выполним
и получим
То есть исключение тоже запустило выполнение блока
finally
.Таким образом, при использовании
try-finally
сyield return
, блокfinally
выполняется:в
enumerator.Dispose();
после исключения
и после
yield break;
И не выполняется вовсе при невыполнении ни одного из этих условий.
Кстати
Все равно не запустит
finally
. Нужен хотябы один вызовenumerator.MoveNext()
.Исходный вариант метода — это, грубо говоря, наивная реализация. Чтобы такое написать, необязательно знать толк в извращениях.
— В одной гос. конторе агрессивная HR интересовалась кредитной историей и наличием просрочек по платежам. На вопрос, как это относится к работе, ответила, что это показатель порядочности и надежности человека. Затем спросила, согласен ли я на уменьшение зарплаты на первое время, т.к. не сразу включусь в работу на полную силу. Несмотря на отрицательный ответ пригласила на тех. собеседование, где в присутствии тех. специалиста задавала вопросы на психологический портрет. С самим специалистом разговора по существу не было. Но удалось выяснить, что в работе используется нелицензионное ПО. Потом она пообещала вернуться с ответом до конца недели и пропала.
— В другом месте человек (который единственный вышел со мной на связь по телефону, а не по почте, указанной как предпочтительный способ связи) под видом предложения о работе впаривал регистрацию на левом рекрутинговом сайте по его реферальной ссылке. Потом еще несколько раз звонил.
— Женщина, представившаяся сотрудницей кадрового агенства, на вопрос, почему она пишет не с корпоративной почты, а с «маша.эйчар@джимэйл.ком», кажется, обиделась и не стала отвечать.
— Еще одна компания предложила оффлайн собеседование. Поскольку они такие были единственные, решил погуглить их. Оказалось, что они существуют менее пяти лет, стабильно терпят убытки, несколько раз выступали ответчиками в суде и меняли данные юр. лица.
— Один представитель американского аутсорсера заявил, что в их цивилизованном мире слово джентльмена не подлежит сомнению, и предложил работать без договора. Прошел общее и тех. интервью ради практики английского и вежливо попрощался.
— Из одного стартапа пришло письмо, типа я с моим опытом могу у них сразу техлидом стать. Поскольку опыт мой независимо от размера точно не релевантен их деятельности (они занимаются супер-пупер-блокчейн-криптотрейдингом), решил поискать отзывы сотрудников. Отзывов оказалось много. И ни одного отрицательного. Но зато как красноречивы были положительные. Везде расписаны сказочные плюсы, а среди минусов только «Психологически трудно привыкнуть к высокому уровню доходов после обычной нищенской зарплаты», «Не знаю, куда девать подержаную kia, т.к. с первой зарплаты купил новенькую bmw», «Пришлось бросить девушку, потому что теперь нет отбоя от супер-моделей». Не хватает только «Посоны ИНФА 100%».
— Еще одна трейдинговая компания дала тестовое, больше похожее на хакатон. Обязательным требованием было невыкладывание решения в отрытый доступ.
— И мое пока самое любимое. Связавшиеся назвались представителями разработчика известной CRM. Прошел тех. интервью. Оно было вполне адекватным. Через пару дней звонок с оффером. Предложили на 20к больше моих ожиданий. Попросил выслать письмо, а сам посмотрел описание вакансии. Оказалось, что зп хоть и выше моих ожиданий, но на 30к меньше суммы в описании. Ладно, думаю, это мелочь, мне все равно хватает, но менеджеру не стоило представлять это как аттракцион невиданной щедрости. Дальше захожу на почту и вижу письмо, но отправлено оно с домена, отличного от того, с которого велась предыдущая переписка. Он принадлежит молодому ноунейм ООО с минимальным уставным капиталом. Оказывается, собеседовали не в известную фирму, а туда, но типа от ее имени. Хорошо, думаю, проделан большой путь и, если договор более или менее адекватный, стоит принять оффер. Прошу выслать договор и выпадаю в осадок. Там в размытых и обтекаемых формулировках минимизируется ответственность работодателя и максимизируется ответственность работника, права соответственно наоборот. Как вишенка на торте — фактически обязательство работника не работать в IT в течение трех лет после увольнения.
Били и отказы без собеседования, и нормальные собеседования, проходившие вроде бы по моим ощущениям хорошо, но остававшиеся без ответа. Были успешные тех. интервью, после которых назначались менеджерские, а менеджер решал, что у меня не достаточно опыта или что я не достаточно мотивирован/не разделяю их ценности.
Но я не унываю. И ты, читающий это и находящийся в похожем положении, не унывай. Приличную работу в нашем деле найти можно. Автор публикации на своём примера показал. Это вопрос времени. А о забавном опыте потом можно будет рассказывать анекдоты.